Jump to content

[code] General framework for rearranging effects and extracting effects to subspells


DavidW

Recommended Posts

Doing spell/item immunities properly requires some rearranging of the order of effects in spell/item headers, and some extraction of effects into subspells. I wrote a generalized framework to do this, which might be useful for some other bugs.

It's controlled by the rearrange_itm_spl function, in %MOD_FOLDER%/files/tph/dw/rearrange_itm_spl.tph. That function runs at the beginning of the dw_fixes section of the FP (though it could easily be moved elsewhere). It looks at the (hand-generated data) in this folder: %MOD_FOLDER%/data/rearrange/[game]. ([game] is bg2, bg1, iwd, sod - as appropriate).

That folder contains 3 files.

reorderings.txt defines permutations of effects, and looks like this (all examples are from bg2):

object			ability			permutation
ax1h10.itm		-1			1,0,2
blun12.itm		-1			1,2,0
blun25.itm		-1			1,2,0
hamm10.itm		-1			1,2,0
hamm11.itm		0			1,2,0
dwfpdeva.spl		-1			2,1,0
dwfpohdv.spl		-1			2,1,0

In each row, the first entry is the object (an itm or spl file) that needs its blocks rearranging. The second is the header that gets rearranged (-1 if all should be). The third entry is a permutation: a comma-separated list of the first N nonnegative integers. So the permutation '1,0,2' says: put block 0 in location 1; put block 1 in location 0; put block 2 in location 2.

