Jump to content

Luke

Modders
  • Posts

    879
  • Joined

Posts posted by Luke

  1. 16 hours ago, subtledoctor said:

    Why not make a separate mod that is meant to be installed last, which converts everything to use spellstates? Kind of the way Klatu's "Standardize ______" or the old lolfixer stuff is designed.

    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...

    15 hours ago, Bubb said:

    You might be able to abuse the bugged op182; if you supply an empty ITM resref it should apply 100% of the time.

    2 hours ago, polytope said:

    Quite possibly an oversight, but it's a good bug if so as it lets us use opcodes on creatures who'd normally be unaffected in specific cases where they should be.

    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...

    2 hours ago, polytope said:

    That said, for balance reasons, Earthquake's unconsciousness duration should probably be nerfed if we decide it's a physical knockdown that works on sleep immune creatures; because it otherwise effectively works as a mass save-or-die with a very harsh penalty of -6.

    Something like a 3-second unconsciousness each time the Damage opcode triggers...? That is:

    • immediately after the spell is cast
    • after 12 seconds
    • after 24 seconds

    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...)

  2. 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...

  3. 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...?

  4. 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

    1. remove op101 along with all its associated effects (again, op169, op267, op296, etc...)
    2. flag it as CONFUSION_IMMUNITY (via op328)
    3. 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...)

  5. 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...)

  6. Well, guess we should also ask @argent77, @Wisp 

    Which of the following code snippets is supposed to take less time?

    Spoiler
    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
    						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...

  7. 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...

  8. 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...?

    Spoiler
    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

     

    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...

  9. 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...)

    Spoiler
    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
    					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...)

  10. 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

       

  11. @Ardanis, @Mike1072

    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...

    Spoiler
    DEFINE_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 😕...

  12. 23 hours ago, Guest Morgoth said:

    Is

      Reveal hidden contents

    Adalon [

    in human form immune to backstab? 

    Guess you meant

    Spoiler

    Abazigal 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...

  13. 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...

  14. 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...
    • 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...?

  15. @kjeron

    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):

    Spoiler

    Default => once per 2 seconds

    Hasted => once per 1 second (multiplier = 2 + floor(log2(2)) = 3)

    1. 1, 1.5, 2
    2. 3, 3.5, 4
    3. 5, 5.5, 6
    4. 7, 7.5, 8
    5. 9, 9.5, 10
    6. 11, 11.5, 12
    7. 13, 13.5, 14
    8. 15, 15.5, 16
    9. 17, 17.5, 18
    10. 19, 19.5, 20
    11. 21, 21.5, 22
    12. 23, 23.5, 24
    13. 25, 25.5, 26
    14. 27, 27.5, 28
    15. 29, 29.5, 30
    16. 31, 31.5, 32
    17. 33, 33.5, 34
    18. 35, 35.5, 36
    19. 37, 37.5, 38
    20. 39, 39.5, 40
    21. 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...?

  16. @Bubb

    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...?

  17. @Bubb

    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...?

  18. 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...

  19. 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...

  20. 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...?

  21. 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...)

  22. 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...

  23. 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...

  24. 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...

×
×
  • Create New...