The alter_effect library contains expanded versions of the core WEIDU function ALTER_EFFECT, CLONE_EFFECT, and DELETE_EFFECT (all written by CamDawg). Part of the point of this section is to explain their expanded functionality under SFO, but the main part is to offer an introduction to SFO's use of functions as arguments ('functional programming') and its 'anonymous function construct'.
This section assumes you have basic familiarity with the ALTER/CLONE/DELETE_EFFECT functions. (They are fully documented in the weidu readme; I discuss them in my "Course on WEIDU".) Note that none of this applies to ADD_SPELL_EFFECT and the like, which SFO does not expand.
The most straightforward SFO change to these functions is to add a large group of new INT_VAR arguments that target specific bits in the 'saving throw' and 'special' fields. Specifically, SFO adds these arguments:
LPF ALTER_EFFECT INT_VAR match_save_vs_poison=1 save_vs_poison=0 save_vs_spell=1 END
SFO's real expansion of these functions comes when you want to do something more sophisticated than the normal interface for ALTER_EFFECT permits. Here are four examples:
This is easier to explain through examples. Here's how to do task 1 (delete several opcodes):
DEFINE_PATCH_FUNCTION delete_several_opcodes
RET value
BEGIN
READ_SHORT 0x0 opcode
value = (opcode=139 || opcode=142 || opcode=174 || opcode=215)
END
COPY_EXISTING "sppr603.spl" override
LPF DELETE_EFFECT STR_VAR match_function=delete_several_opcodes END
And here's how to do task 2 (delete all but a fixed list of opcodes):
DEFINE_PATCH_FUNCTION keep_only_a_few_opcodes
RET value
BEGIN
READ_SHORT 0x0 opcode
value = !(opcode=139 || opcode=142 || opcode=174 || opcode=215)
END
COPY_EXISTING "sppr603.spl" override
LPF DELETE_EFFECT STR_VAR match_function=keep_only_a_few_opcodes END
Note that the match_function is matched only after any other matches are made (this helps with speed).
The other argument, 'function', names a patch function which acts on each matched effect, again treating it as if it was a stand-alone file. Here's how to do task 3 (double the duration of a spell):
DEFINE_PATCH_FUNCTION double_duration
BEGIN
WRITE_LONG 0xe (THIS*2)
END
COPY_EXISTING "spwi305.spl" override
LPF ALTER_EFFECT STR_VAR function=double_duration END
For the last one, we need to use the 'level' variable, which is set to the minimum level of whatever spell is being patched (or to 1 for items). Here's task 4 (level-dependent per-missile damage for magic missile):
DEFINE_PATCH_FUNCTION patch_mm
BEGIN
WRITE_LONG 0x20 (level<5?4:level<9?6:8)
END
COPY_EXISTING "spwi112.spl" override
LPF ALTER_EFFECT STR_VAR function=patch_mm END
Sending functions to ALTER_EFFECT and friends as arguments is extremely powerful, but can feel cumbersome – you are writing functions that you only actually need once. The anonymous function construct greatly streamlines things: instead of sending the name of the function, you can send the function itself - or rather, the bit of the function definition between 'BEGIN' and 'END'. For instance, here's task 1 via the anonymous function construct:
COPY_EXISTING "sppr603.spl" override
LPF DELETE_EFFECT
STR_VAR match_function="READ_SHORT 0x0 opcode ;; value=(opcode=142 || opcode=139 || opcode=174 || opcode=215)"
END
The variable 'function' now defines an anonymous function: a function with no name created just for this particular patch. (The double ;; are for readability; they can be skipped.)
Similarly, here's task 2:
COPY_EXISTING "sppr603.spl" override
LPF DELETE_EFFECT
STR_VAR match_function="READ_SHORT 0x0 opcode value=!(opcode=142 || opcode=139 || opcode=174 || opcode=215)"
END
If your anonymous function definition is just a mathematical expression, SFO will infer a 'value='. This works, for instance, as an alternative implementation of task 1:
COPY_EXISTING "sppr603.spl" override
LPF DELETE_EFFECT
STR_VAR match_function="(SHORT_AT 0x0=142 || SHORT_AT 0x0=139 || SHORT_AT 0x0=174 || SHORT_AT 0x0=215)"
END
You can use anonymous functions for 'function' as well as 'match_function'. Here's task 3:
COPY_EXISTING "spwi305.spl" override
LPF ALTER_EFFECT STR_VAR function="WRITE_LONG 0xe (THIS*2)" END
And task 4:
COPY_EXISTING "spwi305.spl" override
LPF ALTER_EFFECT STR_VAR function="WRITE_LONG 0x20 (level<5?4:level<9?6:8)" END
Anonymous functions are not restricted to ALTER_EFFECT and friends: the anonymous function construct is widely available in SFO functions. In other contexts, it is important for an anonymous function to accept arguments; in that context, you can use '__' as an abbreviation for '%arguments%'. (This is useful because %arguments% will be evaluated if you happen to have it defined at the point at which you specify the anonymous function.)
SFO determines heuristically whether a given 'function' string is to be processed via the anonymous function construct or is the name of an actual function: anonymous functions contain any of these symbols: '[]<>/=+%{} '. In the (unlikely!) event that you manage to write an anonymous function not containing any of them, just put an extra space in.
If you need to debug an anonymous function, you can find the function fully expressed in weidu_external/workspace/sfo_anon_func_[N].tph, where [N] is usually 0 but may be 1 or 2.
In general, anonymous functions should be simple and short. If you are doing anything complicated, it's best to define the function explicitly.