A course on SFO - chapter 2.6

lib_include and lib_sugar: smoother mod structuring, and syntactic sugar for WEIDU

Back to contents

The lib_include function contains the 'include' and 'run' functions, which aim to simplify the organization of your mod into separate pieces of code. One way they do this is through the lib_sugar library, which aims to make WEIDU syntax easier by introducing 'syntactic sugar' –i.e., abbreviations for WEIDU code that make code easier to write and more readable. (Even WEIDU's greatest fans (i.e., me) will concede that its syntax leaves a lot to be desired.).

2.6.1 Using 'include' and 'run'

include

The 'include' function is just a variant of WEIDU's core 'INCLUDE' command. Instead of including a piece of code with

INCLUDE "%MOD_FOLDER%/lib/mycode.tph"

instead do

LAF include STR_VAR file=mycode.tph location=lib END

(you can specify the location of the file in the usual SFO way). This automatically applies SFO's syntactic sugar (see below), which is the main reason to use 'include'; if for some reason you don't want it applied, you can set the INT_VAR 'literal' to 1. Note that because 'include' is a function, the scope of the included file is automatically restricted to the function call. (If you actually want to set variables in an included file to use elsewhere, use WEIDU's native functionality - but usually you should not do this.)

Instead of a single file, 'file' can be a space-separated list of files, in which case 'include' includes them all, in order. The same location/locbase/path applies to each.

run

'run' is a more powerful tool for including files. It specifically applies to so-called 'tpa' files - a tpa file is a file containing WEIDU code (possibly including SFO syntactic sugar) that contains a 'main function': an action function that has the same name as the file itself. So minsc.tpa should contain the action function 'minsc'. That action function should have no arguments (except possibly the STR_VAR 'version'; see below), and should return nothing. Any other functions defined in a tpa should be used only in the main function (though I can't enforce this requirement).

'run' loads the tpa file into memory, applying SFO syntactic sugar if appropriate (as with 'include', set literal=0 to skip this), and then runs the main function. The idea is that a tpa file contains a delineated part of your mod, and execution of a mod component is just the running of one or more tpa files.

An example use of run:

LAF run STR_VAR file=minsc location=npcs END

The location of 'minsc.tpa' is determined in the usual SFO (location/locbase/path) way.

As with 'include', 'file' can be a space-separated list of tpa files (all omitting the '.tpa' suffix).

2.6.2 Using 'run' to structure your mod

SFO's intended mod style is that each component is given by a tpa. (That tpa might call other components in turn.) For instance, Talent's of Faerun's 'Add subraces' component's entry in the main TP2 file, in its entirety, is

//////////////////////////////////////////////////////////////////////
/// New races and subraces
//////////////////////////////////////////////////////////////////////

BEGIN @200 DESIGNATED 20000 GROUP @2

REQUIRE_PREDICATE GAME_IS "bgee bg2ee eet iwdee" @50
FORBID_COMPONENT "setup-stratagems.tp2" 5900 @70

LAF run STR_VAR file=subrace location=subrace END

The component specification is the various require and forbid instructions, and then a single 'run' call - everything else is outsourced to the file 'subrace.tpa', which is found in dw_talents/subrace. (Note that since 'run' is a function, this automatically encapsulates the component: nothing in it can affect other components.

When 'run' is used this way, it has some extra features. Any time 'run' is used outside the scope of any other 'run' command (the idea is this happens only when you use 'run' in your tp2 file to call the whole component, though I can't enforce that), the following things happen:

  • If the variable 'sfo_marker_prefix' has been set (you would normally do this in your ALWAYS block), then a label file is placed in weidu_external/markers to indicate that the component is installed. The name of the label file is sfo_marker_prefix, followed by the name of the file called by 'run'. For instance, Talents of Faerun sets sfo_marker_prefix to 'dw-' in its ALWAYS block, so when the subrace component is installed, the file 'dw-subrace.mrk' is automatically created. Labels can be checked with check_label. If you don't want a marker created, set marker=0.
  • SFO tries to load a tra file, which it assumes lives in either %sfo_tra_loc%/%default_language% or %sfo_tra_loc%/%LANGUAGE% (with the latter taking precedence) - recall that these variables are set when SFO is first installed). By default, it assumes the tra variable has the same name as the main file (so when the subrace component is run, subrace.tra is loaded). You can override this using the 'tra' STR_VAR (if, for instance, one tra file is shared among several components). Tra files are loaded via WITH_TRA, so they are forgotten after the run command concludes.
  • The 'component_loc' variable is set to 'location', so that any further location checks are relative to that location.

2.6.3 Including syntactic sugar

There are two main ways to apply SFO's syntactic sugar. One is to include a file via the 'include' or 'run' functions, in which case it is automatically applied: what happens is that the file is copied over to weidu_external/workspace, a bunch of substitutions replace the syntactic sugar with legal WEIDU code, and then the file is reincluded. A consequence is that if your code has bugs, you may need to look at the copy in weidu_external/workspace to decipher WEIDU's error message. (In general debugging code can be more of a nuisance with lib_include, which is one of its downsides.)

The other way is to define an anonymous function: here you don't need to do anything special to use syntactic sugar, it's included automatically.

If for some reason you want to apply syntactic sugar directly, the patch function 'sugar_apply' will carry out substitutions on the current file.

2.6.4 Syntactic sugar

For this section, code fragments will be assumed to be included via lib_include or an anonymous function.

Quickly setting strings

You can set a string variable in action context like this:

myvar:=="Minsc is a ranger"

(This just abbreviates OUTER_SPRINT myvar "Minsc is a ranger".) In patch context, use ':=' instead of ':=='.

Simpler string comparisons

You can write "%string1%"=="%string2%" (and similar) as an abbreviation for "%string1%" STR_EQ "%string2%". Similarly, "%string1%"!=="%string2%" abbreviates "%string1%" STR_CMP "%string2%.

Begin/End

Instead of doing BEGIN … END, you can do [[… ]] in action context and {{ … }} in patch context. (This is a general feature of SFO syntactic sugar: [,] are used in action contexts, {,} in patch contexts.)

Control flow

You can do an ACTION_IF loop like this:

IF [condition] 
[[
	action
]]

and a PATCH_IF loop like this:

IF {condition}
{{
	action
}}

Context changes

[[[
	code
]]]

abbreviates

OUTER_PATCH "" BEGIN
	code
END

Similarly,

{{{
	code
}}}

abbreviates

INNER_ACTION BEGIN
	code
END

Defining arrays

You can define a new array by

array.new[my_array]
[[
	key1=>val1
	key2=>val2
	…
]]

which abbreviates

ACTION_CLEAR_ARRAY my_array
ACTION_DEFINE_ASSOCIATIVE_ARRAY my_array BEGIN
key1=>val1
	key2=>val2
	…
END

To add to an existing array (i.e. skip the ACTION_CLEAR_ARRAY), use array.add instead. To do it all in patch context, just use {{,}} instead of [[,]].

Getting ids file entries

You can get entries from the ids file like this:

var=race.int[DWARF]

abbreviates

var=IDS_OF_SYMBOL (race DWARF)

while

var=race.sym[3]

abbreviates

OUTER_PATCH "" BEGIN
	LOOKUP_IDS_SYMBOL_OF_INT var race 3
END

Again, just swap square brackets to curly brackets to work in patch context. (I'm mostly going to stop saying that from here on.)

Editing strrefs

This code reads in the string at the given strref, applies the code to it as a patch function, and resets the strref to the new string.

strref.patch[9501]
[
	REPLACE_TEXTUALLY "Minsc" "Minsc the Awesome"
]

Working with strings

You can patch a string like this:

myvar2=myvar.patch[REPLACE_TEXTUALLY "Minsc" "Minsc the Awesome"]

Alternatively you can write the string into dialog.tlk (after patching) and return the strref:

myvar_strref=myvar.strref[REPLACE_TEXTUALLY "Minsc" "Minsc the Awesome"]

You can also display the result of a patch:

myvar.print[REPLACE_TEXTUALLY "Minsc" "Minsc the Awesome"]

In each case, you can leave the [] empty if you want.

Function calls

A function with no arguments can be called like this:

run_this[]

which is equivalent to

LAF run_this END

If the function takes a STR_VAR argument 'arguments', you can set the argument like this:

run_this[cat]

which is equivalent to

LAF run_this STR_VAR arguments="cat" END

Other arguments can be included in this form:

run_this[cat| number_of_cats:i=4 size_of_cats="large"]

which abbreviates

LAF run_this INT_VAR number_of_cats=4 STR_VAR arguments="cat" size_of_cats="large" END

You can leave out the 'arguments' bit entirely, but you still need the |, as in

run_this[| number_of_cats:i=4 size_of_cats="large"]

which abbreviates

LAF run_this INT_VAR number_of_cats=4 STR_VAR size_of_cats="large" END

If the function returns 'value', you can do

out=run_this[cat]

which abbreviates

LAF run_this STR_VAR arguments=cat RET out=value END

(SFO has other syntactic sugar related to functions, but it is specific to the various struct functions used in lib_struct, which is discussed elsewhere.)