Luke
-
Posts
879 -
Joined
Content Type
Forums
Events
Downloads
Gallery
Mods
News
Store
Posts posted by Luke
-
-
12 hours ago, polytope said:
I hope that a subspell with a name starting with something other than "SP-"
A quick test shows the trick works fine, so we can proceed me thinks...
-
3 hours ago, polytope said:
I'm not positive it works on Enhanced Edition, I don't have EE installed on the device I'm currently using, but it certainly worked on the original engine.
Apparently, it works fine on EEs too...
3 hours ago, polytope said:timing mode 0 target type 2 (I'm less certain about those two)
Also these two values will be inherited from the feature block of the spell, you can safely leave them at 0...
3 hours ago, polytope said:(what if the player actually does suffer a curse they need removing in the meanwhile?).
Yeah, that would be a problem... Maybe @kjeron knows a workaround...? I'm not positive...
It'd be much much easier if EE mods adapted to the new splstate system, there would be lots of benefits...
-
6 hours ago, polytope said:
As DavidW said... replacing 101/206 on cre immunity items with 324 in spell or item inflicting the effect needs to come absolutely last in the mod install order or it will break things.
15 hours ago, DavidW said:I certainly see the appeal of this; the problem is that it would wreak absolute havoc on mods. Any newly-added effects that, e.g., confuse or hold will no longer be blocked
Yes, sure, mods will need to adapt to the new system, otherwise it's a mess... Also, having such a tweak last in the mod install order might be a problem (it might be problematic to tell if an op39 effect is, say, Unconsciousness or Knockdown, especially considering mod-added assets...)
6 hours ago, polytope said:My old school way of implementing this was to use short duration opcode 283 which bypasses 101 immunity to other opcodes; thus you can have physical knockdowns through opcode 39 as a separate EFF applied through 283 on creatures normally immune to "sleep" etc.
This is interesting, I did not know it, and it can indeed solve this issue... What do you mean exactly by "short duration"...? I mean, EFF files applied through op177/283 ignore the Duration field (i.e., they use whatever Duration you set on the op177/283...) Could you please attach here how you would code, say, SPIN695.SPL to force the creature to fall down...? Also, the fact that op283 can be removed by op26 (Remove Curse) is a problem, right...? So maybe we should also add an immunity to op26...?
For @Bubb: apparently, EFF files applied by op283 can bypass / ignore op101. Bug or Feature...? Something else to know about it...?
-
On 5/19/2022 at 12:30 AM, CamDawg said:
As a fallback, items or spells that provide immunities would still provide the normal suite of effects you'd see from the BG2 Fixpack immunity batches mentioned in the overview--blocking the opcode, associated portrait icons, combat feedback, and the like.
(also calling @DavidW)
What if I told you this is actually a problem and should be removed on EE games...?
That is to say: what if certain creatures should be immune to, say, all confusion effects except if coming from a specific spell/attack (or from some specific spells/attacks)?
As you all know, we have two Panic opcodes, two Hold opcodes, two Charm opcodes, etc... but we don't have two Confusion opcodes! Additionally, op337 does not work with a limited timing mode (the two Bigby spells use this trick to force the targeted creature to fall down, but the problem is that the immunity is not restored)... plus it cannot remove effects applied with timing mode 9...
Having said that, thanks to the new (splstate) method, we no longer need to rely upon op101 (and its accompanying effects op267, op296, op169, etc...)
Real case study: CLERIC_FALSE_DAWN ("sppr609.spl") is supposed to confuse GENERAL=UNDEAD creatures (as per its description). Now, since most (all?) undead are immune to Confusion (plus icon, visual effects, etc...), this spell is not working as expected. But you see...? If undead were not immune to Confusion (plus its accompanying effects) and CLERIC_FALSE_DAWN did not check for CONFUSION_IMMUNITY, they would be immune to all Confusion sources but this one (as intended).
To sum up: if an undead is immune to Confusion, @DavidW's code should
- remove op101 along with all its associated effects (again, op169, op267, op296, etc...)
- flag it as CONFUSION_IMMUNITY (via op328)
- add the 324 check for CONFUSION_IMMUNITY to all spl/itm files that apply Confusion (op128), while skipping special cases like the aforementioned one
As already said, you might want to do that for all opcodes that are not in pairs... On reflection, you might want to do that for all opcodes indiscriminately... I mean, if I used op241 (Charm 2) to bypass immunity (op101) to op5 (Charm 1), the targeted creature would certainly be charmed (since nobody is supposed to be immune to the alternate opcode), but the Charm Icon / Visual effect / sound / etc... would still be blocked...
Thoughts...?
Addendum: getting rid of op101 would also allow Earthquake-like, knockdown (Dragon Wing Buffet – "spin695.spl") spells/attacks to bypass / ignore Chaotic Commands and the like (these spells are supposed to block only true Sleep / Unconsciousness effects, not also Earthquake and knockdown attacks...)
-
8 hours ago, polytope said:
Is it a better fix to change SPIN695 to have no projectile or direct effects, but cast via 146 a secondary shell spell with AoE projectile and a different prefix to apply the actual effects of damage + unconsciousness + repulsion
Why are you suggesting the main file should use `projectile=1|None` and the shell file should instead use the real projectile...? I mean, CLERIC_HOLD_PERSON ("sppr208.spl") also requires you to target a specific creature (`ability_target=1|Living actor`) while using an AoE projectile ("hold.pro")... On reflection, I don't think it really matters (since we're not using op146*p2=2, op326, op333 + the spl in question is supposed to bypass all MR checks)... just wanted to know whether I'm missing something or not...
Moreover, I think the AoE projectile should be coded as a cone projectile rather than a traditional AoE projectile (only targets facing the dragon should be affected, not also those staying behind...)
-
Well, guess we should also ask @argent77, @Wisp
Which of the following code snippets is supposed to take less time?
SpoilerACTION_TIME "clone_effect_ex" BEGIN WITH_SCOPE BEGIN COPY_EXISTING_REGEXP "^.+\.\(spl\|itm\)$" "override" PATCH_WITH_SCOPE BEGIN PATCH_MATCH "%DEST_EXT%" WITH "SPL" BEGIN GET_OFFSET_ARRAY "ab_array" SPL_V10_HEADERS SET "ab_size" = 0x28 END "ITM" BEGIN GET_OFFSET_ARRAY "ab_array" ITM_V10_HEADERS SET "ab_size" = 0x38 END DEFAULT PATCH_FAIL "Should not happen (~%DEST_FILE%~)" END PHP_EACH "ab_array" AS "ab_ind" => "ab_off" BEGIN PATCH_WITH_SCOPE BEGIN GET_OFFSET_ARRAY2 "fx_array" "%ab_off%" SPL_V10_HEAD_EFFECTS PHP_EACH "fx_array" AS "fx_ind" => "fx_off" BEGIN PATCH_IF (LONG_AT ("%fx_off%" + 0x24) BAND (BIT0 BOR BIT1 BOR BIT2 BOR BIT3 BOR BIT4)) BEGIN READ_ASCII ("%fx_off%" + 0x0) $"fx_data"("%fx_off%") (0x30) END END // Actual cloning PATCH_WITH_SCOPE BEGIN SET "fx_count" = 1 // this variable is needed to take into account the fact that data moves with each iteration – set it to 0 for "insert"="above" PHP_EACH "fx_data" AS "fx_off" => "fx_attributes" BEGIN INSERT_BYTES ("%fx_off%" + 0x30 * "%fx_count%") 0x30 WRITE_ASCIIE ("%fx_off%" + 0x30 * "%fx_count%") "%fx_attributes%" WRITE_LONG ("%fx_off%" + 0x30 * "%fx_count%" + 0x28) (STHIS - 2) // update the `savebonus` field SET "fx_count" += 1 WRITE_SHORT ("%ab_off%" + 0x1E) (THIS + 1) // update # effects // Update `1st_effect_idx` on all subsequent abilities PATCH_WITH_SCOPE BEGIN FOR ("i" = 1 ; "%i%" < SHORT_AT 0x68 - "%ab_ind%" ; "i" += 1) BEGIN WRITE_SHORT ("%ab_off%" + "%i%" * "%ab_size%" + 0x20) (THIS + 1) END END END END END END END BUT_ONLY_IF_IT_CHANGES END END // VERSUS ACTION_TIME "clone_effect_ex" BEGIN WITH_SCOPE BEGIN COPY_EXISTING_REGEXP "^.+\.\(spl\|itm\)$" "override" PATCH_MATCH "%DEST_EXT%" WITH "SPL" BEGIN GET_OFFSET_ARRAY "ab_array" SPL_V10_HEADERS SET "ab_size" = 0x28 END "ITM" BEGIN GET_OFFSET_ARRAY "ab_array" ITM_V10_HEADERS SET "ab_size" = 0x38 END DEFAULT PATCH_FAIL "Should not happen (~%DEST_FILE%~)" END PHP_EACH "ab_array" AS "ab_ind" => "ab_off" BEGIN PATCH_CLEAR_ARRAY "fx_data" GET_OFFSET_ARRAY2 "fx_array" "%ab_off%" SPL_V10_HEAD_EFFECTS PHP_EACH "fx_array" AS "fx_ind" => "fx_off" BEGIN PATCH_IF (LONG_AT ("%fx_off%" + 0x24) BAND (BIT0 BOR BIT1 BOR BIT2 BOR BIT3 BOR BIT4)) BEGIN READ_ASCII ("%fx_off%" + 0x0) $"fx_data"("%fx_off%") (0x30) END END // Actual cloning SET "fx_count" = 1 // this variable is needed to take into account the fact that data moves with each iteration – set it to 0 for "insert"="above" PHP_EACH "fx_data" AS "fx_off" => "fx_attributes" BEGIN INSERT_BYTES ("%fx_off%" + 0x30 * "%fx_count%") 0x30 WRITE_ASCIIE ("%fx_off%" + 0x30 * "%fx_count%") "%fx_attributes%" WRITE_LONG ("%fx_off%" + 0x30 * "%fx_count%" + 0x28) (STHIS - 2) // update the `savebonus` field SET "fx_count" += 1 WRITE_SHORT ("%ab_off%" + 0x1E) (THIS + 1) // update # effects // Update `1st_effect_idx` on all subsequent abilities FOR ("i" = 1 ; "%i%" < SHORT_AT 0x68 - "%ab_ind%" ; "i" += 1) BEGIN WRITE_SHORT ("%ab_off%" + "%i%" * "%ab_size%" + 0x20) (THIS + 1) END END END BUT_ONLY_IF_IT_CHANGES END END
In my case it is the first one, whereas on DavidW's computer is the second one... My computer runs macOS and is quite ancient (mid 2012, sigh), whereas DavidW's computer runs Windows (if I recall correctly) and it is probably not that ancient... I'm not sure if it can make a difference, but just so that you know it...
-
6 minutes ago, Graion Dilach said:
The WeiDU doc claims that during exiting from a WITH_SCOPE, even changes made to globals within the scope are reverted. So it basically duplicates the then-current state to a sandbox at start and drops that altogether during exit. I can imagine that being expensive.
OK, but then why does it (the no PATCH_WITH_SCOPE variant) take more time in my case...? I tried running it several times and it always took more time (~ 1 second) than the one with all those PATCH_WITH_SCOPEs...
-
1 minute ago, DavidW said:
A core requirement in writing any kind of introduction is discipline about keeping its length under control.
Fair enough...
1 minute ago, DavidW said:Your code takes about 3 seconds on my computer; that drops to a bit under 2 seconds if I take out the PATCH_WITH_SCOPEs. (WEIDU timings are not always consistent, especially for short timings; it's worth running it a few times and comparing.)
Just to clarify: is this the code without all those PATCH_WITH_SCOPEs...?
SpoilerACTION_TIME "clone_effect_ex" BEGIN WITH_SCOPE BEGIN COPY_EXISTING_REGEXP "^.+\.\(spl\|itm\)$" "override" PATCH_MATCH "%DEST_EXT%" WITH "SPL" BEGIN GET_OFFSET_ARRAY "ab_array" SPL_V10_HEADERS SET "ab_size" = 0x28 END "ITM" BEGIN GET_OFFSET_ARRAY "ab_array" ITM_V10_HEADERS SET "ab_size" = 0x38 END DEFAULT PATCH_FAIL "Should not happen (~%DEST_FILE%~)" END PHP_EACH "ab_array" AS "ab_ind" => "ab_off" BEGIN PATCH_CLEAR_ARRAY "fx_data" GET_OFFSET_ARRAY2 "fx_array" "%ab_off%" SPL_V10_HEAD_EFFECTS PHP_EACH "fx_array" AS "fx_ind" => "fx_off" BEGIN PATCH_IF (LONG_AT ("%fx_off%" + 0x24) BAND (BIT0 BOR BIT1 BOR BIT2 BOR BIT3 BOR BIT4)) BEGIN READ_ASCII ("%fx_off%" + 0x0) $"fx_data"("%fx_off%") (0x30) END END // Actual cloning SET "fx_count" = 1 // this variable is needed to take into account the fact that data moves with each iteration – set it to 0 for "insert"="above" PHP_EACH "fx_data" AS "fx_off" => "fx_attributes" BEGIN INSERT_BYTES ("%fx_off%" + 0x30 * "%fx_count%") 0x30 WRITE_ASCIIE ("%fx_off%" + 0x30 * "%fx_count%") "%fx_attributes%" WRITE_LONG ("%fx_off%" + 0x30 * "%fx_count%" + 0x28) (STHIS - 2) // update the `savebonus` field SET "fx_count" += 1 WRITE_SHORT ("%ab_off%" + 0x1E) (THIS + 1) // update # effects // Update `1st_effect_idx` on all subsequent abilities FOR ("i" = 1 ; "%i%" < SHORT_AT 0x68 - "%ab_ind%" ; "i" += 1) BEGIN WRITE_SHORT ("%ab_off%" + "%i%" * "%ab_size%" + 0x20) (THIS + 1) END END END BUT_ONLY_IF_IT_CHANGES END END
Also, why is PATCH_WITH_SCOPE supposed to be so relevant...? I mean, it's basically a function that takes no argument and returns nothing...
-
7 hours ago, DavidW said:
Are you suggesting I should put it in the document? I could do, but it's already 60 pages long and that's with a fairly active attempt to stay focused on the essentials.
Yes, it might be useful to know this trick...
7 hours ago, DavidW said:I don't think it's going to be *convenient* to do so. It might occasionally be *necessary*, either if there are no bespoke functions or (your case) if you need to patch thousands of files...
Yes, WeiDU’s low-level functionality should only be used when COPY_EXISTING_REGEXP a large amount of files... If you're dealing with just a bunch of files, then you'd be better off using WeiDU's built-in functions (which are certainly easier to read...)
7 hours ago, DavidW said:... and I can do it in 8 seconds using native WEIDU CLONE_EFFECT and under 4 using SCS's version of CLONE_EFFECT...
Guess you could also opt for WeiDU ADD_SPELL|ITEM_EFFECT (it takes roughly 9 seconds on my computer...)
SpoilerACTION_TIME "clone_effect_ex" BEGIN WITH_SCOPE BEGIN COPY_EXISTING_REGEXP "^.+\.\(spl\|itm\)$" "override" PATCH_WITH_SCOPE BEGIN PATCH_MATCH "%DEST_EXT%" WITH "SPL" BEGIN GET_OFFSET_ARRAY "ab_array" SPL_V10_HEADERS END "ITM" BEGIN GET_OFFSET_ARRAY "ab_array" ITM_V10_HEADERS END DEFAULT END PHP_EACH "ab_array" AS "ab_ind" => "ab_off" BEGIN PATCH_WITH_SCOPE BEGIN GET_OFFSET_ARRAY2 "fx_array" "%ab_off%" SPL_V10_HEAD_EFFECTS PHP_EACH "fx_array" AS "fx_ind" => "fx_off" BEGIN PATCH_IF (LONG_AT ("%fx_off%" + 0x24) BAND (BIT0 BOR BIT1 BOR BIT2 BOR BIT3 BOR BIT4)) BEGIN // Save current values READ_SHORT ("%fx_off%" + 0x0) "fx_opcode" READ_BYTE ("%fx_off%" + 0x2) "fx_target" READ_BYTE ("%fx_off%" + 0x3) "fx_power" READ_SLONG ("%fx_off%" + 0x4) "fx_parameter1" READ_SLONG ("%fx_off%" + 0x8) "fx_parameter2" READ_BYTE ("%fx_off%" + 0xC) "fx_timing" READ_BYTE ("%fx_off%" + 0xD) "fx_resist_dispel" READ_LONG ("%fx_off%" + 0xE) "fx_duration" READ_BYTE ("%fx_off%" + 0x12) "fx_probability1" READ_BYTE ("%fx_off%" + 0x13) "fx_probability2" READ_ASCII ("%fx_off%" + 0x14) "fx_resource" READ_LONG ("%fx_off%" + 0x1C) "fx_max_level" READ_LONG ("%fx_off%" + 0x20) "fx_min_level" READ_LONG ("%fx_off%" + 0x24) "fx_savetype" READ_SLONG ("%fx_off%" + 0x28) "fx_savebonus" READ_SLONG ("%fx_off%" + 0x2C) "fx_special" TEXT_SPRINT $"fx_data"("%fx_opcode%" "%fx_target%" "%fx_power%" "%fx_parameter1%" "%fx_parameter2%" "%fx_timing%" "%fx_resist_dispel%" "%fx_duration%" "%fx_probability1%" "%fx_probability2%" "%fx_resource%" "%fx_max_level%" "%fx_min_level%" "%fx_savetype%" "%fx_savebonus%" "%fx_special%" "%fx_ind%") "irrelevant" END END // Add it PATCH_WITH_SCOPE BEGIN SET "fx_count" = 0 PATCH_MATCH "%DEST_EXT%" WITH "itm" BEGIN PHP_EACH "fx_data" AS "fx_attributes" => "" BEGIN LPF "ADD_ITEM_EFFECT" INT_VAR "type" = 99 // all types "header" = "%ab_ind%" + 1 // count starts from 1 instead of 0!!! "opcode" = "%fx_attributes_0%" "target" = "%fx_attributes_1%" "power" = "%fx_attributes_2%" "parameter1" = "%fx_attributes_3%" "parameter2" = "%fx_attributes_4%" "timing" = "%fx_attributes_5%" "resist_dispel" = "%fx_attributes_6%" "duration" = "%fx_attributes_7%" "probability1" = "%fx_attributes_8%" "probability2" = "%fx_attributes_9%" "dicenumber" = "%fx_attributes_11%" "dicesize" = "%fx_attributes_12%" "savingthrow" = "%fx_attributes_13%" "savebonus" = "%fx_attributes_14%" - 2 "special" = "%fx_attributes_15%" "insert_point" = "%fx_attributes_16%" + 1 + "%fx_count%" // insert below STR_VAR "resource" = "%fx_attributes_10%" END SET "fx_count" += 1 END END "spl" BEGIN PHP_EACH "fx_data" AS "fx_attributes" => "" BEGIN LPF "ADD_SPELL_EFFECT" INT_VAR "header" = "%ab_ind%" + 1 // count starts from 1 instead of 0!!! "opcode" = "%fx_attributes_0%" "target" = "%fx_attributes_1%" "power" = "%fx_attributes_2%" "parameter1" = "%fx_attributes_3%" "parameter2" = "%fx_attributes_4%" "timing" = "%fx_attributes_5%" "resist_dispel" = "%fx_attributes_6%" "duration" = "%fx_attributes_7%" "probability1" = "%fx_attributes_8%" "probability2" = "%fx_attributes_9%" "dicenumber" = "%fx_attributes_11%" "dicesize" = "%fx_attributes_12%" "savingthrow" = "%fx_attributes_13%" "savebonus" = "%fx_attributes_14%" - 2 "special" = "%fx_attributes_15%" "insert_point" = "%fx_attributes_16%" + 1 + "%fx_count%" // insert below STR_VAR "resource" = "%fx_attributes_10%" END SET "fx_count" += 1 END END DEFAULT PATCH_FAIL "File not supported (~%DEST_FILE%~)" END END END END END BUT_ONLY_IF_IT_CHANGES END END
7 hours ago, DavidW said:If you really do want to optimize for speed, I would lose all those PATCH_WITH_SCOPEs - a quick test gets nearly a 50% speedup without them. You need to do PATCH_CLEAR_ARRAY fx_data each time you loop through a new ability...
Are you sure about this...?
Asking because it takes roughly 1 second more than before for me (as far WeiDU Timings are concerned, the most relevant difference between having all those PATCH_WITH_SCOPEs and not having them is `process_patch2` => 2.199 with PATCH_WITH_SCOPEs vs. 3.057 without PATCH_WITH_SCOPEs...)
-
As far as "Adding and subtracting headers" (page 59) is concerned:
-
as far as subtracting is concerned, you showed us a handy way to achieve it (the 999 trick):
Spoiler
/* Delete all SPL abilities whose `min_level` is strictly greater than 1 */ ACTION_TIME "delete_spl_ability_ex" BEGIN WITH_SCOPE BEGIN COPY_EXISTING_REGEXP "^.+\.spl$" "override" PATCH_WITH_SCOPE BEGIN GET_OFFSET_ARRAY "ab_array" SPL_V10_HEADERS PHP_EACH "ab_array" AS "ab_ind" => "ab_off" BEGIN PATCH_IF (SHORT_AT ("%ab_off%" + 0x10) > 1) BEGIN WRITE_BYTE ("%ab_off%" + 0x0) 0xFF // mark it for later deletion – we can't use 999 here because the `header_type` field is just 1-byte long... As a result, we'll use 0xFF (255, maximum unsigned byte) END END // Actual deletion LPF "DELETE_SPELL_HEADER" INT_VAR "header_type" = 0xFF END END BUT_ONLY_IF_IT_CHANGES END END /* Delete all global effects (except for op0, 144, 145) from armors */ ACTION_TIME "delete_itm_eqeffect_ex" BEGIN WITH_SCOPE BEGIN COPY_EXISTING_REGEXP "^.+\.itm$" "override" PATCH_IF (SHORT_AT 0x1C == IDS_OF_SYMBOL ("ITEMCAT" "ARMOR")) BEGIN PATCH_WITH_SCOPE BEGIN GET_OFFSET_ARRAY "fx_array" ITM_V10_GEN_EFFECTS PHP_EACH "fx_array" AS "fx_ind" => "fx_off" BEGIN READ_SHORT "%fx_off%" "current_effectID" PATCH_IF ("%current_effectID%" STRING_MATCHES_REGEXP "^\(0\|144\|145\)$") BEGIN WRITE_SHORT "%fx_off%" 999 // mark it for later deletion END END // Actual deletion LPF "DELETE_ITEM_EQEFFECT" INT_VAR "opcode_to_delete" = 999 END END END BUT_ONLY_IF_IT_CHANGES END END
-
As far as adding is concerned, it might be convenient to use WeiDU’s low-level functionality instead of WeiDU's built-in functions (mainly for efficiency – the following piece of code should take less than 5 seconds to patch 3083 files (unmodded IWD:EE)...)
Spoiler
/* Clone all SPL/ITM V10_HEAD_EFFECTS that offers at least one saving throw. In particular: - Decrease the clone's `savebonus` field by 2 - Insert the cloned effect immediately below the matched effect */ ACTION_TIME "clone_effect_ex" BEGIN WITH_SCOPE BEGIN COPY_EXISTING_REGEXP "^.+\.\(spl\|itm\)$" "override" PATCH_WITH_SCOPE BEGIN PATCH_MATCH "%DEST_EXT%" WITH "SPL" BEGIN GET_OFFSET_ARRAY "ab_array" SPL_V10_HEADERS SET "ab_size" = 0x28 END "ITM" BEGIN GET_OFFSET_ARRAY "ab_array" ITM_V10_HEADERS SET "ab_size" = 0x38 END DEFAULT PATCH_FAIL "Should not happen (~%DEST_FILE%~)" END PHP_EACH "ab_array" AS "ab_ind" => "ab_off" BEGIN PATCH_WITH_SCOPE BEGIN GET_OFFSET_ARRAY2 "fx_array" "%ab_off%" SPL_V10_HEAD_EFFECTS // NB, same for spl and itm PHP_EACH "fx_array" AS "fx_ind" => "fx_off" BEGIN PATCH_IF (LONG_AT ("%fx_off%" + 0x24) BAND (BIT0 BOR BIT1 BOR BIT2 BOR BIT3 BOR BIT4)) BEGIN READ_ASCII ("%fx_off%" + 0x0) $"fx_data"("%fx_off%") (0x30) END END // Actual cloning PATCH_WITH_SCOPE BEGIN SET "fx_count" = 1 // this variable is needed to take into account the fact that data moves with each iteration – set it to 0 for "insert"="above" PHP_EACH "fx_data" AS "fx_off" => "fx_attributes" BEGIN INSERT_BYTES ("%fx_off%" + 0x30 * "%fx_count%") 0x30 WRITE_ASCIIE ("%fx_off%" + 0x30 * "%fx_count%") "%fx_attributes%" WRITE_LONG ("%fx_off%" + 0x30 * "%fx_count%" + 0x28) (STHIS - 2) // update the `savebonus` field SET "fx_count" += 1 WRITE_SHORT ("%ab_off%" + 0x1E) (THIS + 1) // update # effects // Update `1st_effect_idx` on all subsequent abilities PATCH_WITH_SCOPE BEGIN FOR ("i" = 1 ; "%i%" < SHORT_AT 0x68 - "%ab_ind%" ; "i" += 1) BEGIN WRITE_SHORT ("%ab_off%" + "%i%" * "%ab_size%" + 0x20) (THIS + 1) END END END END END END END BUT_ONLY_IF_IT_CHANGES END END
-
as far as subtracting is concerned, you showed us a handy way to achieve it (the 999 trick):
-
Sorry for the necro, but
On 12/7/2021 at 6:56 PM, Ardanis said:In that case, the next best bet is to try including separator string into expression outside of the match variable:
This mostly works, the only issue is that it does not take into account the order of characters. That is to say, if my separator is "ab", then also "ba" is valid...
On 12/8/2021 at 10:46 AM, Mike1072 said:The cases you mention might be caught by the first capture group ([^,]+) which would absorb everything up to the embedded comma, and then ruin the future matches. It might be possible to resolve that just by reordering the capture groups in the alternation and placing it after the other two.
Unless I'm missing something, nothing changes when reordering the capture groups... Do you have any other idea...?
I mean, it is certainly possible to build a parser (function) that scans the input string character-by-character (byte-by-byte) and remembers when quotation is open...
SpoilerDEFINE_DIMORPHIC_FUNCTION "SPLIT_EXPR" STR_VAR "expr" = "" "pattern" = "" RET_ARRAY "array" BEGIN // Initialize ACTION_CLEAR_ARRAY "array" OUTER_SET "count" = 0 OUTER_SET "expr_length" = STRING_LENGTH "%expr%" OUTER_TEXT_SPRINT "temp" "" OUTER_SET "tilda_found" = 0 OUTER_SET "quote_found" = 0 OUTER_PATCH "%pattern%" BEGIN READ_ASCII 0x0 "1st_char" ELSE "" (1) READ_ASCII 0x1 "remaining_chars" ELSE "" (BUFFER_LENGTH - 1) END // Main OUTER_PATCH "%expr%" BEGIN WHILE ("%expr_length%") BEGIN READ_ASCII 0x0 "current_char" (1) PATCH_MATCH "%current_char%" WITH "~" WHEN !("%quote_found%") BEGIN SET "tilda_found" += 1 PATCH_IF ("%temp%" STRING_COMPARE_CASE "") BEGIN TEXT_SPRINT "temp" "%temp%%current_char%" END ELSE BEGIN TEXT_SPRINT "temp" "%current_char%" END DELETE_BYTES 0x0 0x1 SET "expr_length" -= 1 END ~"~ WHEN !("%tilda_found%") BEGIN SET "quote_found" += 1 PATCH_IF ("%temp%" STRING_COMPARE_CASE "") BEGIN TEXT_SPRINT "temp" "%temp%%current_char%" END ELSE BEGIN TEXT_SPRINT "temp" "%current_char%" END DELETE_BYTES 0x0 0x1 SET "expr_length" -= 1 END "%1st_char%" BEGIN READ_ASCII 0x1 "following_chars" ELSE "" (STRING_LENGTH "%pattern%" - 1) PATCH_IF ("%remaining_chars%" STRING_EQUAL "%following_chars%") BEGIN PATCH_IF ("%quote_found%" == 0 OR "%quote_found%" == 2) AND ("%tilda_found%" == 0 OR "%tilda_found%" == 2 OR "%tilda_found%" == 10) BEGIN DEFINE_ASSOCIATIVE_ARRAY "array" BEGIN "%count%" => "%temp%" END SET "count" += 1 DELETE_BYTES 0x0 STRING_LENGTH "%pattern%" SET "expr_length" -= STRING_LENGTH "%pattern%" // Reset vars SET "tilda_found" = 0 SET "quote_found" = 0 TEXT_SPRINT "temp" "" END ELSE BEGIN PATCH_IF ("%temp%" STRING_COMPARE_CASE "") BEGIN TEXT_SPRINT "temp" "%temp%%current_char%" END ELSE BEGIN TEXT_SPRINT "temp" "%current_char%" END DELETE_BYTES 0x0 0x1 SET "expr_length" -= 1 END END ELSE BEGIN PATCH_IF ("%temp%" STRING_COMPARE_CASE "") BEGIN TEXT_SPRINT "temp" "%temp%%current_char%" END ELSE BEGIN TEXT_SPRINT "temp" "%current_char%" END DELETE_BYTES 0x0 0x1 SET "expr_length" -= 1 END END DEFAULT PATCH_IF ("%temp%" STRING_COMPARE_CASE "") BEGIN TEXT_SPRINT "temp" "%temp%%current_char%" END ELSE BEGIN TEXT_SPRINT "temp" "%current_char%" END DELETE_BYTES 0x0 0x1 SET "expr_length" -= 1 END END END // If ~%pattern%~ is not found... ACTION_IF ("%temp%" STRING_COMPARE_CASE "") BEGIN OUTER_SET "count" = "%count%" ? "%count%" + 1 : "%count%" ACTION_DEFINE_ASSOCIATIVE_ARRAY "array" BEGIN "%count%" => "%temp%" END END ELSE BEGIN FAIL "SPLIT_EXPR: ~temp~ is empty (~expr~=~%expr%~, ~pattern~=~%pattern%~). Wut???" END END
However, in case of multiple separators (i.e., if multiple separators are valid), how should I use it?
Guess I should check them one by one, i.e.:
Spoiler// Suppose separators "ab", "cfb89" and ">><<8677vdf2" are all valid OUTER_TEXT_SPRINT "mystring" "" // your test string OUTER_SET "found" = 0 // boolean ACTION_FOR_EACH "separator" IN "ab" "cfb89" ">><<8677vdf2" BEGIN ACTION_IF !("%found%") BEGIN LAF "SPLIT_EXPR" STR_VAR "expr" = "%mystring%" "pattern" = "%separator%" RET_ARRAY "array" END LAF ~ARRAY_LENGTH~ STR_VAR "array" RET "length" END ACTION_IF ("%length%" >= 2) BEGIN OUTER_SET "found" = 1 END END END // where function ~ARRAY_LENGTH~ is DEFINE_DIMORPHIC_FUNCTION ~ARRAY_LENGTH~ STR_VAR "array" = "" // array name RET "length" BEGIN // Initialize OUTER_SET "length" = 0 // Main ACTION_PHP_EACH "%array%" AS "key" => "value" BEGIN OUTER_SET "length" += 1 END END
You see, it is a bit inelegant, but it should work... Having said that, I still did not understand whether it is possible to use a regexp or not ...
-
23 hours ago, Guest Morgoth said:
Guess you meant
SpoilerAbazigal and Draconis
...?
The former is, whereas the latter is not (since it's not coded as a dragon...)
Now that I think of it, the former should not be coded as a dragon while in human form...
-
On 7/23/2017 at 7:22 PM, CamDawg said:
UNDEAD, GOLEM (Clay/Iron/Stone, not Flesh), SLIME, DRAGON, DEMONIC and so forth should be immune to backstab/sneak attack (opcode #292).....
Done. Thoughts...?
I think the tweak can be safely extended to classic bg2 / bgt... However, since I can only test EE games, I'll leave that part to you...
-
On 7/23/2017 at 7:22 PM, CamDawg said:
At least on EE games, consider the idea of penalizing drow when they're outside during the day.
This is interesting, I can work on it...
Also, what about expanding it so as to include other relevant creatures...? For instance:
-
Drows, Duergars
- -2 Dexterity, -2 saves, -2 THAC0
-
Kobolds, Tasloi, Goblins, Kuo-toa
- -1 THAC0
-
Orcs, Orogs
- -1 THAC0, -1 morale
-
Sahuagin
- not sure here... 3E speaks of blindness, 2E is a bit vague ("They dislike light, and bright light [...] is harmful to their eyes.")
-
Spectral Trolls
-
insta-death, though it's not actually insta-death ("Spectral trolls vanish in direct sunlight. They do not take damage from sunlight, they merely fade from view and reappear at the same spot at nightfall.")
- this does sound too complicated or even impossible to implement...
-
insta-death, though it's not actually insta-death ("Spectral trolls vanish in direct sunlight. They do not take damage from sunlight, they merely fade from view and reappear at the same spot at nightfall.")
-
Feyrs
- insta-death ("Common feyrs are slain by the morning light...")
-
Wraiths
- cannot attack, though it's not that simple ("Sunlight cannot destroy the wraith, but the undead creature cannot attack in sunlight. It shuns bright (e.g., continual) light sources in general, but will occasionally attack if the compulsion to do so is strong.")
Thoughts...?
-
Drows, Duergars
-
Interesting, that would certainly explain my in-game tests...
Did you perhaps round up .75 to 1...? I mean, technically speaking (and unless I'm missing something), 8 should be 7.75 (7.5/2 + 4 = 7.75)...
Having said that, if we consider again the BG(2) version of Summon Insects ("sppr319.spl"), we have (in case the targeted creature is hasted):
SpoilerDefault => once per 2 seconds
Hasted => once per 1 second (multiplier = 2 + floor(log2(2)) = 3)
- 1, 1.5, 2
- 3, 3.5, 4
- 5, 5.5, 6
- 7, 7.5, 8
- 9, 9.5, 10
- 11, 11.5, 12
- 13, 13.5, 14
- 15, 15.5, 16
- 17, 17.5, 18
- 19, 19.5, 20
- 21, 21.5, 22
- 23, 23.5, 24
- 25, 25.5, 26
- 27, 27.5, 28
- 29, 29.5, 30
- 31, 31.5, 32
- 33, 33.5, 34
- 35, 35.5, 36
- 37, 37.5, 38
- 39, 39.5, 40
- 41, 41.5, 42
As you can see, the total number of triggers is 63. To be precise, I got 62... But I guess that's due to some rounding issues, right...?
Now, in order to get 42 damage, we would need to block one effect at each interval... So for instance, when the one at 1 triggers, the next one at 1.5 should not fire (i.e., we would need a 0.5 second immunity...)
I tried the following setup and everything seems to work fine
Spoiler- op272, p1=2, p2=3, res="EFF" // once per 2 seconds
-
"EFF"
- op146, p2=1, timing=1, prob1=100, res="SPL"
-
"SPL"
- op12, p1=1, p2a=0, p2b=16, timing=1 // 1 piercing damage -- this will most likely be moved into another subspell so as to support "7eyes.2da"...
- op206, timing=10, duration=1, res="SPL" (1 tick duration)
Moreover, sometimes everything seems to be fine even in case of Slow (i.e., a slowed creature takes 11 piercing damage... Though I'm not sure how to consistently replicate it...) But since we do not know how the multiplier scales with Slow, I'm not sure why it is so...
As far as Creeping Doom is concerned, it should be fine as is since its default frequency is already once per second (i.e., the multiplier is a direct 2x in this case)... To be precise, the subspell is still needed to support "7eyes.2da" (which cannot block EFF files)... However, the trailing op206 effect can hopefully be omitted...
Anyway, just out of curiosity: would you or @Bubb be able to determine the multiplier in case of Slow...? How would you proceed...?
-
Yeah, it's a real mess...
16 hours ago, Bubb said:As for op272 happening multiple times in a single tick, I don't know. It's theoretically possible if the effects list is ticked 15+ times in a single update while the game is unpaused, but I have no idea what would cause that. Do you have an example?
Ideally (and unless I'm missing something), since Summon Insects triggers once per 2 seconds for 42 seconds, it should deal:
- 21 piercing damage if the targeted creature is not Hasted/Slowed
- 42 piercing damage if the targeted creature is Hasted
- 10 piercing damage if the targeted creature is Slowed
However, I got the following (I never manually paused the game and disabled/turned off all auto-pause game options):
- 21 piercing damage if the targeted creature is not Hasted/Slowed
-
62 piercing damage if the targeted creature is Hasted
- Wut???
-
21 piercing damage if the targeted creature is Slowed
- Wut???
As you can see, there is something wrong with Hasted/Slowed targets ... Do you have an explanation for that...?
-
So, here is another possible engine bug...
Could you please look into op272 mechanics and report here how it really works?
As you surely know, this opcode might trigger under intended circumstances, and that's certainly bad...
This odd behavior is easily reproducible with spells such as Summon Insects ("sppr319.spl"): this spell (op272 - once per 2 seconds for 42 seconds => "ipdam1.eff") might deal more damage than intended, especially if the targeted creature is hasted (since the opcode is subjected to Haste/Slow)...
Is there a way to prevent it from triggering under unintended circumstances...? I mean, if we're dealing with a removable (limited) effect, then the following setup should be enough to bypass this issue
Spoiler- op272, <frequency>, res="EFF"
-
"EFF"
- op146, p2=1, timing=1, prob1=100, res="SPL"
-
"SPL"
- op321, timing=1, res="SPL" // prevent self stacking
- <opcode>, timing=0, duration=X (where X should be the lowest duration value compatible with Haste/Slow, which halves/doubles the timing rate of op272)
But what about this particular case (Summon Insects)...?
I mean, op12 (Damage) does not leave behind a removable effect, so op321 would not work in this case... Is there a workaround...?
-
On 6/22/2022 at 4:29 PM, CamDawg said:
Cheers, Luke, IWD is next.
Another FYI:
as you can see, my work is largely based on two functions (which in turn are based on DavidW's code) that automatically make subspells (including dealing with `power`, `primary_type`, `secondary_type`, dice values, etc...) / reorder effects... They are launched relatively early so that I can just open NearInfinity and look at unmodded effect indices to determine the files that need to be patched... Any further patch (if needed) is done later on in the main file (i.e., "iwdee.tph"...)
In so doing, it should be easier to patch the relevant files...
-
21 minutes ago, DavidW said:
It's not as if any modder who wants to free up space for their vast array of class.ids entries can't do it themselves with one line of code.
Worth noting: just because something isn't used in the unmodded game doesn't mean it doesn't get used by some mod. (And even if someone isn't using it actively, many mods probably copy over .cre files that have NO_CLASS/NO_RACE set.) One reason not to change things that don't actually need to change is that it reduces the chance of breaking someone's mod. (Another is just general safety-first coding. Another still is that it would create unnecessary QA work for BD if they incorporate FP into 2.7.)
Fair enough.
In any event, I would use 0 (instead of 255) to blank the CLASS value of Shambling Mounds...
-
On 6/22/2022 at 1:24 PM, DavidW said:
Freeing up space in class.ids might be helpful for some megamod install, but that doesn't make it a bug.
Yes, this would be the main reason...
I mean, it's not really a bug, but they're certainly kinda misleading, aren't they...?
As I said (and unless I'm missing something), they are not referenced in any scripts/dialogues/op318/324/326, so why are there in the first place?
According to the IESDP, the old bg1/bg2/iwd list them as well... Though I doubt they are actually used in scripts/dialogs (cannot check...)
So my question is: is it possible that whoever added them did not know that 0 already stands for 'any RACE/CLASS'...? Otherwise why calling them NO_RACE / NO_CLASS...?
-
2 hours ago, lynx said:
Surely this would break IDS targetting ...
The key point is that it should be the other way round, i.e.: 255 => 0 (I fixed my previous post based on kjeron's comment...)
-
12 hours ago, kjeron said:
You cannot use value "0" for custom EA/GENERAL/CLASS/RACE/ALIGNMENT/GENDER values.
"0" is hardcoded as "Any", as in:
See([0.0.0.0.0.0])
Will return true for any visible creature.
Yeah, you're definitely right, me dumb... I had completely forgotten about that...
And for that specific reason, unlike "splstate.ids", valid values range from 1 to 255 (instead of 0 to 255...)
12 hours ago, kjeron said:AFAIK they are not referenced in any scripts/dialogues, so if anything the "NO_CLASS" and "NO_RACE" entries should be removed to free up space, and their creatures remapped to "0".
Yeah, makes sense, I'll "fix" my other thread...
-
On 6/20/2022 at 5:49 PM, CamDawg said:
OK, so I've got this done for BG2EE and will be moving on to BGEE and IWDEE next.
Just a FYI: I have just committed something similar for IWDEE to make sure "7eyes.2da" works properly.
I also started to include some BG(2)EE resources (for later compatibility with IWDification, which is supposed to ship the Seven Eyes spell), but then just stopped because I think we are doing the exact same thing...
Let me know if you're fine with my work on IWDEE...
-
2 hours ago, DavidW said:
It's slightly odd that they have class=None rather than class=NO_CLASS, but that's either (a) harmless, or (b) actually targeted by some bit of scripting in SoD, in which case changing it would be disastrous. In any case, there's no need to change it, so there is a need not to change it.
As I said here, all 0 should be 255.
As you can see by looking at "class.ids", unlike class #255, class #0 does not exist (what NearInfinity labels as `0|None` is just the default value, not an actual/existing "class.ids" identifier... Even the default value for dialogue at CRE offset 0x2CC is labeled as `None`, but that does not imply that a "None.DLG" file does exist as a game resource...)
If a modder wanted to add a brand new class value, he should be able to use index #0 (i.e., the first available unused index)... And that's why all existing 0s should be changed to 255...
Effect Immunities on the EE Engine (aka Taking Full Advantage of the EE Fixpack)
in Modding How-Tos and Tutorials
Posted · Edited by Luke
As I've already said, it might be problematic to tell if an op39 effect is, say, Unconsciousness or Knockdown, especially considering mod-added assets... Anyway, we now have an alternative method which is mods friendly...
Yes, I think that for the time being we should rely on this bug... Just note that the various "Cure" opcodes only consider EFF files applied by op177, so in case the effect needs to be cured (CLERIC_FALSE_DAWN...?), you need to use op321 targeting the resref applying the op182 effect...
Something like a 3-second unconsciousness each time the Damage opcode triggers...? That is:
Also, I would do the same with Shake Screen (it's currently applied via op272, and that means it's subjected to Haste / Slow... It does not make any sense to me... I'd apply 3 separate op269 effects...) and Play Sound (for some reason they are applied one second before the Damage opcodes...)
Finally, if it's gonna work on sleep immune creatures, then flying creatures and magically levitating creatures (Demilich, Mordenkainen's Sword, etc...) should definitely be immune to it (just add a 324 RACE check to all relevant SPL/ITM files...)