By DavidW - V1.1 12/14/2023
You can install some pretty sophisticated mods just using the tools in Chapter 1, but to really get at WEIDU’s power you need to learn to use it like a programming language. This chapter is a basic introduction to WEIDU at that level. If you’ve never used any other programming language before you might find it a bit fast in places.
Before going any further, we need to reckon with one of the more annoying features of WEIDU: the distinction between action and patch contexts. Look at the following component:
BEGIN "Hello, world" DESIGNATED 100
PRINT "Hello, world!"
This works fine and does exactly what you’d expect it to. But now look at this:
BEGIN "Hello, world BROKEN" DESIGNATED 100
COPY_EXISTING "minsc.cre" override
PRINT "Hello,world!"
BUT_ONLY
This will fail to install: WEIDU will tell you it’s badly formed code.
The reason is that the print command in this second bit of code appears in a patch: a chunk of code that’s modifying a currently-open file (in this case, minsc.cre). WEIDU has different syntax for patch contexts than for action contexts (the basic context you’re in when you’re not doing patching), and WEIDU commands work only in one context, not both. In some cases, that makes sense: COPY_EXISTING and EXTEND_TOP only work in action contexts, WRITE_LONG and SAY only work in patch contexts. But some commands shouldn’t really depend on the context. Usually WEIDU defines duplicate commands in this case. For instance, you can print in patch context like this:
COPY_EXISTING "minsc.cre" override
PATCH_PRINT "Hello,world!"
BUT_ONLY
Occasionally it doesn’t provide a copy at all and you have to work around it. You can force a change of context from action to patch like this:
OUTER_PATCH "" BEGIN
PATCH_PRINT "Hello world"
END
Or from patch to action like this:
COPY_EXISTING "minsc.cre" override
INNER_ACTION BEGIN
PRINT "Hello,world!"
END
BUT_ONLY
(How does WEIDU work out what context we’re in? It goes into patch context as soon as you do a COPY, COPY_EXISTING, or anything else that starts a patch. It goes out of patch context as soon as it encounters a command that’s in action context. So in the broken component above, WEIDU actually goes into action context when it sees the ‘PRINT’ command. It doesn’t get upset until it encounters ‘BUT_ONLY’, which is a patch-context command that doesn’t make sense in action context and can't be used to start a patch.)
In my examples below, I’ll mostly use action context; I’ll explain the patch-context alternatives as I go along.
WEIDU has two basic kinds of variables, Integer and String, and it’s reasonably fussy about distinguishing them. It’s sometimes also helpful to think of it as having a Boolean type (0/1) though really these are just integers. (There are also arrays; we’ll talk about them later.) Variables can have basically any sensible name; there are no real conventions. (Don't use a WEIDU command name, or a string with spaces in, as a variable; both are legal, but neither wise.)
This is a good point to remind you that this whole course assumes that you put AUTO_EVAL_STRINGS into the preamble of your tp2 file. If you didn't, variables work a little differently, and you're on your own.
OUTER_SPRINT var "Minsc is awesome"
In patch context, it’s just
SPRINT var "Minsc is awesome"
To tell WEIDU you want the value of variable ‘var’ and not just the string ‘var’, you wrap it in % signs and quote marks, like this:
PRINT "%var%"
You can concatenate (join) strings like this
SPRINT var "Cat"
SPRINT var2 "Dog"
SPRINT var3 "%var%%var2%"
SPRINT var3 "%var%" ^ "%var2%"
You set an integer like this:
OUTER_SET var=17
SET var=17
var=17
You can enter integers in binary, hexadecimal or decimal form: WEIDU treats 0b11111111, 0xff, and 255 as the same. If you want to set an integer to a negative value, you need to put the value in quotes:
OUTER_SET var="-17"
Unlike strings, WEIDU is fine for you to use the variable name, without quotes or % signs, to reference its value. (I guess because ‘var’ clearly isn’t an integer itself, so if you use it in an integer context, it’s obviously being used as a variable). So you can do
OUTER_SET var1=10
OUTER_SET var2=20
OUTER_SET var3=var1+var2
OUTER_SPRINT var "17"
OUTER_SET var=var+1
WEIDU treats "x+=n" as a synonym for "x=x+n" (you can also use *=, -= and /= in this way) and "++x" as a synonym for "x=x+1".
WEIDU uses the standard arithmetic operations: x+y, x*y, x**y are all well-defined. x MODULO y returns the remainder of x/y; x / y is division without remainder.
WEIDU variables, by default, are defined with global scope: once defined, they remain defined permanently and can be accessed at any later point in the code. This even includes later components - consider this code, for instance:
BEGIN "Set variable" DESIGNATED 100
OUTER_SPRINT var "Variable has been set"
BEGIN "Check variable" DESIGNATED 200
PRINT "%var%"
You can restrict variable scope in several ways. The bluntest is to use the (action-context) command CLEAR_EVERYTHING, which wipes all variables. A subtler move is to use the WITH_SCOPE construct, which works like this:
OUTER_SPRINT var "Minsc is awesome!"
WITH_SCOPE BEGIN
OUTER_SPRINT var "Anomen is annoying"
PRINT "%var%"
END
PRINT "%var%"
The first PRINT command prints 'Anomen is annoying', but the reassignment of 'var' takes place inside WITH_SCOPE, and so is forgotten at the end of the WITH_SCOPE block. The second PRINT command prints 'Minsc is awesome!", as you'd expect. WITH_SCOPE is an action-context command; PATCH_WITH_SCOPE is the patch-context version.
The most powerful way to handle variable scope is to use functions, which we'll come to later.
There are patch-context commands that read data from the currently-being-patched file and store it in a variable – kind of the opposite of WRITE_LONG and friends. READ_BYTE [offset] [variable] reads a BYTE from offset and stores it in [variable]; similarly, READ_SHORT and READ_LONG do what you’d expect.
Here’s code that sets the Strength score of any creature in the game to at least 16:
COPY_EXISTING_REGEXP ".*\.cre" override
READ_BYTE 0x238 str
PATCH_IF str<16 BEGIN
WRITE_BYTE 0x238 16
END
BUT_ONLY
You can also use integer-valued expressions BYTE_AT [offset], SHORT_AT [offset] and LONG_AT [offset] to directly refer to data. Here’s an alternative and slightly more elegant way to write the code above:
COPY_EXISTING_REGEXP ".*\.cre" override
PATCH_IF (LONG_AT 0x238)<16 BEGIN
WRITE_BYTE 0x238 16
END
BUT_ONLY
As a useful shorthand, whenever you do WRITE_[BYTE/SHORT/LONG] WEIDU reads in the current value of whatever you’re writing and stores it in the variable ‘THIS’. So you can do, e.g.,
COPY_EXISTING "minsc.cre" override
WRITE_BYTE 0x238 (THIS + 2)
BUT_ONLY
Some IE integers stored as a signed byte (or signed short, or signed long). In this case you’ll normally want to read them in using READ_SBYTE, READ_SSHORT or READ_SLONG. (If ‘-1’ is stored as a short integer, using READ_SSHORT will return ‘-1’, READ_SHORT will return 65535.)
The READ_ASCII command lets a string be read in from an offset in a file, like this:
COPY_EXISTING "minsc.cre" override
READ_ASCII 0x2cc dialog
PATCH_PRINT "Minsc’s dialog file is %dialog%"
BUT_ONLY
COPY_EXISTING "minsc.cre" override
READ_ASCII 0x280 dv (32)
PATCH_PRINT "Minsc’s script name is %dv%"
BUT_ONLY
Infinity Engine games use .ids files to associate human-readable strings to the integers that the game engine actually uses to store most information. If you look at class.ids in Near Infinity, for instance, it’s a long table with entries like ‘1 MAGE’, ‘2 FIGHTER’, ‘149 WYVERN’ and the like. The engine uses the integer ‘1’ every time it wants to note that something is a mage, and mostly doesn’t care what the human-readable string is; it’s used by WEIDU when you compile a script, and by the engine when it processes the little fragments of script in .dlg files.
It’s often useful (especially when it comes to writing readable code) to use these human-readable values and to translate back and forth between the human-readable value and the engine-visible number. WEIDU has functionality to do this fairly easily. Here’s how you get the number of a human-readable value:
OUTER_SET class_num = IDS_OF_SYMBOL (class WYVERN)
Here IDS_OF_SYMBOL is an integer, which we’re assigning to the variable ‘class_num’ using OUTER_SET. The general syntax is: ‘IDS_OF_SYMBOL (ids_file entry)’. Here’s a application to turn Minsc into a halfling:
COPY_EXISTING "minsc.cre" override
WRITE_BYTE 0x272 IDS_OF_SYMBOL (race HALFLING)
BUT_ONLY
The patch here does the same thing as ‘WRITE_BYTE 0x272 5’, since 5 is the ids code for ‘halfling’, but it’s much more readable, and much harder to get wrong.
To go in the other direction, use the patch-context command LOOKUP_IDS_SYMBOL_OF_INT, like this:
COPY_EXISTING "minsc.cre" override
LOOKUP_IDS_SYMBOL_OF_INT myrace race (BYTE_AT 0x272)
PATCH_PRINT "Minsc’s race is %myrace%"
BUT_ONLY
Annoyingly, there’s no action-context version of LOOKUP_IDS_SYMBOL_OF_INT. If you want to do it in action context, you have to use OUTER_PATCH:
OUTER_PATCH "" BEGIN
LOOKUP_IDS_SYMBOL_OF_INT class_name class 15
END
PRINT "class 15 is %class_name%"
WEIDU treats the integers 0 and 1 as ‘false’ and true’. Most logical operations return 0 or 1 as values. For instance, given integer variables x,y, the expression 'x=y' is either 1 if x=y, or 0 if they’re distinct. ‘x
For strings, we’d instead do
"%x%" STRING_EQUAL "%y%"
"%x%" STRING_EQUAL_CASE "%y%"
Likewise, IS_AN_INT x evaluates to 1 if x is an integer variable and 0 otherwise; VARIABLE_IS_SET x evaluates to 1 if x is a variable (integer or string) and 0 otherwise.
WEIDU also has many Boolean-valued expressions that refer to files in the game. For instance, FILE_EXISTS_IN_GAME "minsc.cre" is true (1) if and only if ‘minsc.cre’ is a game file, either in the game’s main data files or in the override. FILE_EXISTS "mymod/lib/script.baf" is true iff that file exists.
You can do fairly standard things like ‘AND’ (synonym: &&), ‘OR’ (synonym: ‘||’) and ‘NOT’ (synonym: ‘!’) with Booleans.
WEIDU has a nice lightweight way to assign an integer one value if some condition holds and another value if it doesn’t. The general syntax is
(OUTER_)SET variable = condition ? v1 : v2
OUTER_SET vmax= v1 > v2 ? v1 : v2
ACTION_IF v1>v2 THEN BEGIN OUTER_SET vmax=v1 END ELSE BEGIN OUTER_SET vmax=v2 END
Here’s an example of this to give even-more-compact code to patch every creature’s Strength to at least 16:
COPY_EXISTING_REGEXP ".*\.cre" override
WRITE_BYTE 0x238 (THIS<16)? 16: THIS
BUT_ONLY
WEIDU has binary operations BNOT, BAND and BOR (and actually others, but these are the main ones). For instance, 0b00010111 BAND 0b00001100 = 0b00000100. (BNOT is evaluated on the assumption of 32 bits, so BNOT 0b1 is 0b11111111111111111111111111111110.) They’re mainly useful for setting ‘flags’, the one-bit entries in files that keep track of things like weapon usability.
For instance, here’s some code to make a weapon usable by a mage:
COPY_EXISTING "sw1h01.itm" // nonmagical longsword
WRITE_BYTE 0x20 (THIS BAND 0b11111011)
BUT_ONLY
As a useful shorthand, WEIDU defines BIT0-BIT31 in the obvious way: BIT1 is 0b10, for instance. (In general, BITN=2**N.) So the above code could be rewritten as
COPY_EXISTING "sw1h01.itm" // nonmagical longsword
WRITE_BYTE 0x20 (THIS BAND (BNOT BIT2))
BUT_ONLY
WEIDU is fine with things like
OUTER_SPRINT myvar dwvar
OUTER_SPRINT "%myvar%" "Minsc is awesome"
OUTER_SPRINT spell WIZARD_FIREBALL
OUTER_SPRINT WIZARD_FIREBALL SPWI304
OUTER_SPRINT wrong_resref "%%spell%%"
OUTER_SPRINT resref EVAL "%%spell%%"
When WEIDU processes a SPRINT or SET command, it does variable substitution on the right hand side, but it only does it once. So when we tell it to set wrong_resref to "%%spell%%" it replaces "%spell%" with ‘WIZARD_FIREBALL’, but then sets wrong_resref to "%WIZARD_FIREBALL%" without evaluating. The ‘EVAL’ command in the second assignment tells WEIDU to evaluate as far as it can. When it does that, "%WIZARD_FIREBALL%" gets evaluated to "SPWI304" and resref gets set to that.
If you copy a file with string variable values in it, by default they are not evaluated. You can force evaluation with the patch-context command EVALUATE_BUFFER, as in
COPY "mymod/resources/file_with_variables.2da" override
EVALUATE_BUFFER
A slight variant occurs when you compile scripts or dialogs (probably the most common context in which you have variable values). There, EVALUATE_BUFFER is not a separate command but an option in the COMPILE command, used like this:
COMPILE EVALUATE_BUFFER "mymod/scripts/script_with_variables.baf" override
WEIDU includes most of the standard control-flow operations: for, while, if and the like.
Here’s a WEIDU IF loop:
ACTION_IF FILE_EXISTS_IN_GAME "minsc.cre" BEGIN
COPY_EXISTING "minsc.cre" override
… do some stuff
END
You can put an ELSE condition on the end of an IF block:
ACTION_IF FILE_EXISTS_IN_GAME "minsc.cre" BEGIN
COPY_EXISTING "minsc.cre" override
… do some stuff
END ELSE BEGIN
PRINT "Minsc is missing!"
END
You can nest multiple IF … THEN … ELSE like this:
ACTION_IF FILE_EXISTS_IN_GAME "minsc.cre" BEGIN
COPY_EXISTING "minsc.cre" override
… do some stuff
END ELSE
ACTION_IF FILE_EXISTS_IN_GAME "dynahe.cre" BEGIN
PRINT "Minsc is missing, but at least Dynaheir isn’t"
END ELSE
ACTION_IF FILE_EXISTS_IN_GAME "jaheir.cre" BEGIN
PRINT "Minsc and Dynaheir are missing, but we’ve found Jaheira"
END ELSE BEGIN
PRINT "Where is everybody??!"
END
PATCH_IF is the patch context version of ACTION_IF.
Here’s a WEIDU while loop:
OUTER_SET count=10
OUTER_WHILE count>0 BEGIN
PRINT "countdown: %count%"
END
As usual in programming, you should use WHILE loops cautiously, as they can lead to infinite looping. WHILE is the patch-context version of OUTER_WHILE.
Here’s a WEIDU FOR loop:
OUTER_FOR (count=1;count<=10;++count) BEGIN
PRINT "counting up: %count%"
END
This is actually a more powerful command than meets the eye. The general syntax is
OUTER_FOR (patch commands; value; patch commands) BEGIN … END
OUTER_FOR (count=1;count<=10;PATCH_PRINT "counting up: %count%" ++count) BEGIN END
FOR is the patch-context version of OUTER_FOR.
You can cycle through an arbitrary list of elements like this:
ACTION_FOR_EACH hero IN Minsc Jaheira Dynaheir Imoen BEGIN
PRINT "I want %hero% to join my party!"
END
For instance, here’s a somewhat more elegant way to edit all the copies of Minsc:
ACTION_FOR_EACH cre IN minsc minsc2 minsc4 minsc6 BEGIN
COPY_EXISTING "%cre%.cre" override
... some edits
BUT_ONLY
END
ACTION_MATCH is a fairly powerful way of choosing between various options depending on the value of a variable. Here’s an example:
ACTION_MATCH "%npc%" WITH
Minsc BEGIN
PRINT "%npc% is awesome!"
END
Anomen "Sir Anomen" BEGIN
PRINT "%npc% is annoying; don’t hire them"
END
Jaheira Imoen Dynaheir BEGIN
PRINT "%npc% is a solid choice"
END
Edwin Viconia Sarevok BEGIN
PRINT "%npc% is powerful, but watch your back!"
END
DEFAULT
PRINT "%npc%? Never heard of them!"
END
Here the string ‘%npc%’, which is the value of the string variable ‘npc’, is sequentially compared to the various possible matches. If it’s ‘Minsc’ the first block executes; if it’s ‘Anomen’ or ‘Sir Anomen’ the second block executes, and so on. The DEFAULT block executes if none of the conditions is matched.
The patch-context version of ACTION_MATCH is PATCH_MATCH; here’s a slightly more serious use of it, to patch the strength scores of large powerful creatures:
COPY_EXISTING_REGEXP ".*\.cre" override
LOOKUP_IDS_SYMBOL_OF_INT race race (BYTE_AT 0x272)
str=0
str_ex=0
PATCH_MATCH "%race%" WITH
OGRE MINOTAUR BEGIN
str=18
str_ex=100
END
TROLL SNOW_TROLL BEGIN
str=19
END
GIANT GOLEM BEGIN
str=22
END
DEFAULT
END
PATCH_IF str>0 BEGIN
WRITE_BYTE 0x238 str
END
PATCH_IF str_ex>0 BEGIN
WRITE_BYTE 0x239 str_ex
END
BUT_ONLY
This code cycles through all the creatures in the game. It reads in the (human-readable) race of the creature and compares it sequentially to various possibilities. If it’s an ogre or minotaur its strength gets set to 18/00; if it’s a troll or snow troll it’s set to 19; if it’s a giant or golem, to 22; otherwise, it’s not set at all. (The DEFAULT block here is empty, so nothing is done if there’s no match, but we still need it.)
ACTION_MATCH and PATCH_MATCH are actually quite a lot more powerful than this, but their real power requires regexps, which I discuss below.
A WEIDU array is something like arrays, lists or hashes in other programming languages, and defines an association between elements.
The simplest way to define an array is as a list of key=>value pairs, like this:
ACTION_DEFINE_ASSOCIATIVE_ARRAY myarray BEGIN
"Minsc"=>"Awesome!"
"Jaheira"=>"Judgmental"
"Anomen"=>"Annoying"
"Sir Anomen"=>"Over-promoted"
"Melissan"=>"Clearly trustworthy?"
END
Once this is done, you can access the contents of the ‘myarray’ array as if they were variables, like this:
OUTER_SPRINT minsc_status $myarray("Minsc")
You can use a string variable as a key, as in
OUTER_SPRINT hero minsc
OUTER_SPRINT minsc_status $myarray("%hero%")
You can set new array values in the same way:
OUTER_SPRINT $myarray("Sarevok") "Treacherous, but 100hp on a critical… you’re hired."
And you can check if an array element is set using VARIABLE_IS_SET, just as with regular variable values:
ACTION_IF VARIABLE_IS_SET $myarray("Melissan") BEGIN
PRINT "Melissan’s value is set"
END
The main value of arrays is that you can cycle through all data in them, in order, like this:
ACTION_PHP_EACH myarray AS character=>note BEGIN
PRINT "%character%: %note%"
END
The general syntax is ‘ACTION_PHP_EACH [array] AS [key]=>[value] BEGIN… END. The content between the BEGIN and END is executed once for each array key=>value pair, and the actual key and value are substituted in for the variables you choose (which can be anything).(There is actually slightly more to ACTION_PHP_EACH than this, as we’ll see when we look at multidimensional arrays.) PHP_EACH is the patch-context version.
Note that an array is a unique mapping of a value to each key. If you do
OUTER_SPRINT $myarray("Sarevok") "Treacherous, but 100hp on a critical… you’re hired."
OUTER_SPRINT $myarray("Sarevok") "(note to self: don’t trust him.)"
Arrays are stored in the order that they are defined: if you cycle through the example ‘myarray’ array, you’ll get Minsc, Jaheira, Anomen, Sir Anomen, and Melissan – followed by Sarevok if you defined him with a subsequent $myarray("Sarevok") command. You can sort an array by keys, if you want:
ACTION_SORT_ARRAY_INDICES myarray LEXICOGRAPHICALLY
ACTION_SORT_ARRAY_INDICES myarray NUMERICALLY
What’s really going on in array assignments is a little subtle, especially if you’re used to arrays in more standard languages. A command like
OUTER_SPRINT $myarray("Minsc") "Awesome"
OUTER_SPRINT myvar "%myarray_Minsc%"
OUTER_SPRINT myvar $myarray("Minsc")
OUTER_SPRINT myarray_Tiax "Insane"
A little terminology helps: given a string (say, ‘myarray’), then a ‘struct’ (my term) is the collection of all variables with that string as prefix: the myarrray struct might contain myarray_0, myarray_1, myarray_2_fighter, myarray_Tiax, etc., etc. The myarray array ranges over part of the myarray struct, but not necessarily all of it. Using ‘myarray_Tiax=1’ adds to the struct but not to the array; using ‘$myarray("Tiax")=1’, or the ACTION_DEFINE_ASSOCIATIVE_ARRAY command, adds to both.
One place where this comes up is clearing arrays. WEIDU lets you doACTION_CLEAR_ARRAY myarray
ACTION_DEFINE_ASSOCIATIVE_ARRAY cre_abil BEGIN
str=>0x238
int=>0x23a
wis=>0x23b
dex=>0x23c
con=>0x23d
cha=>0x23e
END
ACTION_DEFINE_ASSOCIATIVE_ARRAY prime_req BEGIN
MAGE=>int
FIGHTER=>str
CLERIC=>wis
THIEF=>dex
\\[… - add the other classes here]
END
COPY_EXISTING_REGEXP ".*\.cre" override
READ_BYTE 0x273 class_num
LOOKUP_IDS_SYMBOL_OF_INT class class class_num
TO_UPPER class
PATCH_IF VARIABLE_IS_SET $prime_req("%class%") BEGIN
SPRINT abil $prime_req("%class%")
offset=$cre_abil("%abil%")
READ_BYTE offset current_value
PATCH_IF current_value<16 BEGIN
WRITE_BYTE offset 16
END
END
BUT_ONLY
The first array just describes part of the .cre file structure, listing which ability score lives in which offset. The second array lists the prime requisites. The main bit of code goes through each creature and (i) reads the numerical value of the creature’s class; (ii) looks up the human-readable id for the class in class.ids (and uppercases it just in case, since our array uses upper-case class names); (iii) checks if there’s a prime requisite for that class; (iv) if so, gets the offset for that prime requisite and reads its current value; (v) if it’s less than 16, sets it to 16.
You can actually do the main bit of code a bit more compactly once you’ve got some practice, like this:
COPY_EXISTING_REGEXP ".*\.cre" override
READ_BYTE 0x273 class_num
LOOKUP_IDS_SYMBOL_OF_INT class class class_num
TO_UPPER class
PATCH_IF VARIABLE_IS_SET $prime_req("%class%") BEGIN
offset=EVAL "%cre_abil_%prime_req_%class%%%"
WRITE_BYTE offset (THIS < 16)? 16: THIS
END
BUT_ONLY
WEIDU also permits arrays with more than one key. For instance, here’s (part of) an array that lists the ability scores of joinable characters (note the commas separating keys):
ACTION_DEFINE_ASSOCIATIVE_ARRAY char_abil BEGIN
Minsc,STR=>18
Minsc,STR_EX=>93
Minsc,INT=>8
Dynaheir,STR=>11
Dynaheir,STR_EX=>0
Dynaheir,INT=>17
END
The underlying variables are things like ‘char_abil_Minsc_STR’. You can cycle through an array like this with ACTION_PHP_EACH char_abil AS key=>value; what happens is that key_0 is set to the first key and key_1 is set to the second. (And so on; in principle you can define arrays of any dimension.)
For instance, here’s code to extract Minsc’s ability scores into a subarray:
ACTION_PHP_EACH char_abil AS key=>ability BEGIN
ACTION_IF "%key_0%" STR_EQ "Minsc" BEGIN
OUTER_SET $minsc_abil("%key_1%")=ability
END
END
Because of how WEIDU stores arrays, you can get yourself into trouble if you use multidimensional arrays whose keys contain the "_" symbol. For instance, $array("A" "B" "C"), $array("A_B_C"), $array("A_B" "C"), and $array("A" "B_C") are four different array entries, with keys (A,B,C), (A_B_C), (A_B,C) and (A,B_C) – but all of them point to the same string, array_A_B_C.
In common with many languages, WEIDU uses ‘regular expressions’, or regexps, to identify and edit strings and files. (If you’re completely unfamiliar with regular expressions in programming generally, you might want to find a general tutorial elsewhere before reading this section.)
Suppose ‘desc’ is a long string, and you want to see if it contains ‘Minsc’. You do it like this:
ACTION_IF INDEX ("Minsc" "%desc%")>0 BEGIN … END
That’s fine if you want to check for a specific string. But the first slot in ‘INDEX’ is a regexp, which can search for many strings at once. Suppose you want to check for either Minsc or Jaheira? You can do it like this:
ACTION_IF INDEX ("\(Minsc\|Jaheira\)" "%desc%")>0 BEGIN … END
Some more examples:
The formal rules for WEIDU regexps are in section 13 of the WEIDU readme, but I’ll repeat the main rules here for convenience:
WEIDU also predefines a number of useful strings. TAB is the ASCII character for tab, so matching [ %TAB%]+ lets you look for whitespace in text. WNL, MNL and LNL are the various ‘end of line’ symbols used in various text files, so matching [%WNL%%LNL%%MNL%] matches end-of-line.
WEIDU uses regexps very widely in its commands; here’s a very partial list.
We’ve already seen INDEX. RINDEX is like INDEX, but counts backwards from the end of the string. A fairly different example is COPY_EXISTING_REGEXP, which copies any existing file matching the regexp to the destination directory. For instance,
COPY_EXISTING_REGEXP ".*\.cre" override
[patch]
BUT_ONLY
COPY_EXISTING "minsc.*\.cre"
RESOURCE_CONTAINS [file] [regexp] checks if there’s any match for the regexp in the existing file ‘file’. For instance, RESOURCE_CONTAINS "kitlist.2da" "OHTYR" returns 1 if the string ‘OHTYR’ appears in kitlist.2da, the list of kits. (It was missing in Siege of Dragonspear until 2.6 launched.)
FILE_CONTAINS checks a specific file (which could be an in-game file in the override, or one of your mod’s files).
INDEX_BUFFER is like INDEX but it checks the currently-being-patched file. So
COPY_EXISTING_REGEXP ".*\.cre" override
PATCH_IF INDEX_BUFFER "\(wtasight\|wtarsgt\)">=0 BEGIN
SPRINT $combat_scripts("%SOURCE_RES%")
END
BUT_ONLY
RINDEX_BUFFER is like INDEX_BUFFER but counts down.
COUNT_REGEXP_INSTANCES tells you how many times a regexp appears in a string, as in:
OUTER_SET n=COUNT_REGEXP_INSTANCES "Minsc" "%string%"
PRINT "Minsc is mentioned %n% times in this string"
You can use pattern matching for control flow, like this (the patch-context version is PATCH_MATCH):
ACTION_MATCH "%resref%" WITH
"minsc .*" BEGIN
PRINT "Awesome"
END
"jaheir.*" "imoen.*" BEGIN
PRINT "part of the gang"
END
"anomen.*" BEGIN
PRINT "annoying"
END
DEFAULT
PRINT "mysterious"
END
I've illustrated ACTION_MATCH here with string matching, but it can also match integers, as in:
ACTION_MATCH var WITH
var2 BEGIN
PRINT "equal to var2"
END
1 2 3 BEGIN
PRINT "1, 2, or 3"
END
DEFAULT
PRINT "Something else"
END
In certain edge cases, something intended as a string match accidentally executes as an integer match. Here's a (stripped down, simplified) bug I ran into in my own code:
OUTER_SET enchantment=1
OUTER_SET patch=1
OUTER_SPRINT function "enchantment"
ACTION_MATCH "%function%" WITH
"patch" BEGIN
PRINT "matched 'patch'"
END
DEFAULT
PRINT "matched default"
END
ACTION_MATCH and PATCH_MATCH actually have more features than these, but mostly I haven't found them useful so I won't discuss them here; see the main WEIDU readme, and the tutorial, if you're curious.
So far we’ve only been matching regexps, but we can also replace a matched regexp with new text. You do this in patch context, on the current file being patched. There are two WEIDU find-and-replace commands, the relatively simple REPLACE_TEXTUALLY and the powerful REPLACE_EVALUATE.
In the simplest applications, REPLACE_TEXTUALLY works like this: REPLACE_TEXTUALLY regexp newtext, with the new text being swapped in for each occurrence of the regexp; for instance,
REPLACE_TEXTUALLY "Minsc" "Minsc the Awesome"
REPLACE_TEXTUALLY "\(Anomen\|Jaheira\|Viconia\|Aerie\)" "\1 the cleric"
As a slightly more complicated example, consider
REPLACE_TEXTUALLY "See(\([^)]*\))" "Detect(\1)"
The \1 construct lets you do elementary processing in your search-and-replace, but to do anything more sophisticated you need REPLACE_EVALUATE. The syntax is REPLACE_EVALUATE regexp BEGIN some-processing END newtext. Here’s an example of it in action:
REPLACE_EVALUATE "\(Minsc\|Anomen\|Sarevok\|Jaheira\)" BEGIN
PATCH_MATCH "%MATCH1%" WITH
"Minsc" BEGIN
SPRINT epithet "Awesome"
END
"Anomen" BEGIN
SPRINT epithet "Annoying"
END
"Jaheira" BEGIN
SPRINT epithet "Judgmental"
END
"Sarevok" BEGIN
SPRINT epithet "Violent"
END
DEFAULT
// it shouldn’t be possible to get here
END
END
"%MATCH1% the %epithet"
REPLACE_EVALUATE isn’t just useful for editing a file: it’s also a powerful tool to get information out of a file. For instance, the following code breaks the file into an array, one entry per line in the file:
CLEAR_ARRAY lines
count=0
REPLACE_EVALUATE "^\([^%WNL%%MNL%%LNL%]+\)" BEGIN
SPRINT $lines("%count%") "%MATCH1%"
++count
END
""
The regexp matches any string starting at the beginning of a line (the ^ denotes this) and then containing an arbitrary (but non-zero) number of symbols not including an end-of-line symbol. That match is then written into a line of the array. The actual ‘replace’ is irrelevant – we’re throwing the file away anyway after getting the data – so we just do "".
The OUTER_PATCH action-context command (and its patch-context cousin, INNER_PATCH) extend REPLACE_TEXTUALLY and, even more so, REPLACE_EVALUATE, into extremely flexible tools to analyze strings in situations where WEIDU’s built-in analyzers aren’t sufficient. OUTER_PATCH takes a string and lets you treat that string as if it were a file, patching it however you like (but, most commonly, using REPLACE_EVALUATE and REPLACE_TEXTUALLY to rearrange it).
For instance, suppose you know that a certain string, ‘var’, consists of two words separated by a space. You can get the two words like this:
OUTER_PATCH "%var%" BEGIN
REPLACE_EVALUATE "\([^ ]\) \([^ ]\)" BEGIN
SPRINT var1 "%MATCH1%"
SPRINT var2 "%MATCH2%"
END
""
END
The string being patched is thrown away after the patch is finished. If you want to keep it, you can use OUTER_PATCH_SAVE instead. For instance, the following code replaces all spaces in a string with commas:
OUTER_PATCH_SAVE var "%var%" BEGIN
REPLACE_TEXTUALLY " " ","
END
WEIDU uses ‘functions’ (and, occasionally, ‘macros’) to organize code into discrete sections and to allow useful bits of code to be reused without physically retyping them. Using the extensive library of functions that WEIDU ships with already makes many tasks far, far easier; once you can write your own functions, and learned to use functions provided by other people, you will find WEIDU’s capacities radically transformed.
(Another reminder: this whole course assumes you have AUTO_EVAL_STRINGS in the preamble of your tp2 file. If you don't, functions work a bit differently, and you're on your own.)
You can think of a function as a machine that takes inputs (strings and/or integers), takes some action, and then provides outputs (strings and/or arrays). Or rather, as a machine that maybe takes inputs, maybe does something to files, maybe provides outputs – because each step, individually, is optional. (A function that takes no action and modifies no file, however, would be fairly pointless.) In programming jargon, you 'call' or 'run' a function when you use it.
The basic input/output structure of a function is specified like this:
To see how the function interface works, consider a very simple pair of functions, both of which are included in WEIDU: RES_NUM_OF_SPELL_NAME and RES_NAME_OF_SPELL_NUM. (This example is not all that impressive as an illustration of how useful functions are, to be honest: I’m using it just because it’s easy to understand.)
To understand the example, note that in the IE games there are three ways to refer to spells: by resref (the filename), by IDS number, or by IDS entry. For instance, Fireball can be referred to as WIZARD_FIREBALL (its entry in spell.ids), as 1304 (the number of that entry), or as SPWI304 (its resref, i.e. its actual filename). The filename can be obtained from the IDS entry: entries of form 1xyz have resref SPWIxyz, entries of form 2xyz have resref SPPRxyz, etc.
RES_NUM_OF_SPELL_NAME determines the resref automatically from the ids name (and the number too, though you can just get that from WEIDU’s built-in IDS_OF_SYMBOL). It takes one argument, a STR_VAR called ‘spell_name’ and returns two RET values, spell_num and spell_res. You use it like this (say, to determine the resref and number of Fireball:
LAUNCH_ACTION_FUNCTION RES_NUM_OF_SPELL_NAME
STR_VAR spell_name=WIZARD_FIREBALL
RET fireball_number=spell_num
fireball_resref=spell_res
END
After this code has run, ‘fireball_number’ will be set to 3104 and fireball_resref’ will be set to SPWI304.
Relatedly, RES_NAME_OF_SPELL_NUM takes one argument, an INT_VAR called ‘spell_num’, and returns spell_name and spell_res, like this:
LAUNCH_ACTION_FUNCTION RES_NAME_OF_SPELL_NUM
INT_VAR spell_num=3104
RET fireball_name=spell_name
fireball_resref=spell_res
END
(There’s also NAME_NUM_OF_SPELL_RES, which does exactly what you’d expect.)
Launching functions is, like every other WEIDU command, different in action and patch contexts: you use LAUNCH_PATCH_FUNCTION in patch context. To complicate things further, functions themselves can either be action functions (which only work in action context), patch functions (which only work in patch context), or dimorphic functions (which work in both). RES_NUM_OF_SPELL_NAME and friends are all dimorphic. (To complicate it further still, in principle an action function can have the same name as a different patch function.)
There are a number of variants worth knowing:
OUTER_SPRINT fireball_name WIZARD_FIREBALL
LAF RES_NUM_OF_SPELL_NAME
STR_VAR spell_name="%fireball_name%"
RET fireball_number=spell_num
fireball_resref=spell_res
END
OUTER_SET fireball_number=3104
LAF RES_NAME_OF_SPELL_NUM
INT_VAR spell_num=fireball_number
RET fireball_name=spell_name
fireball_resref=spell_res
END
LAF RES_NUM_OF_SPELL_NAME
STR_VAR spell_name=WIZARD_FIREBALL
RET fireball_resref=spell_res
END
LAF RES_NUM_OF_SPELL_NAME
RET fireball_resref=spell_res
END
OUTER_SPRINT spell_name SPWI304
LAF RES_NUM_OF_SPELL_NAME
STR_VAR spell_name
RET spell_num spell_res
END
To get a better sense of what functions can do for you, let’s consider a less trivial example: suppose you want to modify the Fireball spell so that it does 1d8 fire damage per level instead of 1d6 damage, imposes a 2-point saving throw penalty, and then does an additional 1d4 fire damage per level after one round if you fail your saving throw. (This is way overpowered: it’s a coding example, not an actually-sensible change.)
If you open up Fireball in Near Infinity (for simplicity I’ll assume we’re on the Enhanced Edition), you’ll see that it has six ability headers (one for each of levels 5-10) and, under each, a bunch of effects, including a Damage effect (opcode 12) that encodes the save-for-half rule, the fact that the saving throw is against spells, the lack of a save penalty, and the 1d6/level damage. So making this change in WEIDU requires us (i) to edit the damage block to change the dice size and add the save penalty and (ii) to add an extra Damage block that triggers after 1 round for the extra damage and that you can save to avoid entirely. (To do it properly we’d also ideally add some visual effects and maybe a ‘burning’ text string, but let’s keep it simple for now.)
It is possible to do this directly using WEIDU’s low-level editing commands and control flow (I wrote many such bits of code in the days before WEIDU got functions) but it’s a very time-consuming, finicky business (and so most people would end up making the spell in an editor and just copying it across). Doing it in modern WEIDU is child’s play using the ALTER_EFFECT and CLONE_EFFECT patch functions. I won’t give the full input structure for them, because it’s pretty complicated (see the WEIDU documentation for the full version) but the basic idea is that ALTER_EFFECT cycles through all the effect blocks in a spell (or item or creature), checks each against a set of ‘match’ conditions, and if it finds a match, applies a bunch of ‘patch’ conditions (each patch sets the value of one field in the effect). CLONE_EFFECT is similar, except that instead of modifying the existing effect it creates a copy of it and modifies the copy.
Here, then, is the full implementation of our change to Fireball:
COPY_EXISTING "spwi304.spl" override
SAY DESCRIPTION @100
LPF ALTER_EFFECT INT_VAR match_opcode=12 dicesize=8 savebonus="-2" END
LPF CLONE_EFFECT INT_VAR match_opcode=12 dicesize=4 timing=4 duration=6 special=0 END
BUT_ONLY
And that’s it! The ALTER_EFFECT matches against opcode=12 (the damage opcode) and sets the new number of dice and the save penalty. The CLONE_EFFECT matches the same blocks, but now it creates a copy of them, sets the damage for the copy to 1d4/level, changes the timing/duration so that it comes in after 6 seconds instead of being instant, and removes the ‘save for half’ flag (so that a saving throw removes the effect altogether).
There are many functions like ALTER_EFFECT and CLONE_EFFECT which, collectively, move WEIDU a long way towards editing objects at a high level (via their logical content) rather than at a low level (via editing the actual bytes). This makes code radically faster to write, easier to read, and less likely to contain stealth bugs.
DEFINE_ACTION_FUNCTION add_two_integers
INT_VAR
int1=0
int2=0
RET
value
BEGIN
OUTER_SET value=int1+int2
END
First you have a DEFINE_ACTION_FUNCTION that tells WEIDU you’re defining an action function. Then you have the name of the function. Then you have the INT_VARs and/or STR_VARs, along with their default settings. Then you have the return values. Finally, you have actual WEIDU code, wrapped in BEGIN…END.
The above function’s code is in action context, because it’s an action function. For patch functions, you write patch-context code. Here's a marginally more useful function that pulls the name and description from the file being edited (assumed to be a spell):
DEFINE_PATCH_FUNCTION get_name_desc
RET
name_strref
desc_strref
BEGIN
READ_LONG 0x8 name_strref
READ_LONG 0x50 desc_strref
END
Dimorphic functions are basically action functions that can also be run as patch functions, so you define them like this:
DEFINE_DIMORPHIC_FUNCTION add_two_integers
INT_VAR int1=0
int2=0
RET value
BEGIN
OUTER_SET value=int1+int2
END
When defining your own functions, note that you must actually return all the values you say you will. If you declare a variable in the RET part of a function, and don't actually return that variable when the function is called, WEIDU will throw an error. For instance, the following is badly designed:
DEFINE_ACTION_FUNCTION return_cre_name_strref_BAD
STR_VAR cre=""
RET cre_name
BEGIN
COPY_EXISTING - "%cre%.cre" nowhere
READ_LONG 0x8 cre_name
IF_EXISTS
END
DEFINE_ACTION_FUNCTION return_cre_name_strref
STR_VAR cre=""
RET cre_name
BEGIN
OUTER_SET cre_name="-1"
COPY_EXISTING - "%cre%.cre" nowhere
READ_LONG 0x8 cre_name
IF_EXISTS
END
Importantly, any variables set within a function are forgotten at the end of running the function (that is: their scope is local to the function), unless you explicitly return them as a RET or RET_ARRAY value of the function. Consider the following function
DEFINE_PATCH_FUNCTION is_goblinoid
RET value
BEGIN
READ_BYTE 0x272 race_var
value= (race_var=143 || race_var=111 || race_var=161)
END
DEFINE_PATCH_FUNCTION I_am_not_encapsulated
RET value
BEGIN
value = ( VARIABLE_IS_SET external_variable ) ? 1 : 0
END
END
The RET_ARRAY feature of functions is subtle and should be used carefully. A RET_ARRAY this_array, in a function definition, does two things:
For instance, consider this function
DEFINE_PATCH_FUNCTION set_this_value
STR_VAR array=""
RET_ARRAY array
BEGIN
SET $array("value1")=1
END
LPF set_this_value STR_VAR array=array1 RET_ARRAY array2=array END
DEFINE_PATCH_FUNCTION set_this_value_and_copy_the_array
STR_VAR array=""
RET_ARRAY array
BEGIN
PHP_EACH "%array%" AS k=>v BEGIN
SPRINT $array("%k%") "%v%"
END
SET $array("value1")=1
END
Unlike ordinary RET variables, WEIDU lets you return an empty array with RET_ARRAY. There is, however, a bug in WEIDU (as of v249) which sometimes causes this to fail (and WEIDU to throw an error) if you return an empty array whose name is shared with an already-defined array.
As well as functions, WEIDU permits macros. A macro is effectively a small piece of included code (there is not much difference between putting a bit of code in a macro and INCLUDE-ing it). Unlike functions, macros are not encapsulated at all: any change to a variable made in the macro is preserved once the macro stops running.
The syntax for macros is very similar to that for functions. You call them via LAUNCH_ACTION_MACRO (abbreviated to LAM) or LAUNCH_PATCH_MACRO (abbreviated to LPM), like this:
LAM my_macro
DEFINE_ACTION_MACRO my_macro BEGIN
[some code]
END
DEFINE_ACTION_MACRO set_spell_vars
BEGIN
COPY_EXISTING_REGEXP - "sp\(in\|cl\|pr\|wi\)[0-9][0-9][0-9]\.spl" nowhere
SPRINT spell_res "%SOURCE_RES%"
TO_UPPER spell_res
LPF NAME_NUM_OF_SPELL_RES STR_VAR spell_res RET spell_name END
SPRINT "%spell_name%" "%spell_res%"
END
If you tried doing this with a function, all the values would be lost when the function finished running, and they can't be returned as separate RET values because you don't know what they all are (and even if you did, the syntax would be hideous).
Although by default variables in a macro have global scope, you can declare some to be local to the macro if you want to. You do so using the command LOCAL_SET (for integer variables) or LOCAL_SPRINT (for string variables), with the same syntax as SET and SPRINT, respectively. For instance, in the above macro the string variables 'spell_res' and 'spell_name' really ought to be internal to the macro: we don't want them returned. So a better version of the macro is
DEFINE_ACTION_MACRO set_spell_vars
BEGIN
LOCAL_SPRINT spell_name ""
LOCAL_SPRINT spell_res ""
COPY_EXISTING_REGEXP - "sp\(in\|cl\|pr\|wi\)[0-9][0-9][0-9]\.spl" nowhere
SPRINT spell_res "%SOURCE_RES%"
TO_UPPER spell_res
LPF NAME_NUM_OF_SPELL_RES STR_VAR spell_res RET spell_name END
SPRINT "%spell_name%" "%spell_res%"
END
I strongly recommend that you make sure any variables that you don't explicitly want to return are made local in this way. Loose variables in macros can lead to extremely nasty and hard-to-trace bugs.
Variables set in a function are local scope and are forgotten after the function finishes running, but functions are not completely effective in localizing scope. For one, function (and macro) definitions always have global scope, even if defined within another function. The following code works properly, for instance:
DEFINE_ACTION_FUNCTION wrapper BEGIN
DEFINE_ACTION_FUNCTION hello_world BEGIN
PRINT "Hello, world!"
END
END
LAF wrapper END
LAF hello_world END
However, function definitions are wiped by the CLEAR_EVERYTHING command: the following code will throw an error:
DEFINE_ACTION_FUNCTION hello_world BEGIN
PRINT "Hello, world!"
END
CLEAR_EVERYTHING
LAF hello_world END
Similarly, TRA files LOADed during a function call are remembered even when the function finishes, so that this code works properly, for instance:
DEFINE_ACTION_FUNCTION load_tra BEGIN
LOAD_TRA "%MOD_FOLDER%/test.tra"
END
LAF load_tra END
OUTER_SPRINT var @1
PRINT "var is %var%"
I'll be honest: WEIDU's error handling is shaky compared to mainstream computer languages. Still, it does exist, and it's useful to learn a bit about it.
It's helpful to think of four sorts of errors in your code:
Lexer errors occur when WEIDU runs into an 'invalid character' in your code: a character that just isn't allowed to be there. Lexer errors are fairly rare, but if you just put the symbol '#' in your code, for instance, you'll get one. WEIDU reports lexer errors fairly helpfully, telling you which the character is and where it occurs in your code. (It will also give you a condescending message telling you not to use MS Word to write your code. I assume this was a thing people used to do.)
In my experience the usual cause of lexer errors is leaving out a quotation mark somewhere - either not wrapping a string in quotation marks properly, or failing to end the quotes on a string.
Parse errors occur when WEIDU can make sense of the individual characters in your code but can't work out how to interpret it as grammatical WEIDU code. When WEIDU encounters a parse error, it tells you which bit of code caused it, and lists all the WEIDU commands that would have been legal. Sometimes this is helpful, but often it's a bit counterintuitive exactly where WEIDU finally gives up and decides your code is ungrammatical - the actual error in the code might have been much earlier. For instance, in this code
COPY_EXISTING "minsc.cre" override
// make Minsc a berserker if he's already a fighter
READ_BYTE 0x273 class
ACTION_IF class=1 BEGIN
WRITE_LONG 0x244 0x4001
END
BUT_ONLY
In my experience, parse errors usually occur either because action and patch context get mixed up, or because an END is missing (or too many are present), or because I have misremembered the syntax of some fiddly WEIDU command. A good highlighter can really cut down on parse errors.
Install time errors occur when your code is legal, but something goes wrong when it's running (i.e., when the mod is installing). WEIDU will try to give you an error message when this happens that explains what went wrong, but it is not reliable in doing so. A fairly common WEIDU error message is 'Not_Found' - this doesn't mean that some game file can't be found, but that WEIDU can't find the error message it was looking for.
Don't ask me why, but I have found that WEIDU can sometimes be coaxed into giving a more informative error message if you force it to emit some other string - via PATCH_PRINT or similar - shortly before the place where the error happens.
In any case, if a WEIDU component throws an install-time error, the whole component will be rolled back, and all the changes it made before the error will be undone, leaving the game you're modding in the same state it was before the component started to install. This is something WEIDU is reliable on - I've never known it to mess up clean uninstallation.
Run time errors occur when the code has managed to run and the component has installed without errors, but it hasn't done what it is supposed to do. For instance, consider this code:
COPY_EXISTING "minsc.cre" override
// make Minsc a berserker if he's already a fighter
SAY 0x0 "Minsc the Awesome!"
BUT_ONLY
Run-time errors are the worst errors, as they can't be caught without actually testing your component. Where possible, try to code so that if something goes wrong it leads to an install-time rather than run-time error.
When WEIDU hits an install-time problem but the problem isn't immediately fatal, it throws a WARNING: a message is displayed on the screen and then the component is flagged as INSTALLED WITH WARNINGS rather than SUCCESSFULLY INSTALLED. There's no really systematic rule for when WEIDU gives a warning rather than throwing an error, but an example is if it tries to compile a BAF file to BCS but the compilation fails - WEIDU will keep going after this failure, but warns you something has gone wrong.
It can be tricky to work out just what WEIDU is warning you about: if it throws an error then installation stops right away, so you can read what WEIDU says, but a WARNING might be buried behind thousands of lines of subsequent text. Usually the best solution is to look at your mod's debug log. This file has a name like 'setup-mymod.debug'. By default it is just dumped in your game directory, but if you create a directory called 'debugs' in your game directory, WEIDU puts debug logs there instead. Searching it for 'warning' and 'error' usually finds the problem fairly quickly. (Sometimes it is also useful to do this for especially obscure install-time errors.)
Do not be complacent about WEIDU warnings. Just because your mod has installed, that doesn't mean it's working correctly. You should always try to identify the source of warnings and resolve the problem. I strongly recommend against releasing any mod that gives install-time warnings.
For instance, suppose you're writing a patch function to patch spell files, and you want to guard against it accidentally being applied to an item file (which will normally be broken if you apply to it code intended for a spell file). You can guard against this by putting this code into the function:
PATCH_IF !("%SOURCE_EXT%" STR_EQ "SPL") BEGIN
PATCH_FAIL "function 'my_function' has been applied to %SOURCE_FILE%, but it only works on SPL files"
END
Similarly, 'WARN string' (or its patch-context analog, 'PATCH_WARN string') throws a WEIDU warning and displays 'string'.
Most modern programming languages have the ability to 'catch' and 'handle' errors - instead of just ending execution of the code when an error occurs, the code itself responds to the error.
WEIDU can technically do this. (There is a significant caveat, that I'll come to later.)
As an example, suppose we're patching a file, and we have a string, 'patch', that might or might not be the name of a patch function. If it is, we want to apply it to the file. We would like to do something like this:
PATCH_IF IS_A_FUNCTION "%patch%" BEGIN // not actually a WEIDU command!
LPF "%patch%" END
END
PATCH_TRY
LPF "%function%" END
WITH
".*Unknown function.*" BEGIN
END
DEFAULT
PATCH_RERAISE
END
Here, we TRY running the function. If we get an error, we can then pattern-match on the error just as in PATCH_MATCH. In the above code, if the error message contains the string 'Unknown function' then the problem is that the function doesn't exist, in which case we shrug and get on with things. If it does not contain that string, the problem is instead that the function went wrong somehow, and so we want to keep the error. We do that with the PATCH_RERAISE command, which just re-triggers the error.
Of course, the action-context versions are ACTION_TRY and ACTION_RERAISE.
Unfortunately, error handling in WEIDU is unsafe. As the main WEIDU readme says, 'TRY is not safe to use because many errors are intended to be fatal and if the mod installation were to proceed anyway, it might do so in an inconsistent state, with resource leaks or with other errors'. So you should be careful, at best, using it in live code. (The specific example I use above happens to be an exception - failing to run a function because it doesn't exist doesn't create any state problems - but I know this because I happened to ask WEIDU's current maintainer, not because of any systematic principle.)
At the least, you should usually make sure that a TRY generates a warning. As a real-life example, my SCS mod goes through every creature in the game and applies a function 'genai' to it. That function often gets upset if some earlier mod has done something stupid to the file. Rather than have SCS fail outright in that situation, I get it to throw a warning, with code like this:
COPY_EXISTING_REGEXP ".*\.cre" override
PATCH_TRY
LPF genai END
WITH DEFAULT
PATCH_WARN "SCS function 'genai' has failed to run on creature file %SOURCE_FILE%"
END
BUT_ONLY
MODDER is an additional flag you can add to the preamble of your TP2 code while you are developing it (you need to remove it when you release the mod). Its syntax looks like this:
MODDER
setup_tra none
area_variables none
missing_extern none
missing_resref none
ict2_actions none
missing_eval none
overwriting_file none
fun_args warn
The example above is the one I use when modding. As you can see, I actually don't find most of the MODDER flags useful. The huge exception is fun_args, which warns you when you call a function with an argument that actually isn't one of the arguments it was defined with. (Yes, you can do this; no, you should never do this, which is why I didn't tell you it was legal in section 2.7) This is a major source of silent bugs in function-heavy WEIDU coding, and it's extremely helpful to have fun_args turned on to catch them.
If multiple mods are installed at once, the MODDER flag on one mod can in some circumstances affect other mods. For this reason it's essential to remove (or comment out) your MODDER flag before releasing your mod.