A course on SFO - chapter 1.3

Navigating the function libraries

Back to contents

This section explains how SFO's function library is organized and how its documentation works, along with some conventions that it is useful to know and a brief discussion of a few redefined WEIDU functions.

1.3.1 The SFO libraries

The main SFO function library lives in files with the .tph suffix in the main sfo folder. To first approximation, SFO uses these conventions:

  1. Functions all have lower-case names, like array_read or 2da_write. (This distinguishes them from WEIDU's built-in functions - and most other third-party functions - which tend to use the convention that functions have upper-case names like ALTER_EFFECT or HANDLE_CHARSETS.)
  2. Functions are organized in files ('libraries') with names like lib_array and lib_sto (functions with similar purposes are normally grouped together).
  3. Functions always begin with the name of their library - so functions in lib_array have names like array_read and array_write, for instance.
Inevitably there are a few exceptions.
  • A few libraries - lib_fn, lib_ietools, and lib_tools - contain very commonly used functions which intentionally diverge from the standard naming conventions.
  • A few libraries have unusual names, usually because they are self-contained libraries also used elsewhere; files in those libraries don't respect any particular naming convention.
  • For legacy reasons, occasional libraries diverge from the normal conventions: notably, lib_ui has the convention that all functions begin with uppercased UI (so UI_alter_function is a function in this library.

The SFO-LUA function library lives in files with the .tph suffix in the lua subfolder of the sfo folder. It has no special naming conventions beyond the convention that functions are lowercased (yes, this is a poor design choice; it's too late to change without doing more harm than good.)

1.3.2 SFO documentation

SFO and SFO-LUA functions are documented in files in sfo/doc/functions, all linked to index.html. If you go to that link, you'll find a few links to master lists of functions, and then an alphabetical list of all SFO libraries with a link to each library's documentation page, followed by a similar list for each SFO-LUA function.

The documentation page for a library starts by listing which libraries that library depends on (this might matter if you want to port fragments of an SFO library elsewhere.) It then lists the functions, in two groups: the public functions and the internal functions. The distinction is that internal functions are only used inside this library itself, and should not be used by other libraries or in your mod; only the public functions should be used. (In a more sophisticated language, this would be enforced and calls to internal functions would fail; WEIDU isn't that clever, so nothing actually stops you using an internal function. But I strongly recommend against it, and I make no promises about the stability of internal functions in future SFO updates.)

SFO describes functions in a standard, quite abbreviated, format. Here's an example:

resolve_sectype(strref:i="-1", sectype:s, arguments:s, string:s)=(sectype_value:s, value:s) dimorphic

The general shape of this is function_name(arguments)=(return values) function_type. Arguments and return values are labeled with their type: sectype:s means that the 'sectype' variable is a string, i.e. a STR_VAR, while strref:i means that the 'strref' variable is an integer, i.e. an INT_VAR. The default value for an integer variable is usually 0 and the default value for a string variable is usually ""; when other default values are used, they are listed explicitly. The function type is either action, patch, dimorphic, action-macro, or patch-macro. So, the resolve_sectype function looks like this in WEIDU:

DEFINE_DIMORPHIC_FUNCTION resolve_sectype
	INT_VAR strref="-1"
	STR_VAR 
		sectype=""
		arguments=""
		string=""
	RET 
		sectype_value
		value
BEGIN
...
END

SFO recognizes several other function argument types other than i and s: these include:

  • var:a means that var is either an array returned via RET_ARRAY (if it's a return value) or a STR_VAR that names an array (if it's an argument)
  • var:b means that var is an INT_VAR that must be either 0 or 1 (SFO doesn't always enforce this, but using values other than 0 and 1 will cause the function to behave oddly).
  • var:f means that var is a STR_VAR that names a function
  • var:[val1|val2|val3] means that var is a STR_VAR that should equal one of val1, val2, or val3 (again, SFO may or may not enforce this, but you should stick to it).

Of course, this is just an internal labeling convention: SFO can't change the fact that the only variables that WEIDU deals with are INT_VAR and STR_VAR.

SFO documentation is generated automatically (via the lib_funlib SFO library) using markup codes inside the .tph file. If you want to regenerate it for any reason, just do

LAF funlib_document_libraries END
The special variable labels (other than arrays returned via RET_ARRAY) have to be marked up manually, so they are sometimes erroneously listed as :s or :i for that reason.

1.3.3 SFO file-location conventions

Many SFO action functions use a standard system to specify the location of a file to be edited. For instance, the ini_read function reads a file in the ini format into an array, and is formatted like this:

ini_read(backwards:b, flat:b, file:s, section:s, path:s, location:s, locbase:s, case:[upper|lower])=(array:a, section_array:a) action

Here 'file' is the name of the file to be read in, and 'location', 'locbase' and 'path' specify where to find that file.

The formal rules for identification work like this:

  • if 'path' is set, just use it.
  • if not, and 'location' is set:
    • if 'locbase' is set, use '%MOD_FOLDER%/locbase/location'
    • if not, and the 'component_loc' variable is set, use '%MOD_FOLDER%/%component_loc%/location'
    • otherwise, use '%MOD_FOLDER%/location'
  • if not, and 'locbase' is set, use '%MOD_FOLDER%/locbase'
  • otherwise, just use "". (Though see below.)

To understand this system, it helps to understand that SFO is designed around the way I organize my mods. I organize my mod into one folder per component or group of components; within each folder, I put a bunch of sub-folders that contain particular sort of files. So in Sword Coast Stratagems, for instance, there is a folder 'spell' inside the main mod folder that contains all the spell tweak components. Within 'spell' there is a folder 'resource' that contains human-readable data files, a folder 'resource' that contains ITM, SPL files and the like, and so on. The first thing the 'spell' component does when run is set component_loc=spell. With that done:

  • A file located in 'spell/data' can be accessed just by setting location=data
  • A file located outside the component, for instance in 'shared/resource', would be accessed by setting locbase=shared (overriding 'spell'), location=resource
  • A file located outside the mod entirely, for instance in weidu_external/workspace, would be accessed by setting path=weidu_external/workspace

Another way of organizing mods is more popular: in that approach, you organize your mod by file type, so that the 'baf' folder contains all BAF files, the 'spl' folder contains all SPL files, and so on. If your mod is organized like that, you'll want to ignore the 'locbase' variable entirely: 'location' is used to point to any file in your main mod folder.

In very many (not all) functions, SFO uses one more convention: if 'path', 'location' and 'locbase' are all empty, it assumes that the file being edited is a game file, and uses COPY_EXISTING rather than COPY to edit it. For instance, this

LAF ini_read STR_VAR file="1205.ini" RET_ARRAY array END

tries to read the in-game file '1205.ini' (and complains if there is no such file). The convention shouldn't get in the way in most circumstances; the main exception is if you are using my organizational convention and for some reason you want to edit a file that's directly in your component's component_loc folder. If this happens, just work around it with locbase="%component_loc%".

Should you ever wish to get the file location encoded by 'location', 'locbase' and 'path' yourself (for instance, in your own function) then you can use the function 'sfo_path' to do so. For instance, this

LAF sfo_path STR_VAR location="resource" file="myscript.baf" RET_ARRAY file_path path END

if component_loc is set to 'spell', will return file_path='%MOD_FOLDER%/spell/resource/myscript.baf' and path='%MOD_FOLDER%/spell/resource'.

1.3.4 SFO standard functions

A number of SFO functions refer to an 'SFO standard function'. This is a function (nearly always a patch function) with the following format:

DEFINE_PATCH_FUNCTION myfunction
	STR_VAR arguments=""
	RET value
BEGIN
...
END

or, in SFO format,

myfunction (arguments:s) = (value:s) patch

That is, the function has exactly one argument, the STR_VAR 'arguments', and exactly one return value, just called 'value'. A standard function uses exactly these names for the argument and return value: a standard function isn't just one where there is one STR_VAR argument and one return value, it's one where the STR_VAR is specifically called 'arguments' and the return value is specifically called 'value'.

The SFO functions that use standard functions usually use them to apply the function to some file being edited. In many cases, you can define a standard function in-place via the 'anonymous function construct' (I discuss this in chapter 2.2).

1.3.5 Redefined WEIDU functions

As well as defining its own functions, SFO redefines a (very) few standard WEIDU functions. These are RES_NUM_OF_SPELL_NAME and NAME_NUM_OF_SPELL_RES (in the resolve_spell library), and ALTER_EFFECT, CLONE_EFFECT, and DELETE_EFFECT (in the alter_effect library).

The changes to RES_NUM_OF_SPELL_NAME and NAME_NUM_OF_SPELL_RES allow them to fail gracefully (rather than throw a runtime failure) if the spell cannot be resolved, and also to read spells from the so-called 'extended namespace' introduced by SFO-LUA. In any circumstance where the original version of the function would have executed without FAILing, the new function gives the same return values as the old function.

The changes to ALTER_EFFECT and friends mostly extend their functionality; I discuss the changes in chapter 2.2. In any circumstance where a call of the original version of the function would have executed correctly, the new function makes the same edits as the old function.