A course on SFO - chapter 2.7

alter_script and disjunctive_substitution: tools for script and dialog editing

Back to contents

These two libraries (the second is really a single function) simplify various tasks involving script and dialog files.

2.7.1 alter_script: fine-grained editing of BCS files

The alter_script library lets you make fine-grained alterations to scripts (i.e., .bcs files) – specifically, it lets you alter, add, clone, or remove script blocks on a block-by-block basis. There are five different functions in alter_script, but they all operate in a similar way.

delete_script_block

This is the simplest alter_script function, but it serves as a good illustration of the way the other functions work. You specify the name of the script you want to alter, and up to six regexps (labelled by STR_VARs match, match1, match2, match3, match4, match5). The script is decompiled, and the regexps are checked on each script block separately, Any block that matches all specified regexps is deleted.

For instance, the wtasight script in BG2EE contains a block that responds to Shouts from creatures giving Shout=111. You could delete this block like this:

LAF delete_script_block STR_VAR script=wtasight match1=”111” match2=”Heard” END

SFO checks all four blocks in the script. Only one matches both ‘Heard’ and ‘111’; that block is deleted. (In fact either regexp alone would be sufficient.)

A more advanced option is to use the STR_VAR match_function, which specifies an SFO standard patch function that is run sequentially on each block. Blocks that return value=1 are deleted. You can use the anonymous function construct.

Two more occasionally-useful arguments:

  • INT_VAR only_once: defaults to 0, if set to 1 the function stops matching after the first match.
  • INT_VAR recompile: defaults to 1, if set to 0 the script is left in BAF rather than recompiled. (The main use for this is debugging.)

alter_script_block

This function finds blocks exactly as does delete_script_block, but when a block is found, it is edited rather than deleted. Editing is via up to six pairs of STR_VARs (swap_out/swap_in, swap_out1/swap_in1, etc.). On any matched block, we do a REPLACE_TEXTUALLY for each pair.

For instance, this adds a go-hostile command to all blocks that use Attack or AttackReevaluate:

LAF alter_script_block
	STR_VAR 
		script=wtasight 
		match="\(Attack(\|AttackReevaluate(\)"
		swap_out="RESPONSE #\([0-9]+\)"
		swap_in="RESPONSE #\1 Enemy()"
END

(Incidentally, these are all pretty elementary examples that could just as well be done by a standard REPLACE_TEXTUALLY, but of course there are much more complicated applications.) A more advanced option is to set the STR_VAR ‘function’ to a patch function that acts on every matched block. You can use the anonymous function construct.

clone_script_block

This function matches blocks in the usual way, and then makes a copy of any matched block. The copy then gets patched in exactly the same way as alter_script_block, and the original gets patched using six other pairs of STR_VARs (original_swap_out/original_swap_in, original_swap_out1/original_swap_in1, etc). (You can also specify a function, original_function, to act on the original block). For instance, this code edits wtasight so that humanoid creatures shout for help before attacking.

LAF clone_script_block
	STR_VAR 
		script=wtasight
		match="\(Attack(\|AttackReevaluate\)"
		swap_out1="ActionListEmpty()"
		swap_in1="ActionListEmpty() General(Myself,HUMANOID)"
		swap_out2="RESPONSE #\([0-9]+\)"
		swap_in2="RESPONSE #\1 Help()"
		original_swap_out="ActionListEmpty()"
		original_swap_in="ActionListEmpty() !General(Myself,HUMANOID)"
END

insert_script_block

This function inserts a new BAF file immediately after (or, if you set INT_VAR insert_above=1, immediately before) any matched block. Set ‘insert’ to the name of a BAF file to be inserted (without the .baf) and specify its location in the usual SFO style via ‘location’, ‘locbase’, and ‘path’.

replace_script_block

Like insert_script_block, except that the new script replaces any matched block.

2.7.2 disjunctive_substitution: smooth handling of OR() blocks under substitution

A general problem with editing IE scripts is handling their Boolean logic. For instance, suppose you’ve introduced a Paladin of Tyr and you want it to be treated like clerics of Tyr in certain scripts/dialogs. The natural inclination is to do something like

REPLACE_TEXTUALLY "Class(\([^,]*\),OHTYR)" "OR(2)Class(\1,OHTYR)Class(\1,DW_TYR_PALADIN)"

That works when the original Class([whatever],OHTYR) just appears simply in a script’s triggers. But it will give you a compile-time error if that text appears negated, or inside an OR() block of its own. Similarly, suppose you want to restrict something to lawful good clerics of Tyr. The temptation is to do

REPLACE_TEXTUALLY "Class(\([^,]*\),OHTYR)" "Class(\1,OHTYR)Alignment(\1,LAWFUL_GOOD)"

That will compile if the original Class() check is in an OR block or negated, but it won’t give the right logic.

SFO’s disjunctive_substitution function solves this problem. Here’s how it does the second substitution:


LAF disjunctive_substitution 
STR_VAR script="script1 script2"
   dialog="dialog1 dialog2"
   match="Class(\([^,]*\),OHTYR)"
   replace="Class(\1,OHTYR)|Alignment(\1,LAWFUL_GOOD)"
END

Here, ‘script’ is a space-separated list of scripts to be substituted; ‘dialog’ is a space-separated list of dialogs. ‘match’ is the term to be matched, while ‘replace’ is the multiple lines to be subbed in, separated by ‘|’. To do the first kind of substitution, prepend ‘OR()’ to the ‘replace’ string, like this:

LAF disjunctive_substitution 
STR_VAR script=”script1 script2” 
   dialog=”dialog1 dialog2”
   match=”Class(\([^,]*\),OHTYR)”
   replace=”OR()Class(\1,OHTYR)|Class(\1,DW_TYR_PALADIN)”
END

Note that you need not (indeed, must not) use OR(2) here. Just use OR(), and SFO will take care of the rest.

You can set ‘script’ and/or ‘dialog’ to ‘all’, and then SFO will do all the scripts/dialogs. Use with caution, though: it’s a bit slow.

(Incidentally, WEIDU theoretically has native support for this task, via REFACTOR_TRIGGER. My experience is that it’s ridiculously slow and not that reliable, though, whereas disjunctive_substitution is pretty fast unless you’re patching thousands of scripts.)