(In all cases, these entries are to organize an undead-disrupting weapon's effect stack so that damage comes first, then the death effect, then the 'you die' message.)

item_subspells.txt looks like this:

itm		ability		subspell	extract
deva		0		dwfpdeva	4,5,6
ohbdeva		0		dwfpohdv	0,1,2
spermel		0		dwfpsmst	11,12,13
spermel		0		dwfpsmsl	1,10
wand12		0		dwfpwwfs	5,15
wand12		0		dwfpwwhs	3,4,14  	note the interweaving of the last two

In each row, the first entry is the resref of the item to be patched. The second says which ability block is to be patched. The third is a new spell (and so needs to be unique, with a modder prefix - and in my case a modder subprefix so I don't worry about overwriting SCS!). The fourth is a comma-separated list of effects. All those effects (counting from 0) are extracted from the item and placed in a new subspell that the item casts. Probabilities and saving throws remain on the parent item; everything else gets moved to the subspell. (So all the extracted blocks need the same probabilities and saving throw data, and it doesn't make sense to extract a damage-doing effect that uses save-for-half.)

(Incidentally, you can reuse a subspell if you're 100% confident that the contents will be exactly the same.)

spell_subspells.txt looks like this:

spl		track_levels		subspell	extract
insanity	0			dwfpdgfb	6,7
insanity	0			dwfpdgcn	0,1,2,3,4,5

It works basically the same way, for spells rather than abilities. The main difference is that all headers get the rows extracted (so this shouldn't be used with spells that have different effect structures at different levels). By default the code assumes that the effects are exactly the same at each level; if not (e.g. a varying duration) set the second column to 1.

(In the example above, note the order - we need to extract the later effects first, because extracting the earlier ones will renumber the later ones.)

rearrange_itm_spl first does the subspell extractions, then the permutations (so you can permute a subspell). It then does a small number of rearrangements that are too complicated for this automated structure (currently, only 3 White Doves).

You can also do permutations and extractions on a per-item basis, using the underlying functions that these tables rely on (located in dw_functions.tph, currently loaded in the ALWAYS block). I'll put them in a separate post.

Link to comment

Thanks for sharing.

Besides immunities, this is also useful for dealing with the "7eyes.2da" issue I'm working on. I was trying to make the extraction/permutation universal (that is, without manually listing the effects to extract / permute), but that's not possible. I mean, if a SPL/ITM applies both, say, Stun and Slow (with the same probabilities, saves, power, etc...), there's no way to know (a priori!) if a given op215 effect (play visual effect) is associated with Stun or Slow (undecidable problem...?)
Anyway, since the mod is supposed to be installed first, then I think it's fine to have a hardcoded list of effects...

4 hours ago, DavidW said:

The third is a new spell (and so needs to be unique, with a modder prefix - and in my case a modder subprefix so I don't worry about overwriting SCS!).

Is this really necessary...? What would be the problem with the following naming scheme

itm		ability		subspell	extract
deva		0		deva		4,5,6
ohbdeva		0		ohbdeva		0,1,2
spermel		0		spermel1	11,12,13
spermel		0		spermel2	1,10
wand12		0		wand12a		5,15
wand12		0		wand12b		3,4,14  	note the interweaving of the last two

?

4 hours ago, DavidW said:

Probabilities and saving throws remain on the parent item; everything else gets moved to the subspell.

I would also leave power, dicenumber (Maximum Level) and dicesize (Minimum Level) on the parent file... And consequently, the extracted blocks should need the same power, dicenumber (Maximum Level) and dicesize (Minimum Level) data (along with probabilities and saving throw...)

Edited by Luke
Link to comment
7 minutes ago, Luke said:

What would be the problem with the following naming scheme

The same problem with any naming scheme that doesn't use a modder prefix: you can't be confident that someone else isn't occupying the same region of namespace. In practice it's probably safe in the context of a FP, but I think there is a good general principle that says it's better to always use modder prefixes rather than deciding on a case-by-case basis whether it's safe enough.

 

8 minutes ago, Luke said:

would also leave power, dicenumber (Maximum Level) and dicesize (Minimum Level) on the parent file... And consequently, the extracted blocks should need the same power, dicenumber (Maximum Level) and dicesize (Minimum Level) data (along with probabilities and saving throw...)

I don't think there's any particular advantage in doing so, not least because if you did, the extracted blocks indeed need to conform to a constraint they wouldn't otherwise have to conform to. (But persuade me otherwise, I could be missing something.)

Link to comment
8 hours ago, DavidW said:

I don't think there's any particular advantage in doing so, not least because if you did, the extracted blocks indeed need to conform to a constraint they wouldn't otherwise have to conform to. (But persuade me otherwise, I could be missing something.)

Power on the mainspell for reflection.

Power on the subspell for opcodes 220/221.  I would also include opcodes 229/230, except using separate subspells already breaks them.

Link to comment
18 hours ago, DavidW said:

In practice it's probably safe in the context of a FP...

Yes, this is exactly what I intended to say. Also, since all of this is going to be included in v2.7 (hopefully), then a non-prefixed naming scheme should be safe enough... Having said that, if a prefix is really needed, then yes, I too would suggest something like <modder_prefix>fp...

18 hours ago, DavidW said:

(But persuade me otherwise, I could be missing something.)

Pretty much what @kjeron said. On top of it, leave also dicenumber (Maximum Level) and dicesize (Minimum Level) on the parent file, so that all checks are on the parent file (only `power` should be on both the parent and the child file).

Similarly, `resist_dispel` should only be on the parent file. In particular, if the extracted effects are coded as `resist_dispel=1` (Dispellable / Not bypass MR), then make sure the effects on the subspell are coded as `resist_dispel=3` (Dispellable / Bypass MR).
To sum up

Spoiler
// make the edits (keep power, probabilities, saving throws, dicenumber, dicesize)
LPF ALTER_EFFECT INT_VAR match_opcode=998 opcode=146 timing=1 duration=0 parameter1=0 parameter2=1 special=0 STR_VAR resource="%subspell%" END

// overwrite probabilities, saving throws, dicenumber, dicesize, resist_dispel
WRITE_LONG 0x24+fx_off 0
WRITE_LONG 0x28+fx_off 0
WRITE_BYTE 0x12+fx_off 100
WRITE_BYTE 0x13+fx_off 0
WRITE_LONG 0x1c+fx_off 0 // dicenumber
WRITE_LONG 0x20+fx_off 0 // dicesize
WRITE_BYTE 0xd+fx_off (THIS == BIT0 ? BIT0 | BIT1 : THIS) // resist_dispel

 

Finally:

  • if the parent file is a SPL file, then make sure the subspell inherits its type (Header @ 0x1C)
  • if the parent file is a SPL file, then make sure the subspell inherits both its primary type (Header @ 0x25) and secondary type (Header @ 0x27)
  • if the parent file is an ITM file, then make sure the subspell inherits both its primary type (Extended Header @ 0x17) and secondary type (Extended Header @ 0x19)
  • (bonus, optional) make sure the subspell inherits the parent's icon (Extended Header @ 0x4)
9 hours ago, kjeron said:

I would also include opcodes 229/230, except using separate subspells already breaks them.

This is because those opcodes would only consider the resource the op146 is on, right...? But that would be a problem because the real effects are on the SPL cast by that op146 effect...

How bad is this in practice...? I mean, as far as the base game is concerned, we only have op230 effects targeting SPELLPROTECTIONS... And SPELLPROTECTIONS are not supposed to cast subspells, so everything should be fine... The problem is when some other mod uses a different value... (this is something that should be noted in the final docs...)

Edited by Luke
Link to comment
1 hour ago, Luke said:

This is because those opcodes would only consider the resource the op146 is on, right...? But that would be a problem because the real effects are on the SPL cast by that op146 effect...

How bad is this in practice...? I mean, as far as the base game is concerned, we only have op230 effects targeting SPELLPROTECTIONS... And SPELLPROTECTIONS are not supposed to cast subspells, so everything should be fine... The problem is when some other mod uses a different value... (this is something that should be noted in the final docs...)

It's because those two opcodes only remove a single spell resource per use, and the main/sub spells will inevitably have different filenames.  AFAIK not a base game issue.

Link to comment
12 hours ago, kjeron said:

Power on the mainspell for reflection.

Makes sense.

 

12 hours ago, kjeron said:

Power on the subspell for opcodes 220/221.

I suspect something as subtle as those opcodes probably should be treated manually anyway, but noted.

 

4 hours ago, Luke said:

On top of it, leave also dicenumber (Maximum Level) and dicesize (Minimum Level) on the parent file, so that all checks are on the parent file (only `power` should be on both the parent and the child file).

That I'm not seeing. What's the advantage? (The obvious disadvantage is that it means all effects need to have the same min/max level

 

4 hours ago, Luke said:

if the parent file is a SPL file, then make sure the subspell inherits its type (Header @ 0x1C)

Does type do anything relevant here?

 

4 hours ago, Luke said:
  • if the parent file is a SPL file, then make sure the subspell inherits both its primary type (Header @ 0x25) and secondary type (Header @ 0x27)
  • if the parent file is an ITM file, then make sure the subspell inherits both its primary type (Extended Header @ 0x17) and secondary type (Extended Header @ 0x19)

This is sensible.

 

4 hours ago, Luke said:

(bonus, optional) make sure the subspell inherits the parent's icon (Extended Header @ 0x4)

This I don't understand. It's a subspell - when can its icon be seen?

Link to comment
24 minutes ago, DavidW said:

I suspect something as subtle as those opcodes probably should be treated manually anyway, but noted.

To be precise, the problem is only relevant for op229/230...

As @kjeron stated, those opcodes would only remove effects on the main / parent file (the one containing the op146 effect...) As a result, SPL/ITM files that apply op229/230 should also apply op321 effects targeting these subspells if necessary...

Anyway, not a base game issue...

24 minutes ago, DavidW said:

The obvious disadvantage is that it means all effects need to have the same min/max level

Why should you ignore min/max level in the first place...?

I mean, the only case in which you should ignore min/max level is when your "extract" list contains all those opcodes that do not treat those fields as min/max level, that is: op12, op17, op18, op331, etc... But unless we're dealing with very specific cases, you should not extract such opcodes into subspells...

As a result, I think that all extracted opcodes should indeed share the same min/max level... That's at least what I would do...

38 minutes ago, DavidW said:

Does type do anything relevant here?

It is certainly relevant if the subspell scales with level...

In any case, there should be no harm in inheriting it... That's at least what I would do...

40 minutes ago, DavidW said:

This is sensible.

Just to clarify: it is related to op220/221.

If the subspell inherits `power` but not `primary/secondary type`, then those opcodes would remove nothing...

53 minutes ago, DavidW said:

It's a subspell - when can its icon be seen?

Yes, you're right. As I said, it's totally optional and harmless, up to you...

Link to comment
10 minutes ago, Luke said:

Why should you ignore min/max level in the first place...?

It's not being ignored. It's present on the subspell. 

 

11 minutes ago, Luke said:

 

1 hour ago, DavidW said:

Does type do anything relevant here?

It is certainly relevant if the subspell scales with level...

Point.

Link to comment
1 minute ago, DavidW said:

It's not being ignored. It's present on the subspell.

Yeah, what a mess.

Well, let's say that I can't understand why "all effects need to have the same min/max level" is a disadvantage...

Link to comment
2 hours ago, Luke said:

Yeah, what a mess.

Well, let's say that I can't understand why "all effects need to have the same min/max level" is a disadvantage...

It's more that I can't see any advantage. Why not (e.g.) be able to extract a subspell with different bits that affect characters at different levels, or that has some opcodes where those fields aren't interpreted as min/max level?

Link to comment
On 6/6/2022 at 3:44 AM, kjeron said:

Power on the subspell for opcodes 220/221.

Before I forget: if the parent file uses an AoE projectile, then `power` should be 0 on the child/subspell file (which is supposed to use the single-target projectile `#1|None`), otherwise those effects would be subjected to deflection/reflection/trap (and that would be incorrect). Right?

Also, speaking of this: can you confirm that all hardcoded projectiles (those that have no resource to list, such as "Cone_Of_Fire") are Single-Target projectiles...?

Link to comment

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...