All SFO and SFO-LUA functions

Description

Public Functions

2da_clone_column(silent:b, array:a, clone_to:s, location:s, clone_from:s)=(array:a) dimorphic (lib_2da)

Given a 2d array, a column label 'clone_from', and a new column label 'clone_to', insert a copy of the clone_from column with the clone_to label, in the position specified by 'location'. 'location' can be 'first' (or 'start'), 'last' (or 'end'), 'before column_label' or 'after column_label'. If we can't find the column label, we default to 'last' (and whine about it unless silent=1).

2da_clone_row(silent:b, array:a, clone_to:s, location:s, clone_from:s)=(array:a) dimorphic (lib_2da)

Given a 2d array, a row label 'clone_from', and a new row label 'clone_to', insert a copy of the clone_from row with the clone_to label, in the position specified by 'location'. 'location' can be 'first' (or 'start'), 'last' (or 'end'), 'before row_label' or 'after row_label'. If we can't find the row label, we default to 'last' (and whine about it unless silent=1).

2da_column_to_array(silent:b, column:s, array:a)=(array_out:a) dimorphic (lib_2da)

Given a 2d array and column label 'column', extract that column as a 1d array indexed by the row labels.

If we can't find the column label, we return an empty array (and whine about it unless silent=1).

2da_delete_column(array:a, column:s)=(array:a) dimorphic (lib_2da)

Given a 2d array, and column label 'column', delete that column.

2da_delete_row(array:a, row:s, lookup_column:s)=(array:a) dimorphic (lib_2da)

Given a 2d array, and row label 'row', delete that row.

2da_extract_array(silent:b, domain:s, range:s, array:a, keymap:f, map:f, case:[upper|lower|mixed])=(array:a) dimorphic (lib_2da)

Given a 2d array, and column labels 'domain' and 'range' for that array, return a 1d array whose keys are the elements of the 'domain' column and whose values are the elements of the 'range' column.

If you leave either 'domain' or 'range' empty, the row headers are used instead. If we can't find domain or range, we return an empty array (and whine unless silent=1).

Optionally, you can specify functions 'keymap' and/or 'map', which are applied to the keys and values respectively before being put into the output array. You can use the anonymous function construct.

2da_find_default()=(value:s) patch (lib_2da)

In the current file, return the default 2da entry, or "NOT_2DA" if the file is not a 2da file.

2da_fix(apply_to_file:b=1)=(legal:s, fixed_file:s) patch (lib_2da)

Turns the current file (assumed to be a 2da file) into a legal 2da file with no incomplete entries, by

  1. forcing the first line to be '2DA V1.0'
  2. removing any entry beyond the first on the second line
  3. truncating any entries that lie beyond the range defined by the columns on the third line
  4. filling in any incomplete entries with the array's default character.
Returns 'legal', which is 1 or 0, and 'fixed_file', which is just a string containing the full contents of the file. Optional argument: 'apply_to_file' (can be 1 or 0, default is 1); if set to 1, the changes are actually carried out on the file being patched; if set to 0, they're discarded

2da_inject_array(silent:b, force_uppercase:b=1, array:a, array_in:a, column:s, row:s)=(array:a) dimorphic (lib_2da)

Given a 2d array, a column header of that array, and a k=>v array whose keys are row headers in the 2d array, inject the array elements into the 2d array, as (k=>v) goes to (k,col,v).

If force_uppercase=1 (default), array_in's keys are uppercased.

2da_insert_column(silent:b, array:a, column:s, location:s, entry:s="-1")=(array:a) dimorphic (lib_2da)

Given a 2d array, and a column label, insert a new column with that column label in the position specified by 'location'. 'location' can be 'first' (or 'start'), 'last' (or 'end'), 'before column_label' or 'after column_label'. If we can't find the column label, we default to 'last' (and whine about it unless silent=1).

The new rows are filled with 'entry'.

2da_insert_row(silent:b, array:a, row:s, location:s, entry:s="-1")=(array:a) dimorphic (lib_2da)

Given a 2d array, and a row label, insert a new row with that row label in the position specified by 'location'. 'location' can be 'first' (or 'start'), 'last' (or 'end'), 'before row_label' or 'after row_label'. If we can't find the row label, we default to 'last' (and whine about it unless silent=1).

The new rows are filled with 'entry'.

2da_make(rows:a, columns:a, fill:s="*")=(array:a) dimorphic (lib_2da)

Given two arrays in k=>_ format, make a 2da with each character filled with some fixed data

2da_prettier_print()=() patch (lib_2da)

Like PRETTY_PRINT_2DA, but checks the file has >2 rows and doesn't treat "2DA V1.0" as two columns

2da_process_table(inline:i, 2da:i, table:s, path:s, location:s, locbase:s, function:s)=() action (lib_2da)

'table' should be a header table whose headers are the arguments of the action function 'function'. (INT_VAR arguments should be indicated with ':i'.) Each row of the table is fed to the function.

Alternately, if INT_VAR 2da is set to 1, 'table' can be a 2da. It is treated the same way, except that each row name is fed to the function as the STR_VAR 'rowname'

2da_read(silent:b, reflect:b, remove_comments:b=1, type:[2da|ids|table_header|table_no_header], rowmap:f, colmap:f, rowname_column:s, case:[upper|lower|mixed])=(default:s, columns:a, rows:a, array:a) patch (lib_2da)
2da_read(silent:b, inline:b, reflect:b, file:s, case:[upper|lower|mixed], path:s, type:s, location:s, locbase:s, rowmap:f, colmap:f, rowname_column:s)=(default:s, value:s, columns:a, rows:a, array:a) action (lib_2da)

Read a 2da file (or, in patch context, the current 2da file) into a 2d array. Also return an array of uppercased row headers and column headers, in the format row_label=>row_number. ('case' controls the case of the row and column headers; it's uppercase by default on genuine 2das, mixed by default otherwise). The default value is also returned, as 'default'. (On non-2da files, * is returned)

If you don't specify a path for the 2da file, it's assumed to be a game file.

In action context, if the file doesn't exist return value=0; otherwise, return value=1. Also whine if it doesn't exist, unless silent=1.

If the file is a 2da, and 'reflect' is set, reverse rows and columns. If it's a 2da, and "rowname_column" is set, use that column (if it's present) for the row names instead of the usual entries. In the latter case, we add a new 'ROWNUMBER' column containing column zero. (This is not compatible with 'reflect'.)s

If 'rowmap' and/or 'colmap' are set, they get applied to the row and column entries before the array is constructed.

If the file contains WEIDU-style // comments, they are removed. (You can override this by setting remove_comments to 0.)

2da_renumber(start_at:i, array:a)=(array:a) dimorphic (lib_2da)

Given a 2d array, replace its row labels with sequential integers starting at 'start_at' (i.e. 0 by default)

2da_row_to_array(silent:b, row:s, array:a)=(array_out:a) dimorphic (lib_2da)

Given a 2d array and row label 'row', extract that row as a 1d array indexed by the column labels.

If we can't find the row label, we return an empty array (and whine about it unless silent=1).

2da_sort(rows:s, columns:s, array:s)=(array:a) dimorphic (lib_2da)

Takes a 2da, sorts rows and/or columns. Set "rows" and/or "columns" to "lexicographically" or "numerically" (case-insensitive), or else feed it a function to use in array_sort. Can use 'l' or 'n' as synonyms

2da_to_3da(column1:s, column2:s, array:a)=(array_out:a) dimorphic (lib_2da)

Given a 2d array and two column headers, extract a 3d array where the first two keys are the values in the new columns and the third is the old column header.

e.g. if row 14 has col1=x, col2=y, then 3da(x,y,z)=2da(14,z).

2da_to_ini(2da:s, ini:s, path:s, location:s, locbase:s, ini_path:s, default:s="*", ignores:s, section_key:s)=() action (lib_2da)

2da_write(reflect:b, silent:i, number_rows:b, array:a, type:[2da|ids|table_header|table_noheader], default:s, case:[m="mixed"ixed|upper|lower])=() patch (lib_2da)
2da_write(number_rows:b, reflect:b, file:s, path:s, location:s, locbase:s, type:[2da|ids|table_header|table_noheader], array:a, default:s)=() action (lib_2da)

Write a 2d array into a 2da file (or, in patch context, the current 2da file).

If you don't specify a path for the 2da file, it's assumed to be a game file.

If 'number_rows' is set to 1, the row names are replaced by integers, counting upwards from 0. If 'reflect' is set to 1, rows and columns are swapped.

If you don't specify the default element, we try to read it from the current file

2daq_copy_column(data_read:b, column:s, column_new:s="%column%")=(value:s) patch (lib_2daq)

Copies a column in the current 2da file to the right-hand side of the table, with new column name 'column_new'. Returns 1 if copy is successful. Stoically silent if it isn't.

2daq_copy_row(rowname_column:i, data_read:b, row:s, row_new:s="%row%")=(value:s) patch (lib_2daq)

Copies a row in the current 2da file to the bottom of the table, with new row name 'row_new'. Returns 1 if copy is successful. Stoically silent if it isn't.

If 'rowname_column' is set, the row id is looked up in that column, and column 0 is assumed to be numbered sequentially. (This is the convention for kitlist.2da.)

2daq_extract(rowname_column:i, data_read:i, row:s, column:s, case:[m=mixedixed|upper|lower])=(value:s, rownum:s, array:a) patch (lib_2daq)
2daq_extract(rowname_column:i, data_read:b, resref:s, row:s, column:s, case:s)=(value:s, array:a) action (lib_2daq)

Given a column header and/or a row header, and a 2da file resref, (or, in patch context, the current 2da file), extract the column or row as k=>v array using the row or column headers, as appropriate, as keys.

If both row and column are set, instead return the element (if any) at (row,column) as 'value', as well as its row as 'rownum'.

2daq_has_column(data_read:b, column:s)=(value:s) patch (lib_2daq)

Return 1 iff the current 2da file has 'column' as a column id.

2daq_has_row(rowname_column:i, data_read:b, row:s)=(value:s) patch (lib_2daq)

Return 1 iff the current 2da file has 'row' as a row id. (Check rowname_column).

2daq_inject(reflect:b, rowname_column:i, data_read:b, array:a)=() patch (lib_2daq)
2daq_inject(reflect:b, rowname_column:i, data_read:b, array:a, resref:s)=() action (lib_2daq)

Given a 2D struct 'array' and a 2da file resref, (or, in patch context, the current 2da file) insert the elements of the struct into the 2da. (i.e. if the struct contains "array_x_y" and x and y are row and column entries in the 2da, insert the value of array_x_y at (x,y).

If reflect=1, swap rows and columns. If rowname_column is nonzero, look up the row names in that column.

2daq_insert_column(column:s, insert_loc:[l="last"ast|before_last])=() patch (lib_2daq)

Adds a new column, with header 'column', as the last column, or the second-to-last column if insert_loc is 'before_last'. All entries are filled with the 2da default value.

2daq_insert_row(rowname_column:i, row:s)=() patch (lib_2daq)

Adds a new row, with header 'row', as the last row. All entries are filled with the 2da default value. If rowname_column>0, we put the header in this column, and assume column 0 should be numbered sequentially from 0.

3p_kit()=() action (lib_3p)

Goes through the array 'sfo_3p_kit', and for each cre=>kit assigns the kit 'kit' (from kit.ids), if it exists, to the cre file 'cre'.cre

The kit is not enforced but can be picked up by other mods (e.g. SCS) and will apply normally to joinables.

3p_kit_bonus_spells()=() action (lib_3p)

Goes through the array 'sfo_3p_kit_spheres', and for each kit=>spheres assigns those spheres to the kit. Input can be either, (i) a comma-separated list of spheres (ii) a comma-separated list of spheres prepended by + or -.

In case (ii), the list is applied as modifiers to the class default.

3p_kit_sphere()=() action (lib_3p)

Goes through the array 'sfo_3p_kit', and for each (kit,'spheres')=>spheres assigns those spheres to the kit. Input can be either, (i) a comma-separated list of spheres (ii) a comma-separated list of spheres prepended by + or -.

In case (ii), the list is applied as modifiers to the class default.

3p_load_data action_macro (lib_3p)

Loads all the inis in the folders 'MOD_FOLDER/%sfo_3p_folder_internal%' (by default 'MOD_FOLDER/3p') and 'weidu_external/data/dw_shared/%sfo_3p_folder%' (by default weidu_external/data/dw_shared/MOD_FOLDER3p), with the latter overriding the former. Returns them as a series of arrays, labelled by ini sections - so all the entries in [sphere], across all the inis, get returned in $sfo_3p_sphere. If the key has the form firstbit:secondbit, instead we return a 2D array using secondbit as a key: so entries in [kit:dw_cleric_mask] would get returned as values of $sfo_3p_kit("dw_cleric_mask" [whatever])

add_basic_spell_ability()=() patch (lib_ietool)

Add a standard (innate) ability to the currently-being-patched spell, assumed to be a blank freshly-created v1 spell from WEIDU's CREATE command. (For lightweight spell-creation.)

add_dual_class_kit_option(kit:s, oldclass:s, oldkit:s, newclass:s)=() action (ui_dual_class_kits)

Enable dual-classing from kit 'oldkit' of old class 'oldclass', into kit 'kit' of class 'newclass'. Class entries should be IDS entries like 'FIGHTER'. Kit entries should be kitlist.2da rownames.

add_elem_spell_desc(elem_string:s, spell:s)=() action (ui_spell_system_elemental)

add_ini_func()=() patch (ui_set_ini_globals)

add_kit_menu(name:i, desc:i, title:i, trueclass_desc:i, name_strref:i, desc_strref:i, title_strref:i, trueclass_desc_strref:i, id:s, kits:s, class:s)=() action (ui_virtual_class)

This creates a kit-selection menu for a given class. All kits in the space-separated list 'kits' (identified by kitlist.2da rownames) for the class 'class' (identified by a class.ids entry) are grouped into their own menu. 'id' is some unique identifier for this menu (use a modder prefix).

'name', 'desc' and 'title' should be the numbers of entries in the currently-loaded tra file. 'name' is the name of the menu itself (displayed in place of a kit name on the parent class's kit menu). 'title' is the name for the submenu (replacing 'Choose Kit'). 'desc' is the description displayed when you click or mouse over the submenu on the main kit-selection screen.

For instance, if you want to gather several dragon disciple kits together, 'name' might be 'Dragon Disciple', 'desc' might be the general description for Dragon Disciples, and 'title' might be 'Choose Dragon' or similar.

add_pseudorace(NAME:s="-1", DESC:s="-1", REAL_RACE:s="-1")=() action (ui_add_subraces)

Add a single race to SFO's master table of pseudoraces. (This only updates the tables; the game itself is not updated.)

add_race(race:s, NAME:s, DESCSTR:s, UPPERCASE:s, BIOGRAPHY:s)=() action (ui_add_subraces)

Add a single race to SFO's master table of races. (This only updates the tables; the game itself is not updated.)

add_subrace(COLOR_SKIN:i="-1", COLOR_HAIR:i="-1", COLOR_MAJOR:i="-1", COLOR_MINOR:i="-1", STR:i, DEX:i, CON:i, INT:i, WIS:i, CHA:i, EXTRA_PROF:i, PP:i, OL:i, FT:i, MS:i, HS:i, DI:i, ST:i, subrace:s, NAME:s, DESC:s, PARENT:s, FINAL_RACE:s="*", ICON:s="-1", ICON_STRREF:s="-1", BIO_OLD:s="-1", BIO_NEW:s="-1", SPL_INIT:s, SPL_RECUR:s)=() action (ui_add_subraces)

Add a single subrace to SFO's master table of subraces. (This only updates the tables; the game itself is not updated.)

add_to_clab_level_1(clab:s, spell:s)=() dimorphic (ui_extra_spells)

Lightweight adder that just adds the spell to a CLAB file

add_to_lua_lang(array:a)=() dimorphic (lib_ietool)

Given a k=>v array 'array' with entries like MY_TEXT_STRING=>1234, go through each entry, and for each, get the string with tra ref 1234 in the current tra file and set TEXT_STRING equal to it in the current EE language file (e.g., en_us.lua).

Exits with a warning if not on EE.

add_to_subrace_tables(subrace_table:s, race_table:s, pseudorace_table:s, disabled:s, table_path:s)=() action (ui_add_subraces)

Take a bunch of tables defining race, subrace and pseudorace and add them to SFO's master tables. (This only updates the tables; the game itself is not updated.)

add_virtual_class(multiclass:b, name:i, desc:i, name_strref:i, desc_strref:i, ident:s, parent:s, human:s, dwarf:s, elf:s, gnome:s, halfling:s, half_elf:s, halforc:s, all_races:s="true")=() action (ui_virtual_class)

This adds a new virtual class to the class-selection menu (to the single-class bit unless multiclass is set to 1). You need to specify a unique string 'ident' to identify the class, and a real class ('parent', an entry in class.ids) as its parent.

You also need a name and description, specifiable either as a tra ref ('name', 'desc') or directly as a strref ('name_strref', 'desc_strref').

By default, the class will be available to all races. To restrict it, set 'all_races' to 'false' and then set each of 'human', 'dwarf' etc to 'true' if you want it available.

all_to_utf8(permanent:b, tra_path:s="MOD_FOLDER/lang")=() action (lib_ietool)

Given the (full) path to your tra files (default: MOD_FOLDER/lang), convert all the files in all languages to UTF-8 (with lowercased filenames), leaving no detritus (no fl#utf files). If permanent=1, will persist through uninstall.

ALTER_EFFECT(check_globals:i=1, check_headers:i=1, header:i="-1", header_type:i="-1", multi_match:i=999, verbose:i, silent:i, match_opcode:i="-1", match_target:i="-1", match_power:i="-1", match_parameter1:i="-1", match_parameter2:i="-1", match_timing:i="-1", match_resist_dispel:i="-1", match_duration:i="-1", match_duration_high:i="-1", match_probability1:i="-1", match_probability2:i="-1", match_dicenumber:i="-1", match_dicesize:i="-1", match_savingthrow:i="-1", match_savebonus:i="-11", match_special:i="-1", match_save_vs_spell:i="-1", match_save_vs_breath:i="-1", match_save_vs_poison:i="-1", match_save_vs_wand:i="-1", match_save_vs_polymorph:i="-1", match_ignore_primary:i="-1", match_ignore_secondary:i="-1", match_bypass_mirror_image:i="-1", match_ignore_difficulty:i="-1", match_drain_hp_to_caster:i="-1", match_transfer_hp_to_target:i="-1", match_fist_damage_only:i="-1", match_drain_to_max_hp:i="-1", match_suppress_feedback:i="-1", match_save_for_half:i="-1", match_made_save:i="-1", match_does_not_wake:i="-1", opcode:i="-1", target:i="-1", power:i="-1", parameter1:i="-11", parameter2:i="-11", timing:i="-1", resist_dispel:i="-1", duration:i="-1", duration_high:i="-1", probability1:i="-1", probability2:i="-1", dicenumber:i="-1", dicesize:i="-1", savingthrow:i="-1", savebonus:i="-11", special:i="-1", save_vs_spell:i="-1", save_vs_breath:i="-1", save_vs_poison:i="-1", save_vs_wand:i="-1", save_vs_polymorph:i="-1", ignore_primary:i="-1", ignore_secondary:i="-1", bypass_mirror_image:i="-1", ignore_difficulty:i="-1", drain_hp_to_caster:i="-1", transfer_hp_to_target:i="-1", fist_damage_only:i="-1", drain_to_max_hp:i="-1", suppress_feedback:i="-1", save_for_half:i="-1", made_save:i="-1", does_not_wake:i="-1", match_resource:s="SAME", resource:s="SAME", match_function:s, function:s)=() patch (alter_effect)

Edited version of Cam's ALTER_EFFECT (shipped with WEIDU) that allows for finer control on the savingthrow and special fields, and also to check the value of a function ('match_function') and apply one ('function'). Functions can be anonymous.

anim_collect(no_log_record:b, animation:s, resource_loc:s, ini_file:s, path:s, location:s, locbase:s, resref_original:s)=() action (lib_anim)

Collects the resources used by an animation (i.e. the bam and wav files), according to its EE ini file. Files are saved in 'resource_loc'/'animation'.

The animation is specified by an EE-format animate.ids entry. (We check a stashed copy of animate.ids in %sfo_library_file%/resource).

You can supply a local copy of the ini file, using path/location/locbase. If ini_file is not set, we assume the one inferred from animate.ids.

You can override the ini-specified resref with 'resref_original'.

If no_log_record=1, the resources are copied over irreversibly and will not be undone if you uninstall.

anim_install(overwrite:i, resource_loc:s, animation:s)=() action (lib_anim)

Given an animation packaged by anim_collect, install it. If overwrite=1, overwrite existing files; otherwise, don't.

anon_check(has_output:i, function:s, input:s=arguments, output:s=value, prepend:s, append:s)=(function:s, SFO_anon_func_count:s) dimorphic (lib_anon)

Check if 'function' is an anonymous function definition, and if so create it and return its name; if not, return 'function' itself.

A string is an anonymous function definition if it contains any of these: [ <>/=+*{}]. (If you somehow manage to define an anonymous function that doesn't use any of these, just add a space at the beginning.)

anon_define(has_output:b, action_function:b, return_array:b, function:s, input:s=arguments, output:s=value, prepend:s, append:s)=(function:s, SFO_anon_func_count:s) dimorphic (lib_anon)

Define the string 'function' as an anonymous patch function (setting its single STR_VAR input to 'input' and its single output to 'output'), and return the name of the function and an integer keeping track of how many anonymous functions have been defined.

The point of the integer is to avoid namespace collisions. By leaving it in scope for other calls of anon_define, we guarantee distinct functions.

We allow the following syntactic sugar for anon_define:

- '__' evaluates to '%input%' - 'SFO_args' evaluates to 'input' - '{' and '

anon_eval(has_output:i, arguments:s, function:s, append:s, prepend:s)=(value:s) action (lib_anon)
anon_eval(has_output:i, arguments:s, function:s, append:s, prepend:s)=(value:s) patch (lib_anon)

Evaluate 'function' as an anonymous patch function, apply it to 'arguments', and return the result. NB: anon_eval is not dimorphic. The action version runs on a blank patch. The patch version runs on whatever is currently being patched.

append(arguments:s)=() patch (lib_tools)
append(no_log:i, arguments:s, file:s, location:s, locbase:s, path:s)=() action (lib_tools)

Append the string 'arguments' at the end of the file 'file' at locations given by location/locbase/path (only use 'path' if you want to run self-contained), or, in patch context, at the end of the current file, with no spacing or line breaks

are_copy(allow_missing:i, debug:i=1, are:s, source_path:s, source_location:s, source_locbase:s, path:s, location:s, locbase:s, edits:s)=() action (lib_are)

General copier for are files. No special features.

are_edit(allow_missing:i, debug:i=1, edit_strrefs_in_place:i, are:s, path:s, location:s, locbase:s, edits:s)=() action (lib_are)

General editor for are files. No special features.

are_make(debug:i=1, are:s, version:s, path:s, location:s, locbase:s, edits:s)=() action (lib_are)

General maker for are files. No special features.

array_2d_to_list(sort:b=1, array:a, separator:s=" ", require_value:s)=(list:a) dimorphic (lib_array)

Take a 2d array (k1,k2)->v. Return a 1d array with the k1 as keys and a list of the k2 as values. Optionally, require that v=require_value. If sort=1, sort entries lexicographically.

array_contains(array:a, key:s, val:s)=(value:s) dimorphic (lib_array)

Depending on which of 'key' and 'val' are set, return true if (i) array 'array' contains 'key' as a key; (ii) array 'array' contains 'val' as a value; (iii) array 'array' contains the pair 'key'=>'val', or I suppose (iv) array 'array' is non-empty.

This is intentionally case-sensitive (since a common use-case is going to be pulling an array element out by its key if it's in the array).

array_copy(array:a)=(array:a) dimorphic (lib_array)

Copy an array.

array_echo(single_line:i, array:a)=() dimorphic (lib_array)

Print an array to the screen (good for debugging). If single_line=1, returns a single string with comma-separated entries; otherwise returns one k=>v per line.

array_fill(array:a, fill:s="*")=(array:a) dimorphic (lib_array)

Given an array of keys, set each to the value 'fill'.

array_from_string(string:s)=(array:a) dimorphic (lib_array)

Take a string of k=>v pairs separated by spaces. Read them into an array.

array_invert(array:a)=(array:a) dimorphic (lib_array)

Input an array of k->v; output an array of v->k.

array_join(array1:a, array2:a)=(array:a) dimorphic (lib_array)

Take two arrays and combine them, so that key k is in the new array iff it is in one or other of the old arrays. If there's a clash of key allocations, array2 gets priority.

array_keys(silent:i, array:a)=(is_empty:s, keys1:a, keys2:a, keys3:a, keys4:a, keys5:a) dimorphic (lib_array)

Given an array, return arrays k=>_ of the first five levels of its keys.

WEIDU limitations mean that if the array is empty, the return value is unpredictable and may equal an already-defined array. Empty arrays lead to a WARNing, which you can suppress with silent=1. In applications where the array might be empty, make sure you explicitly CLEAR_ARRAY it before calling array_keys.

array_keys_from_string(separator:s=" ", string:s)=(array:a) dimorphic (lib_array)

Take a string of strings separated by 'separator' (a character). Read them in as the keys of an array. (Values are blank.)

array_length(array:a)=(value:s, length:s) dimorphic (lib_array)

Returns the number of elements in the array. ('value' and 'length' are synonyms.)

array_map(array:a, map:f, keymap:f)=(array:a) dimorphic (lib_array)

Take a k->v array is input; return keymap(k)->map(v). (Either function can be absent, in which case it's treated as the identity.) You can use the anonymous function construct.

array_read(firstrow:i, backwards:b, case:[u=mixedpper|lower|mixed])=(array:a) patch (lib_array)
array_read(firstrow:i, silent:i, backwards:b, inline:b, file:s, path:s, location:s, locbase:s, case:[u=mixedpper|lower|mixed])=(value:s, array:a) action (lib_array)

Take a file (or, for the patch version, the current file), which should be a table, (not necessarily a 2da) and read in the first two columns into a k->v array. (If there's only one column, read it into a k->_ array.)

Start at row firstrow.

In action context, return value=1 if the file exists, value=0 if it doesn't, and whine if it doesn't unless silent=1.

If backwards=1, swap the order of key and value.

array_sort(depth:i=1, array:a, function:f)=(array:a) dimorphic (lib_array)

Given a patch function (of argument->value type) with domain the keys of an array, sort that array alphabetically by the values of the function. The function doesn't have to be 1:1. You can use the anonymous function construct.

array_split(array:a, match_key:f, match_value:f)=(split:a, rest:a) dimorphic (lib_array)

Take a k->v array as input, along with functions 'match_key' and/or 'match_value'; return an array 'split' of all elements with keys that match 'kmatch' and values that match 'vmatch', and an array 'match' of all that don't. You can use the anonymous function construct.

array_values_from_string(quick:i, string:s, separator:s=" ")=(array_length:s, array:a) dimorphic (lib_array)

Given a string of strings separated by 'separator' (a character), return an [int]=>v array of those strings, labelled by sequential integers. If 'quick' is set, assume no entries are quoted. 'quick' only works for a ' ' or tab separator.

array_write(new:b, permanent:b, path:s="weidu_external/data/MOD_FOLDER", file:s, array:a)=() dimorphic (lib_array)

Output an array as a 2-column table. If 'new' is set, overwrite any existing file by the same name; if not, append. If 'permanent' is set, the table will persist even if the component is uninstalled. (This automatically sets 'new'.)

assess_main_state_do_commands(dialog_main_state:i, dialog_main:s)=(needs_passback:s, do_list:s) patch (lib_interject)

assign_bonus_spells(update_description:b=1, skip_clab:b, class:s, kit:s, spells:s, spell_array:s, tra:s="sfo_lua", tra_path:s="DEFAULT")=() action (ui_bonus_spells)

Given kit 'kit, and a space-separated list of spells 'spells' and/or a k=>_ array of spells 'spell_array', grant those spells as bonus learned spells at the appropriate level, making appropriate LUA and CLAB changes to do so.

Spells can be given as bare resrefs, or as spell.ids entries, or as dw_ext_spell.ids entries, or as abbreviated entries (i.e., with the 'WIZARD_' or 'CLERIC_' preamble stripped (if ambiguous, we assume WIZARD). Spells of the wrong type (i.e. cleric spells for wizards, or vice versa) are autoconverted to the appropriate type using the lib_splconv library.

Multiclass kits can be handled, but only if they're in SFO format and only on a full sfo install (we use lib_kit functionality). The parent class can normally be inferred; if you are adding bonus spells to a cleric/mage, you need to specify 'class' explicitly as cleric or mage.

The function will attempt to update the kit description (you can tell it not to by setting update_description=0). It needs to be given a tra file and a tra path; the defaults point to lua/lang and to sfo-lua's default tra file. (Look at that file for format if you want to do your own.) The same tra file is also passed to the splconv function to make any cleric->wizard and wizard->cleric conversion descriptions.

If you set skip_clab to 1, the new spells are added to the LUA and the kit description, but not to the CLAB. (This is useful if you're cloning an existing kit.)

assign_kit_to_virtual_class(kit:s, class:s)=() action (ui_virtual_class)

The kit 'kit' (a rowname from kitlist.2da) is added to the virtual class 'class' (the unique ID for the class, specified in add_virtual_class). The kit needs to be a legal kit for the parent class of the virtual class, and will now be displayed in the virtual class's kit menu rather than the real class's menu.

assign_spheres(update_scroll_usability:i=1, update_scrolls_later:i=1, update_description:i=1, class:s, kit:s, kit_clastext:s, spheres:s, default:s, add:s, subtract:s, block:s, base_tra:s=sfo_lua, base_tra_path:s="DEFAULT")=() action (ui_spell_system_spheres)

Allocate spheres to a given kit or class.

bam_get_available_mos_index(current:i)=(value:s) action (lib_bam)

Find the first unused filename of form mosxxxx.pvrz (where xxxx is an integer padded to 4 digits), starting with mos0000.pvrz.

This function is exposed (not internal) because it's used in some lib_mos functions.

bam_install_v2(lowest_mos_index:i, bam_name:s, path:s, location:s, locbase:s, pvrz_path:s="MOD_FOLDER/sfo", pvrz_location:s="%location%", pvrz_locbase:s="%locbase%")=() action (lib_bam)

Installer for v2 BAMs. Feed it the lowest mos index you want to try, the name and location of the bam file to be installed (given by path/location/locbase), and the location of all the pvrz files associated with the bam (given by pvrz_path,pvrz_location,pvrz_locbase, but defaulting to the bam version in each case)

bam_pad_to_four(arguments:i)=(value:s) action (lib_bam)

Given an integer argument<10,000, return a 4-digit string of that number, padded out with zeroes at the start if necessary.

This function is exposed (not internal) because it's used in some lib_mos functions.

bam_patch_colors(suppress_warning:b, condition:f, action:f, bam:s)=() action (lib_bam)
bam_patch_colors(condition:f, action:f)=() patch (lib_bam)

palette editor for BAMs. "condition" should either be blank, or a function that takes as INT_VAR inputs "green", "red" and "blue" and returns "value", which should be 1 (if the color should be patched) or 0 (if not) "action" should be a function which takes "green", "red", "blue" as inputs and then outputs "green", "red" and "blue"

The action-context version patches an in-game bam. The patch-context version patches the current bam.

bonus_spells_from_list(skip_clab:b, update_description:i=1, kit:s, path:s, class:s)=() action (ui_bonus_spells)

Loads the file in 'path' (which should be a 2-column table - the first column is conventionally a level, but need not be). Go through the entries in the second column. Each is a |-separated list of spell ids entries (possibly including extended-namespace ids). Assign the first entry in each column that actually exists as an installed spell as a bonus spell to the kit 'kit'.

build_subraces(tlk_signal_start:i=900000, delay_mode:b)=() action (ui_add_subraces)

Operate on the subrace tables to actually construct the subraces if delay_mode=1, introduce a 1-second delay into the spells (for experimental HoW compatibility, though we haven't got this stable yet).

charset_wrapper(from_utf8:b=1, overwrite:b, verbose:b, silent:b, tra_path:s, iconv_path:s, setup_tra:s="setup", load:s, default_language:s="english", extra_tra_folders:s)=(out_path:s) action (charset_wrapper)

Wrapper for HANDLE_CHARSETS. Copies files over to weidu_external/lang/MOD_FOLDER, whether or not conversion is required. Makes sure all tra are present by using default-language ones if preferred-language ones aren't available.

Inputs:

  • from_UTF8 (default=1): set to 0 if your tra files aren't in UTF8
  • overwrite (default=0): set to 1 if you want to regenerate the converted files every run
  • verbose (default=0): set to 1 to get more feedback from HANDLE_CHARSETS
  • silent (default=0): set to 1 if you don't want to be warned when tra files from a folder in the extra_tra_folders list overlap with the main list
  • tra_path (default=""): set to where your tra files are (with or without 'MOD_FOLDER'), if they're not in MOD_FOLDER/tra or MOD_FOLDER/lang or MOD_FOLDER/languages
  • iconv_path (default=""): set to wherever your iconv.exe file is (with or without 'MOD_FOLDER'), if it's not in MOD_FOLDER/%tra_path%/iconv
  • setup_tra (default="setup"): set to whatever you're keeping your WEIDU installation strings in
  • load (default=""): set to a space-separated list of any tra files you want loaded automatically
  • default_language (default="english"): set to whatever language you wrote the mod in.
  • extra_tra_folder (default=""): set to a space-separated list of any additional folders (relative to MOD_FOLDER) containing TRA files.

The returned variable out_path is where the language files are copied to (hardcoded to weidu_external/lang/MOD_FOLDER).

check_ini(silent:i, ini:s, arguments:s, section:s)=(value:s) dimorphic (lib_ini)

Legacy name for ini_check.

check_label(label:s, prefix:s)=(value:s) dimorphic (lib_sfo)

Check for a marker file, which can be set by make_label.

Do not use make_label or check_label in your base tp2 to decide whether to install a component, as they are invisible to Project Infinity and the like.

class_edit(edit_strrefs_in_place:i, class:s, edits:s, struct:s=k)=() action (lib_class)

Edit the class 'class' (or the list of classes 'class'). 'edits' is executed as an anonymous function, which first reads the class into struct 'struct' (default 'k') and then writes that struct back. ('k' so that the same patch can be used for kits and classes.)

If 'edit_strrefs_in_place' is set to 1, any strrefs have their values updated, rather than being replaced with new strrefs (use this with caution).

(Arguably this is just an internal function: you can put unkitted classes into kit_edit and it just calls class_edit.)

class_read(class:s)=(struct:a) dimorphic (lib_class)

Read a class into a struct. (A wrapper for vtable_read, with a bit of extra stuff to handle editing clabs.)

class_write(edit_strrefs_in_place:b, class:s, struct:s)=() dimorphic (lib_class)

Write the contents of a struct into class 'class'. This includes setting the clab file and the race tables (which are controlled by the 'any_race' and 'human'/'dwarf' (etc) struct variables. If 'edit_strrefs_in_place' is set to 1, any strrefs have their values updated, rather than being replaced with new strrefs (use this with caution).

Note that (unlike for lib_struct functions) 'struct' needs to be an array, not just a set of values. (So intermediate functions need to return the whole array.)

CLONE_EFFECT(check_globals:i=1, check_headers:i=1, header:i="-1", header_type:i="-1", multi_match:i=999, verbose:i, silent:i, match_opcode:i="-1", match_target:i="-1", match_power:i="-1", match_parameter1:i="-1", match_parameter2:i="-1", match_timing:i="-1", match_resist_dispel:i="-1", match_duration:i="-1", match_duration_high:i="-1", match_probability1:i="-1", match_probability2:i="-1", match_dicenumber:i="-1", match_dicesize:i="-1", match_savingthrow:i="-1", match_savebonus:i="-11", match_special:i="-1", match_save_vs_spell:i="-1", match_save_vs_breath:i="-1", match_save_vs_poison:i="-1", match_save_vs_wand:i="-1", match_save_vs_polymorph:i="-1", match_ignore_primary:i="-1", match_ignore_secondary:i="-1", match_bypass_mirror_image:i="-1", match_ignore_difficulty:i="-1", match_drain_hp_to_caster:i="-1", match_transfer_hp_to_target:i="-1", match_fist_damage_only:i="-1", match_drain_to_max_hp:i="-1", match_suppress_feedback:i="-1", match_save_for_half:i="-1", match_made_save:i="-1", match_does_not_wake:i="-1", opcode:i="-1", target:i="-1", power:i="-1", parameter1:i="-11", parameter2:i="-11", timing:i="-1", resist_dispel:i="-1", duration:i="-1", duration_high:i="-1", probability1:i="-1", probability2:i="-1", dicenumber:i="-1", dicesize:i="-1", savingthrow:i="-1", savebonus:i="-11", special:i="-1", save_vs_spell:i="-1", save_vs_breath:i="-1", save_vs_poison:i="-1", save_vs_wand:i="-1", save_vs_polymorph:i="-1", ignore_primary:i="-1", ignore_secondary:i="-1", bypass_mirror_image:i="-1", ignore_difficulty:i="-1", drain_hp_to_caster:i="-1", transfer_hp_to_target:i="-1", fist_damage_only:i="-1", drain_to_max_hp:i="-1", suppress_feedback:i="-1", save_for_half:i="-1", made_save:i="-1", does_not_wake:i="-1", match_resource:s="SAME", resource:s="SAME", insert:s="above", match_function:f, function:f)=() patch (alter_effect)

Edited version of Cam's CLONE_EFFECT (shipped with WEIDU) that allows for finer control on the savingthrow and special fields, and also to check the value of a function ('match_function') and apply one ('function'). Functions can be anonymous.

collect_this_animation(animation:s, resref_orig:s, ids_file:s, deposit_base:s="MOD_FOLDER/anims")=() action (lib_anim)

color_finder_tool()=() action (ui_add_portraits)

NOT FOR LIVE USE - alter the UI so that the CHARGEN color customization screen displays the numerical values of the chosen colors

compile_with_ict_handling(dialog:s)=() action (lib_interject)

copy_item_to_spell(abil_ind:i, item:s, spell:s)=() action (lib_ietool)

Take ability 'abil_ind' (default=0) from item 'item'. Build a new spell, 'spell', that casts that ability as its only ability.

cre_add_items(replace:i, struct:s, arguments:s, default:s)=(struct:a) dimorphic (lib_cre)

(This is a struct function.)

Add a list of items. Each can have, in parentheses, a list of comma-separated instructions, which can be either:

  • 'unequipped', in which case the item isn't equipped even if it's a weapon. (By default, it is.)
  • 'undroppable',unstealable', 'identified', 'stolen', in which case the appropriate flag is set
  • An integer, interpreted successively as the first, second, third charge number
  • A slot. (If no slots are specified, we choose on the basis of the item type.)
You can set 'default' and it will be added to the instructions for each item.

If 'replace' is set to 1 (default 0) then the item in the slot will be replaced; if not it will be bumped.

cre_add_spells(known:b=1, memorized:b=1, struct:s, arguments:s)=(struct:a) dimorphic (lib_cre)

(This is a struct function.)

Add a list of spells, by default to both the 'known' and 'memorized' lists (set 'known' or 'memorized' to 0 to override this). You can use the ids name or the resref, and you can omit 'wizard_' or 'cleric_' where that's unambiguous. Adding (n) after the spell adds it n times (to the memorized list only).

cre_copy(tv:i, allow_missing:i, debug:i=1, cre:s, source_path:s, source_location:s, source_locbase:s, path:s, location:s, locbase:s, edits:s)=() action (lib_cre)

General copier for eff files. No special features.

cre_delete_original_class()=() patch (lib_cre)

NOT a struct function. Remove the 'original class' flag from the currently-edited creature.

cre_delete_spells_of_type(core_namespace_only:i, known:i=1, memorized:i=1, arguments:s)=() patch (lib_cre)

NOT a struct function. Deletes all spells of given type(s) in the CRE file. 'arguments' should be a space-separated (case-insensitive) list containing one or more of 'wizard', 'priest', or 'innate'

If core_namespace_only=1, require spells to be in the SPPR/SPWI/SPIN/SPCL namespace.

By default we delete both known and memorized spells. Set known=0 or memorized=0 to override.

cre_edit(tv:i, allow_missing:i, debug:i=1, edit_strrefs_in_place:i, cre:s, path:s, location:s, locbase:s, edits:s)=() action (lib_cre)

General editer for cre files. No special features.

cre_enforce_saves(struct:s, arguments:[at_worst|at_best|within_tolerance])=(struct:a) dimorphic (lib_cre)

(This is a struct function.)

Set the creature's saving throws to their legal values. 'arguments' can be blank (the default), 'at_worst', 'at_best', and 'within_tolerance', where tolerance is set by the ini value 'save_tolerance' and saves are modified if the old and new versions are close enough.

Requires data_saving_throws macro to have been run

cre_enforce_saves_notstruct(arguments:s)=() patch (lib_cre)

NOT a struct function.

Enforce a correct set of saving throws.

Arguments can be: - "" (default) - just sets it - at worst - at_best - only_if_new - a little delicate, responds only to zero - within_tolerance - adjust if within the 'save_tolerance' value of the correct #

cre_enforce_thac0(struct:s, arguments:[at_worst|at_best|within_tolerance])=(struct:a) dimorphic (lib_cre)

(This is a struct function.)

Set the creature's thac0 to its legal value. 'arguments' can be blank (the default), 'at_worst', 'at_best', and 'within_tolerance', where tolerance is set by the ini value 'save_tolerance' and thac0 is modified if the old and new versions are close enough.

Requires data_thac0 macro to have been run

cre_enforce_thac0_notstruct(arguments:s="exact")=() patch (lib_cre)

cre_insert_script(struct:s, arguments:s, script:s, loc:s)=(struct:a) dimorphic (lib_cre)

(This is a struct function.)

Given the usual array of combat scripts (override/class/race/general/default), and a script 'script' (synonym: 'arguments'), insert a new one in position 'loc'. Insert position can be 'high', 'low', 'after x' (defaults to 'high' if x isn't present), or 'before x' (defaults to 'low' if x isn't present). If there isn't space, merge the two lowest scripts to make space.

cre_make(debug:i=1, cre:s, version:s, path:s, location:s, locbase:s, edits:s)=() action (lib_cre)

General maker for cre files. No special features.

cre_min_stats(struct:s, arguments:s)=(struct:a) dimorphic (lib_cre)

(This is a struct function.)

Supply a space-separated list of 6 integers (the first can be a strength score like 18/76). Set the creatures STR/DEX/CON/INT/WIS/CHA to those integers, if lower.

cre_quickset_known_spells(max_level:i=9, arguments:s, type:[wizard|priest|innate])=() patch (lib_cre)

Given an array 'arguments' in the form resref=>level, and 'type' either 'wizard', 'priest', or 'innate', add the spells to the creature's known spells (removing any previous version if appropriate). The spells are added in place using WEIDU's built-in ADD_KNOWN_SPELL function (i.e. this is not a struct function). Use this for bulk spell adding when speed matters: it's much faster than the struct system.

If max_level is set, add spells only up to this level.

cre_quickset_memorized_spells(arguments:s, type:[wizard|priest|innate])=() patch (lib_cre)

Given a 2d array 'arguments' in the form (level,resref)=>number_memorized, and 'type' either 'wizard', 'priest', or 'innate', add the spells to the creature's memorized spells. The spells are added in place using WEIDU's built-in ADD_MEMORIZED_SPELL function (i.e. this is not a struct function). Use this for bulk spell adding when speed matters: it's much faster than the struct system.

cre_read_kit()=(value:s) patch (lib_cre)

NOT a struct function. Find the kit ID of the current kit. We don't look it up directly because that means parsing kitlist and it might be more efficient (depending on context) to do it directly.

cre_read_original_class()=(value:s) patch (lib_cre)

NOT a struct function. Returns (as lower-case ascii) a creature's original class, or 'none' if they don't have one.

cre_remove_items(struct:s, arguments:s)=(struct:a) dimorphic (lib_cre)

(This is a struct function.)

Input a list of resrefs. Remove any items matching the list from item slots. (The items are then deleted when the struct is written back into the cre file.) If the argument is 'all', everything is deleted.

cre_remove_spells(known:b=1, memorized:b=1, struct:s, arguments:s)=(struct:a) dimorphic (lib_cre)

(This is a struct function.)

Delete a list of spells, by default from both the known and memorized lists (you can override this by setting 'known' or 'memorized' to 0). You can use the ids name or the resref, and you can omit 'wizard_' or 'cleric_' where that's unambiguous. Using 'all' removes all spells.

cre_set_joinable_priest_spells()=() action (lib_cre)

Assign the correct known spells to all party-joinable characters who can cast (non-spontaneous) priest spells. Requires data_joinable_dvs to have been run

cre_set_kit(kitnum:i="-1", kit:s)=() patch (lib_cre)

NOT a struct function. Given a kit (a kitids entry 'kit' or an integer 'kitnum'), sets the current creature's kit to that

cre_set_known_priest_spells()=() patch (lib_cre)

On the current creature, set the appropriate priest spells as known, based on class, level and alignment. (This is fairly hardcoded as it'll tend to be applied in bulk.)

Requires the data_priest_spells and data_spells_by_level functions/macros to have been run

cre_set_proficiencies(struct:s, arguments:s, default:s)=(struct:a) dimorphic (lib_cre)

(This is a struct function.)

Input a k=>v list. Each k is a proficiency; each v is its value. Set each of them.

cre_strip_scripts(struct:s, arguments:s)=(struct:a) dimorphic (lib_cre)

(This is a struct function.)

Input a list of scripts. Remove any script on the list from the creature. If 'all' is inputted, remove all scripts.

cre_swap_items(struct:s, arguments:s)=(struct:a) dimorphic (lib_cre)

(This is a struct function.)

Input a list of resref1=>resref2 pairs. Swap each resref1 for resref2 in the creature item list.

cre_swap_scripts(struct:s, arguments:s)=(struct:a) dimorphic (lib_cre)

(This is a struct function.)

Input a k=>v list of pairs of scripts. Swap any k script for a v script.

data_hp_range action_macro (lib_data)

Works out the minimum and maximum hit points for all classes for levels 1-40 and stores them in the arrays $class_hp_minimum(class level), $class_hp_maximum(class level), with class in uppercase. Barbarians are also included.

At present this is *hard-coded*, on speed grounds: it will not allow for changes made to the HP tables.

data_joinable_dvs action_macro (lib_data)

Returns the array 'sfo_joinable_dvs', in k=>_ form, containing the lowercased dvs of all joinable creatures

data_levels_by_max_level()=(array:a) action (lib_data)

Returns an array that identifies, for each multiclass and each level N, what level the individual classes are when the highest-level class is level N. For instance, $array(CLERIC_THIEF 10 CLERIC)=8, because thieves reach level 10 before clerics and when a thief has enough XP for level 10, a cleric would be level 8.

data_lines(data:s, path:s, file:s)=(lines:a) action (lib_tools)
data_lines(default_to_current_file:i=1, data:s, path:s, file:s)=(lines:a) patch (lib_tools)

Given either 'data' (a string) or a path to a file, return either the string or the file separated into an array of lines, separated by line-breaks. If 'data' and 'file' are both blank, use the contents of the current file (if in patch context) as input (override this by setting the default_to_current_file INT_VAR to 0)

data_priest_spells action_macro (lib_data)

Return arrays "sfo_druid_spells" and "sfo_cleric_[alignment]_spells_n", containing the learnable cleric and druid spells of in resref=>level form. [alignment] is each of the nine alignments as presented in align.ids, lowercased. Arrays are ordered from highest to lowest level and from highest-numbered to lowest-numbered spell (this facilitates correctly (or at least intuitively) ordering the learned-spell lists if we use cre_quickset_known_spells).

data_priest_spells_by_type()=(druid_spells:a, cleric_spells:a) dimorphic (lib_data)

Goes through all spells in the SPPR namespace and returns a resref=>discard array of druid and cleric spells. We don't return spells in HIDESPL or in the hardcoded list of spells in the 50-99 namespace that aren't in use

data_proficiencies()=(weapprof:a, profs:a, profsmax:a) dimorphic (lib_data)

Returns the (uppercase-rows/cols) arrays 'weapprof','profs', and 'profsmax', containing the contents of weapprof.2da and prof.2da respectively. For weapprof, columns are indexed by the proficiency ID. Adds SORCERER entries automatically to weapprof, cloning MAGE.

If copies of these files exist in weidu_external/data/dw_shared, use those instead (these are generated by ToF's externalized proficiency system.)

data_saving_throws action_macro (lib_data)

Read in saving throws for all classes and store in a 3da struct, in the form sfo_saves_%class%_%level%_=n, up to level 50 (padding out if necessary)

Here type is 'death','wands','polymorph','breath', or 'spells', and class is lowercased.

data_scroll_resrefs action_macro (lib_data)

Reads in every divine and arcane spell, and stores them like this:

WIZARD_FIREBALL_SCROLL=scrl1g $sfo_arcane_scrolls("WIZARD_FIREBALL")=scrl1g CLERIC_FREE_ACTION_SCROLL=scrl58 $sfo_divine_scrolls("CLERIC_FREE_ACTION")=scrl58

data_spell_resrefs action_macro (lib_data)

Read in every entry in spell.ids and set a variable with that name whose value is the spell resref, and store the data in the sfo_spell_resrefs array. Also store the spell type

e.g. WIZARD_FIREBALL = SPWI304 $sfo_spell_resrefs("WIZARD_FIREBALL") = SPWI304 WIZARD_FIREBALL_LEVEL = wizard $sfo_spell_types("WIZARD_FIREBALL") = wizard

Also, for wizard/priest spells, store the spell level in the format

WIZARD_FIREBALL_LEVEL=3

If extended spell namespace is in use, add the spells from dw_ext_spell.ids too.

Also check for spells in the DWWP, DWPW, DWPI, DWWI namespace (as per ToF naming conventions) , e.g.

CLERIC_FIREBALL=DWWP304 CLERIC_FIREBALL_LEVEL=3 INNATE_WIZARD_FIREBALL=DWWI304

data_spells_by_level action_macro (lib_data)

Read in spell slots for all classes and store in a struct 'sfo_spell_level', in the form

sfo_spell_level_%class%_%level%_%spell_level%"=n (# spells known at level) sfo_spell_level_sl_to_cl_%class%_%level%=n (minimum level at which caster gets spells of this level) sfo_spell_level_cl_to_sl_%class%_%level%=n (maximum level of spells known at caster level) sfo_spell_level_max_%class%=n (max level of spells known)

data_thac0 action_macro (lib_data)

Read in thac0 for all classes and store in a 2da, in the form $sfo_thac0("class" "level")=n up to level 50, padding out if necessary. 'class' is lowercased.

data_vanilla_high_spells()=(vanilla_high_spells:a) dimorphic (lib_data)

This just loads a hardcoded (uppercased, resref=>discard) list of hidden spells in the 50-99 namespace. It allows for the Spell Revisions mod.

decomment_code()=() patch (lib_interject)

define_spell_list(determine_empty_levels:b, empty_level_max:i=9, silent:b, spells:s, key:s, list_name:s, spell_array:s)=() action (ui_spell_system)

Given a space-separated list of spells 'spells', and/or an array spell=>_ or spell=>spell_level of spells, and a string 'key', construct a spell list 'key' comprising those spells. If you use the spell=>spell_level format, it saves looking up the levels of the spells).

Spells can be given either as resrefs or as IDS entries (if the latter, they need to have been loaded into memory using data_spell_resrefs). If a spell is missing, whine unless silent=1.

If determine_empty_levels=1, also record which levels are empty in the lua output. (empty_level_max is the highest level at which this is done - default is 9.)

Spell system is automatically set up if it isn't already.

DELETE_EFFECT(check_globals:i=1, check_headers:i=1, header:i="-1", header_type:i="-1", multi_match:i=999, verbose:i, match_opcode:i="-1", match_target:i="-1", match_power:i="-1", match_parameter1:i="-1", match_parameter2:i="-1", match_timing:i="-1", match_resist_dispel:i="-1", match_duration:i="-1", match_duration_high:i="-1", match_probability1:i="-1", match_probability2:i="-1", match_dicenumber:i="-1", match_dicesize:i="-1", match_savingthrow:i="-1", match_savebonus:i="-11", match_special:i="-1", match_save_vs_spell:i="-1", match_save_vs_breath:i="-1", match_save_vs_poison:i="-1", match_save_vs_wand:i="-1", match_save_vs_polymorph:i="-1", match_ignore_primary:i="-1", match_ignore_secondary:i="-1", match_bypass_mirror_image:i="-1", match_ignore_difficulty:i="-1", match_drain_hp_to_caster:i="-1", match_transfer_hp_to_target:i="-1", match_fist_damage_only:i="-1", match_drain_to_max_hp:i="-1", match_suppress_feedback:i="-1", match_save_for_half:i="-1", match_made_save:i="-1", match_does_not_wake:i="-1", match_resource:s="SAME", match_function:s)=() patch (alter_effect)

Edited version of Cam's DELETE_EFFECT (shipped with WEIDU) that allows for finer control on the savingthrow and special fields, and also to check the value of a function ('match_function'). Functions can be anonymous.

despecialize(string:s)=(string:s) dimorphic (lib_fn)

Take a string, and return it with any WEIDU regexp special characters escaped out.

detect_chargen_status()=() action (ui_shared_code)

Install the detect_chargen_status LUA functions in m_dw_shr.lua (these functions are used by other SFO functions (and elsewhere in ToF) to detect whether a given menu is being called as part of chargen)

detectable_spells(fix_khelben:i=1, skip_legacy:i)=() action (ds)

Set up detectable spells (allows AI to detect spells; this is a shared set of conventions developed by the modding community). By default, fixes legacy issues in Khelben's Warding Whip caused by (ancient) mods; skip by setting fix_khelben=0. By default, addresses some legacy issues; set skip_legacy=1 to skip.

disable_subrace(subrace:s)=() action (ui_add_subraces)

Disable the listed subrace (assumed to be installed) from the list of subraces offered to the player in character generation.

disjunctive_substitution(telemetry:b, script:s, dialog:s, match:s, replace:s, exclude_array_script:s="NO_ARRAY", exclude_array_dialog:s="NO_ARRAY")=() action (disjunctive_substitution)

For each bcs in the space-separated list 'script', and each dlg in the space-separated list 'dialog', swap 'match' for 'replace', keeping to the rules of Boolean algebra.

The syntax for 'replace' is either "OR()line1|line2|..." or "line1|line2|...".

If 'script'='all', process all scripts except those listed in 'exclude_array_script'; likewise for dialogs.

Set telemetry=1 to get some debugging information.

divide_round_up(numerator:i, denominator:i)=(value:s) dimorphic (lib_fn)

Divide two numbers, rounding up any remainder

dlg_copy(tv:i, allow_missing:i, debug:i=1, dlg:s, source_path:s, source_location:s, source_locbase:s, path:s, location:s, locbase:s, edits:s)=() action (lib_dlg)

General copier for dlg files. No special features. 99.9% of the time, it will be better to use WEIDU's .d format instead of this function.

dlg_edit(tv:i, allow_missing:i, debug:i=1, edit_strrefs_in_place:i, dlg:s, path:s, location:s, locbase:s, edits:s)=() action (lib_dlg)

General editer for dlg files. No special features. 99.9% of the time, it will be better to use WEIDU's .d format instead of this function.

dlg_make(debug:i=1, dlg:s, version:s, path:s, location:s, locbase:s, edits:s)=() action (lib_dlg)

General maker for dlg files. No special features. 99.9% of the time, it will be better to use WEIDU's .d format instead of this function.

ds_make_detectable(match_opcode:i="142", match_parameter2:i="-1", stat_value:i=1, complain:i=1, duration:i="-1", id:s, resource:s, match_resource:s="SAME")=(ds_ids_map_stats:a, ds_ids_map_splstate:a) action (ds)

The core function in the 'detectable_spells' library. The resource called out is marked so that a given opcode (by default, the icon opcode) is accompanied by a spellstate specified in 'id'.

ds_process_table(complain:i, default_opcode:i=142, table:s, default_stat:s)=(ds_ids_map_stats:a, ds_ids_map_splstate:a) action (ds)

Process a table of DS instructions. Possible columns are:

  • resource (the thing being patched; compulsory; can be a spell.ids ref, a full resource name, or the resref of a spell or item. If the latter, and if both resref.spl and resref.itm exist, spl is default)
  • stat (the stat being assigned; defaults to the default_stat variable)
  • stat_value (the value assigned to the stat; defaults to 1)
  • match_opcode (the opcode to clone; defaults to 142)
  • match_parameter2 (a restriction on which opcode is cloned; defaults to -1, clone all)
  • match_resource (a restriction on which opcode is cloned; defaults to SAME, clone all)
Columns can be in any order, but first row must be column headers. Other columns are ignored/

ds_resolve_stat(id:s)=(stat_ind:s, stat_param:s, stat_opcode:s, stat_type:s, ds_ids_map_stats:a, ds_ids_map_splstate:a) patch (ds)
ds_resolve_stat(id:s)=(stat_ind:s, stat_param:s, stat_opcode:s, stat_type:s, ds_ids_map_stats:a, ds_ids_map_splstate:a) action (ds)

Take the stat 'id' and see if it's already present. If not, add it. Either way, return its value as stat_ind.

By default, we work out where to find/put it heuristically:

  • If it's already in stats.ids, we assume it's there and return that value.
  • If not, and we're on EE, we return it in splstate.ids.
  • If not, and we're on ToBEx, we return it in stats. ids.
  • If not, and we're not on EE or ToBex, we give up.
The function gives a little more data: stat_opcode is the opcode used to apply the stat; stat_param is the parameter value used to apply the stat; stat_type is 'stat', 'splstate' or 'null' depending where we put it. (The array returns are for internal use.)

If you enter id as 'splstate:blah' then 'blah' will always be resolved as a splstate (use this when the same id is in stats.ids and splstate.ids)

dual_class_kits()=() action (ui_dual_class_kits)

Do setup work, ready to install specific dual-class kits.

eff_copy(tv:i, allow_missing:i, debug:i=1, eff:s, source_path:s, source_location:s, source_locbase:s, path:s, location:s, locbase:s, edits:s)=() action (lib_eff)

eff_edit(tv:i, allow_missing:i, debug:i=1, edit_strrefs_in_place:i, eff:s, path:s, location:s, locbase:s, edits:s)=() action (lib_eff)

General editer for eff files. No special features.

eff_make(debug:i=1, eff:s, version:s, path:s, location:s, locbase:s, edits:s)=() action (lib_eff)

General maker for eff files. No special features.

eff_make_casting_effect(target:i=2, type:i=1, default:, cast, instantly, at, caster, level, level:i, if, set,, we, assume, type:i=2, effect:s, spell:s)=() action (lib_eff)

Make an effect that casts a spell.

ensure_hex(pad_length:i, in:s)=(out:s) dimorphic (lib_fn)

Forces the string 'in' into hex form and passes it as 'out'. If pad_length is set to a non-zero value, ensure the hex is (at least) that long.

eval(var:s, arguments:s)=(var:s, value:s) dimorphic (lib_fn)

Given a string, return its value if it's a variable, or the string itself if not. Also permits 'argument'->'value' interface.

exclude_subrace(kit:s, subrace:s)=() action (ui_virtual_class)

This forces 'kit' (a rowname entry from kitlist.2da) to prohibit characters of subrace 'subrace' (an ID from the sfo-lua-generated table 'dw_subrace_ids.2da' in dw_shared).

ext(uppercase:b)=(ext:s, version:s) patch (lib_ietool)

Returns the extension of the currently-being-patched file, if we can tell. (we can tell for 2da, are, chr, cre, dlg, gam, itm, pro, spl, sto, vef, vvc, wed, and wmp) Also return its version

By default we return lowercase; if you want it in uppercase, set uppercase=1.

extend(inline:i, ssl:i, tv:i, allow_missing:i, script:s, files:s, file:s, location:s, locbase:s, path:s, top:s, bottom:s, variables:s)=() action (lib_ietool)

Extend the script(s) 'script' (legacy synonyms: file, files) with 'top' at the top and/or 'bottom' at the bottom. top and bottom location is specified in the usual SFO way. If inline=1, 'top' and 'bottom' are assumed inlined at .../stratagems-inline

If ssl=1, assume 'top' and 'bottom' are ssl scripts, and pass 'variables' to the SSL parser. If tv=1, prepend to each script. If allow_missing=1, skip silently any missing script (default is to WARN).

extend_area_script(inline:i, ssl:i, area:s, location:s, locbase:s, path:s, top:s, bottom:s, variables:s)=() dimorphic (lib_ietool)

Extend the area script of area 'area' with 'top' at the top and/or 'bottom' at the bottom. If the script doesn't exist, we create one, using standard conventions. 'area' can be a list of areas, in which case each one is extended. Same input conventions as 'extend'.

extend_worldscripts(inline:i, ssl:i, location:s, locbase:s, path:s, top:s, bottom:s, variables:s)=() dimorphic (lib_ietool)

Extend the global scripts (baldur, baldur25, and whatever is in campaign.2da worldscript column). Same input conventions as 'extend'.

extended_add_spell(force_extended:i, replace:i, id:s, file:s, path:s, location:s, locbase:s, exclude_alignment:s, exclude_kit:s, include_class:s)=(resref:s) action (ui_extra_spells)

extended_add_spell_helper(force_extended:i, level:i, type_num:i, type:s, id:s)=(resref:s, lua_line_needed:s) action (ui_extra_spells)

extended_spell_test()=() action (ui_extra_spells)

This just copies 10 spells of each level into the 51-55 and extended namespaces, for testing. It also makes the Doom copy (SPPR1A3) good-only

externalize_profsmax()=() action (ui_externalize_proficiencies)

This moves the functionality of profsmax.2da to the LUA, permitting control of maximum proficiencies at a finer grain than the engine permits (in particular, this is required for libraries like ui_add_subraces that use proficiencies to signal).

externalize_weapprof()=() action (ui_externalize_proficiencies)

This moves the functionality of weapprof.2da to the LUA, permitting control of allowed proficiencies at a finer grain than the core engine permits.

find_parenthesis_range(index:i, left:s="{", right:s="}")=(start:s, end:s) patch (lib_tools)

given an index in a file, a left string, and a right string, extract the index number of the first left string after that index and the matching right string

funlib_combine_libraries(library:s, path:s="MOD_FOLDER/sfo", data_path:s="MOD_FOLDER/sfo/data")=() action (lib_funlib)

Given 'library' and 'path' (defaults to the sfo library path), look for a template file, '.tpt'. If you find it, copy it to '.tpc' (also at 'path'), replacing any instances of include-library{lib_whatever

funlib_document_component(component_loc:s, extra_path:s)=() dimorphic (lib_funlib)

Particular application of funlib_document_libraries that documents all tpa/tph files in a directory, setting extra_path to the sfo library directory.

funlib_document_libraries(library_path:s="MOD_FOLDER/sfo", library_array:a, extra_path:s, extra_array:s, style_path:s="../../doc/files", resource_path:s="MOD_FOLDER/sfo", template_path:s="MOD_FOLDER/sfo/resource/doc_template.html", index_template_path:s="MOD_FOLDER/sfo/resource/index_template.html", doc_path:s="MOD_FOLDER/sfo/doc/functions", ignore:s="MOD_FOLDER/sfo/data/weidu_functions.2da", menu_data:s="menu_data.ini", menu_path:s="MOD_FOLDER/sfo/data")=() dimorphic (lib_funlib)

Given either an array of libraries in the form file=>path, or a path to a directory of libraries (assumed to be all and only the tph/tpa files) generate HTML documentation for each library (and also check for namespace collisions). MOD_FOLDER/sfo/resource/doc_template.html points to the HTML template into which we substitute the data. ../../doc/files points to the location of the css files and similar. doc_path is where you want them to go. Functions listed in the 'ignore' 2da (by default, the weidu built-in functions) are ignored. If 'extra_array' or 'extra_path' are set, the libraries contained in the array or on the path are included for dependencies but not documented.

'menu_data' and 'menu_path' point to the file that describes which overall lists of functions are to be generated. (The default file lists the SFO functions, the SFO-LUA functions, all the functions, and all the functions including deprecated and internal ones.

funlib_get_functions_used(file:s, path:s, location:s, locbase:s)=(functions_used:a, ignore_dependencies:a) action (lib_funlib)

Return a k=>_ array of all functions used (via LAF/LPF) in the specified library. Also return an array of dependencies we're directed to ignore (via ignore_dependencies).

funlib_report_dependencies(recursive:i=1, library_path:s, library_array:a, ignore:s="MOD_FOLDER/sfo/data/weidu_functions.2da")=() dimorphic (lib_funlib)

Given either an array of libraries in the form file=>path, or a path to a directory of libraries (assumed to be all and only the tph/tpa files), generate a report in the data_loc directory on dependencies, missing functions, and use of internal functions. If recursive=1, close dependency relations transitively.

generate_interjection(line_count_here:i, passback_state_number:i="-1", needs_passback:i, global_passback_say:i="-1", condition:s, dialog_main:s, stack:s, stack_base:s)=(stack:s, passback_state_number:s) patch (lib_interject)

get_spell_array(list:s, root:s)=(this_array:a) action (ui_spell_system)

Given 'root' either SPPR or SPWI, and 'list' a list of spell-list keys, return an array of all spells with that root, in the format resref=>[01], with the value being 1 iff the spell is in one of the lists.

If list is 'cleric' or 'druid', instead parse the spell directly and check its usability fla

get_virtual_parent_classes()=(virtual_parent_classes:a) action (ui_virtual_class)

Parses m_dw_vcd.lua to extract an array of virtual classes, in the form $virtual_parent_class([virtual class id])=[id of first kit included in the class]

get_weapprof_path()=(weapprof_path:s) action (ui_externalize_proficiencies)

This returns either 'override/weapprof.2da' or '[data_loc_shared]/weapprof.2da', depending on whether the weapprof system has been externalized.

globalize_array_load(silent:b, array:s)=(success:s, array:a) dimorphic (lib_globalize)

The associative array 'array', which should previously have been saved, is loaded and returned as 'array'. 'success' is returned as 1 if the array was previously saved or 0 otherwise. If success=0, we print a warning, unless silent=1.

globalize_array_save(append:i, array:s)=() dimorphic (lib_globalize)

The associative array 'array' is saved, to be accessed later. If append=1, we append to an existing saved array.

handle_ict_blocks()=() patch (lib_interject)

handle_unusable(arguments:s)=(value:s) dimorphic (lib_ietool)

Take as input a string, assumed to be an item description. If on EE, remove the 'not usable by:' bit.

hide_ability_button_for_kit(kit:s, buttons:s)=() action (ui_hide_ability_button)

The kit 'kit' has the button(s) 'buttons' hidden by the UI. 'kit' is a rowname entry from kitlist.2da. 'Buttons' is a space-separated list of strings which appear as 'button' in enginest.2da entries of form STRREF_GUI_BUTTONS_[button].

hide_hla(resref:s)=() action (ui_externalize_hlas)

Given the resref of a HLA, that HLA is hidden (i.e., player-unselectable) for all players on the HLA screen. (Mostly used internally; in general it will be more sensible just to remove the HLA from the LU files.)

hide_proficiency(id:i)=() action (ui_externalize_proficiencies)

This hides the proficiency with id 'id' so that it is not displayed on the proficiency select screen.

hide_this_displayed_ability(string:s)=() action (ui_shared_code)

Mark a specific ability (or space-separated list of abilities) to be hidden on the character sheet. Abilities are identified by their enginst.2da ID.

ids_resolve(min:i="0", max:i="-1", ids:s, idsfile:s)=(value:s) dimorphic (lib_ids)

Given an ids file and an ids symbol, return its int value, adding it if necessary. min and max are the minimum and maximum values to try.

ids_sort(idsfile:s)=() action (lib_ids)

Given an ids file, sort it numerically by index, preserving duplicates

immunity_effect(permanent:i=1, arguments:s, struct:s)=(struct:a) dimorphic (lib_immunity)

(This is a struct function).

Patches the spell/item/creature to offer immunity to a list of effects. If run in permanent mode (the default) it adds the effect outright with timing=9 (2 for items). On a spell, this is added to all abilities; on an item or cre, to the base item/cre. If run in non-permanent mode, everything is applied as a clone of the controlling immune-to-this opcode (if any).

The list of effects is: charm, stun, fear, hold, sleep, poison, diseased, energy_drain, blinded, insects

immunity_effects_load action_macro (lib_immunity)

Defines the arrays for the immunity_effect function

immunity_find_strings()=(immunity_string_array:a) action (lib_immunity)

This function gets all the immunity strings - loading them from a premade file if it exists, creating that file otherwise. The file is created with COPY + and so persists even when the component is uninstalled.

The 'create file' version only works in English. It shouldn't be run in distributed code. Make sure it's run ahead of time (on each of BG2, SoD, IWD) to make the files.

Currently EE-only, though wouldn't be difficult to adapt.

immunity_spell(struct:s, arguments:s)=(struct:a) dimorphic (lib_immunity)

Given a list of spell resrefs, grants (for an item or creature) while-equipped or permanent immunity. On a spell, adds permanent (not just until-death) immunity to target=2 as an ability effect.

include(inline:b, literal:b, file:s, files:s, location:s, locbase:s, path:s)=() dimorphic (lib_include)

'file' (synonym: 'files') is a list of tpa files (leave off the suffix), located at the location specified by 'location', 'locbase', and 'path'. (If none are set, assume location is component_loc.) Each is INCLUDEd. Unless literal is set to 1, the SFO syntactic-sugar changes are applied.

indirect_load action_macro (lib_indirect)

Load the various variables in resource_id, checking for duplicates and prebuilding any listed duplicates

ini_check(silent:i, ini:s, arguments:s, section:s)=(value:s) dimorphic (lib_ini)

Return a value from the mod's ini (which needs to have previously been read in, as a flat ini, into SFO_reserved_ini_hash). If 'section' is set, instead return a section-dependent value from the ini (it needs to have previously been read into SFO_reserved_ini_hash_2d). If the ini doesn't contain that value, whine unless silent=1. ini and arguments are synonyms.

ini_global_add(default:i, add_to_ini:i, type:s, id:s, global:s)=() dimorphic (ui_set_ini_globals)

ini_global_add_function(function:s)=() dimorphic (ui_set_ini_globals)

ini_global_setup()=() action (ui_set_ini_globals)

ini_load action_macro (lib_ini)

ini_read(backwards:b, flat:b, case:[upper|lower], section:s)=(array:a, section_array:a) patch (lib_ini)
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 (lib_ini)

Read in a file, or in patch context this file, in the 'ini' format. By default, store it in a 2d array (section,key)-> value. If 'flat' is set, instead store it as key=>value, ignoring sections. If 'backwards' is set, swap key and value. if 'case' is set, force the key and value into upper or lower case. Also return a k=>_ array 'section_array' of section keys. If "section" is set, return only entries in the 'section' section (this automatically sets flat=1).

In action context, if path,location, and locbase are all blank, assume an in-game ini file.

Ini entries wrapped in "" will have them stripped.

ini_to_2da(ini:s, location:s, locbase:s, path:s, 2da_path:s, 2da:s, defaults:s, main_default:s="*", first_column:s, force_columns:s)=() action (lib_ini)

ini_write(array:s)=() patch (lib_ini)
ini_write(file:s, path:s, location:s, locbase:s, array:s)=() action (lib_ini)

insert_script(script_array:s, script:s, arguments:s, loc:s)=(script_array:a) dimorphic (lib_ietool)

Given a numbered-from-0 array of scripts 'script_array', and a new script 'script' (synonym: 'arguments'), insert the new script in position 'loc'. Insert position can be 'high', 'low', 'after x' (defaults to 'high' if x isn't present), or 'before x' (defaults to 'low' if x isn't present). If there isn't space, merge the two lowest scripts to make space.

install(overwrite:b=1, inline:b, arguments:s, files:s, file:s, location:s, locbase:s, path:s, postfix:s, ext:s)=() action (lib_ietool)

'arguments' (synonym: 'files'; synonym:'file') is a list of files (including extensions) located at the location given by path/location/locbase. Each is copied over to the override; if any are BAF/D, they are COMPILE EVALUATE_BUFFERred; if they are SSL, they are compiled to BCS; if they are v1 CREs, we run FJ_CRE_EFF_V2 to force V2 effects. If they are 2da/ids/ini files, we EVALUATE_BUFFER. If none of 'location', 'locbase', and 'path' are set, set location=resource. If overwrite=0, don't overwrite already-present files (does not affect scripts).

If 'ext' (legacy synonym: 'postfix') is set, automatically add that file extension (so LAF install STR_VAR files="script1 script2" ext=baf ... END installs script1.baf and script2.baf).

If the argument is 'all', apply to every file at the path. If both 'ext' and 'all' are set, apply to every file at the path with that file extension.

install_extended_spell_functions()=() action (ui_extra_spells)

Add the LUA functions and menu edits to enable extended spells. Most of this is now externalized to the onOpen and systemcall libraries.

install_script(inline:i, script:s, location:s, locbase:s, path:s)=() action (lib_indirect)

invert_string(block_size:i, string:s)=(string:s) dimorphic (lib_fn)

Break 'string' into 'block_size'xN substrings, and invert the order of those substrings.

iter_2da()=(array:a) patch (lib_iter)

Return an array of the game resources contained in the current 2da file, assumed to be of type old-monster-summoning (prototype MONSUM01), new-monster-summoning (prototype MSUMMO1), or spell-selection (prototype SPCL621)

iter_ini()=(array:a) patch (lib_iter)

Assuming the current file is an IWD-style area ini, return an array of the game resources it uses

iter_resource(start:s, ignore:s, spell_use_function:s, icon_function:s)=(array:a) action (lib_iter)

Take as input the filename of a file. Return an array of filenames of resources used by that file, constructed recursively. Ignore any filenames for which the action function 'ignore', if set, returns true.

We don't currently recurse through or collect the following:

  • Wish spell resources
  • Familiars
  • Tilesets
  • The worldmap
  • Creature animations

If 'spell_use_function' is set, apply it as a patch function to every spell resource referred to in an opcode. (We don't actually iterate through all of these since they're not actually a resource used by the spell.) The function should take as inputs 'source' (the resref of the resource being patched), 'source_ext' (the ext of the resource being patched), 'resref' (the resref of the resource being removed/protected from, 'ext' (the ext of that resource - in this setup, basically always spl, though we do log item uses from 318 etc).

iter_script()=(array:a) patch (lib_iter)

Return an array of all in-game resources referenced in the currently-being-patched, assumed-decompiled, script.

iter_struct(arguments:s)=(array:a) action (lib_iter)

Take as input the filename of a struct file. Return a k=>_ array of the filenames of all game resources referenced by it. (We don't check if the resources exist.)

iter_vef()=(array:a) patch (lib_iter)

Return an array of the game resources contained in the current VEF file.

itm_bespoke_restriction(strip_other_restrictions:i=1, display_strref:i, race:s, scriptname:s)=() patch (lib_itm)

(NOT A STRUCT FUNCTION.) Attaches a 319 opcode to an item to make it usable only by a certain race or by a creature with a certain scriptname. Specify 'race' (an entry from race.ids, e.g. TIEFLING) or 'scriptname' (e.g. 'keldorn'). If you specify a scriptname, you should also specify display_strref, the string to display in the 'usable' field (probably a specific creature name).

The function strips any previous race/scriptname 319s, and by default removes any other unusability flags from the item. (Set strip_other_restrictions=0 to skip this.)

itm_copy(tv:i, allow_missing:i, debug:i=1, itm:s, source_path:s, source_location:s, source_locbase:s, path:s, location:s, locbase:s, edits:s)=() action (lib_itm)

General copier for itm files. No special features.

itm_edit(tv:i, allow_missing:i, debug:i=1, edit_strrefs_in_place:i, itm:s, path:s, location:s, locbase:s, edits:s)=() action (lib_itm)

General editer for itm files. No special features.

itm_make(debug:i=1, itm:s, version:s, path:s, location:s, locbase:s, edits:s)=() action (lib_itm)

General maker for itm files. No special features.

kit_add_clab_mc(kit_ids_entry:i, parent_class:s, primary_class:s, commands:s)=() action (lib_kit)

Adds a list of powers to a multiclass kit. Supply: the entry of the kit in kit.ids ('kit_ids_entry'), the kit's parent class (parent_class), the class to which the powers are to be added ('primary_class'), and the list of powers to add ('commands'). 'commands' is a comma-separated list each of whose elements should be of the form 'GA(arg)' or 'AP(arg)', where 'arg' is the usual form of a lib_kit power command, i.e. 'resref first_level', 'resref first_level interval', or 'resref first_level interval last_level'.

kit_apply_powers(arguments:s, struct:s, class:s)=(struct:a) dimorphic (lib_kit)

kit_baseline_lua()=() action (lib_kit)

Make sure every kit is using a unique LUA entry. (Idempotent.)

kit_clab_initialize()=() action (lib_kit)

Wild Mage squats on the core CLABs mages; give them unique entries. Also make sure every clab actually exists

kit_clab_mc_initialize()=() action (lib_kit)

kit_copy(debug:b=1, kit:s, edits:s, struct:s=k)=() action (lib_kit)

Given kit="old=>new", copy kit 'old' onto kit 'new'. 'old' can be a class instead of a kit, and can be a rowname entry from kitlist or a row label from clastext. This assumes 'new' is a new kit, and doesn't officially support overwriting, i.e. when kit 'new' already exists. (It might work fine but no guarantees.)

'edits' is executed as an anonymous function, which first reads old kit into struct 'struct' (default 'k') and then writes that struct onto the new kit.

kit_copy_from(struct:s, arguments:s, source:s)=(struct:a) dimorphic (lib_kit)

Struct function. 'arguments' is a space-separated list of kit-defining 2das. 'source' is another kit, identified via rowname. The values used by that kit are copied over.

kit_edit(edit_strrefs_in_place:b, debug:b, clab_only:i, kit:s, edits:s, struct:s="k")=() action (lib_kit)

Edit the kit (or class) 'kit'. (You can also specify a string of kits, and each will be edited, but be careful using this function for bulk minor editing of kits, as it may be faster to edit the relevant 2da directly.) 'kit' should be a rowname entry from kitlist.2da (or a class from class.ids), but we can cope with it being a row entry from clastext.2da instead (e.g. we can cope with ARCHER or ASSASSIN rather than FERALAN or ASSASIN).

'edits' is executed as an anonymous function, which first reads the kit into struct 'struct' (default value 'k') and then writes that struct back.

If 'edit_strrefs_in_place' is set to 1, any strrefs have their values updated, rather than being replaced with new strrefs (use this with caution).

kit_edit_all(edit_strrefs_in_place:b, skip_trueclass:b, clab_only:b, parent_class:s, edits:s, filter:s, struct:s=k)=() action (lib_kit)

Apply 'edits' as an edit to all kits with parent class 'parent_class', using the same syntax as 'kit_edit'. If 'skip_trueclass' is set to 0 (the default) also include the parent class itself. If 'filter' is set, apply 'filter' as an SFO function to each (uppercased) kit label, and apply the edit only if the return value is 1. (You can use the anonymous function construct.)

kit_edit_clab(power_array:s, delete_powers:s, clab:s)=() dimorphic (lib_kit)

kit_find_class_clab_prefix(class:s)=(prefix:s, clab:s) dimorphic (lib_kit)

Given a class, return the CLAB prefix for that class, and also the full unkitted CLAB file for that class.

kit_find_next_ids(to_start:ix4029)=(idsnum:s) action (lib_kit)

Find an unused ids entry in kit.ids, and return it in hex format.

kit_find_next_unused_lua(clone:i=1, try_next:i, root:s)=(lua_short:s) dimorphic (lib_kit)

Given 'root', find the first unused lua of the form 'LU[root][0-9]', starting from try_next (by default, 0). If clone=1 (default) copy the default version over to the new one. The returned string, 'lua_short', does not include the 'lu'.

kit_find_unused_clab(try_next:i=1, parent_class:s, file_ext:s)=(clab:s) dimorphic (lib_kit)

Find an unused clab for the current kit.

kit_get_id(kit:s)=(kit_id:s, found:s) dimorphic (lib_kit)

Find the kit_id of a kit. (The left-hand column of kitlist.2da.) Returns a new entry if not found. Also returns 'found' as 1 if found, 0 if not

kit_grant_apply_mc_powers(arguments:s, struct:s, class:s, prefix:s)=(struct:a) dimorphic (lib_kit)

kit_grant_powers(arguments:s, struct:s, class:s)=(struct:a) dimorphic (lib_kit)

kit_label_original_classes()=() action (lib_kit)

kit_match_usability(struct:s, arguments:s)=(struct:a) dimorphic (lib_kit)

Struct function. 'arguments' is a kit; the current kit's 'unusable' flag is set to match it

kit_read(clab_only:i, kit:s)=(struct:a) dimorphic (lib_kit)

Read the contents of a kit (or the kit part of a class, if 'kit' is a class) into a struct. (This is basically a wrapper for vtable_read, with a bit of extra functionality to support clab editing.)

kit_remove_powers(arguments:s, struct:s)=(struct:a) dimorphic (lib_kit)

kit_resolve_spell_applicator(number:i=1, spell:s="null")=(applicator:s) action (lib_kit)

Checks a master table (dw_mc_applicators.txt, in data_loc_shared) to see if we have a spell which grants 'spell' 'number' times. If we do, return its resref. If we don't, build it and then return its resref.

kit_set_alignment(struct:s, arguments:s)=(struct:a) dimorphic (lib_kit)

Struct function. 'arguments' is a space-separated list of alignments in format 'cg', 'nn', etc. Those and only those alignments are permitted.

kit_set_race_tables(force:b, struct:s, kit:s)=() dimorphic (lib_kit)

Given a kit-defining struct, set the race tables to reflect any changes in availability. If force=1, set them whether or not the struct metavariables are set (use this for new/copied kits).

kit_set_table(present:b=1, kit_id:i, table:s)=() dimorphic (lib_kit)

Given a k_x_y table (which might not exist), a kit id, and 'present' (a Boolean), add or subtract the kit to/from the table, creating it if necessary

kit_write(is_dummy:b, clab_only:i, edit_strrefs_in_place:b, kit:s, struct:s)=() dimorphic (lib_kit)

Write the contents of a struct into (new or existing) kit 'kit'. This includes setting the clab file and the race tables (which are controlled by the 'any_race' and 'human'/'dwarf' (etc) struct variables). If 'edit_strrefs_in_place' is set to 1, any strrefs have their values updated, rather than being replaced with new strrefs (use this with caution). ('is_dummy' just creates a dummy kit; this is used internally).

level_at_xp(xp:i, num_classes:i=1, class:s)=(level:s) dimorphic (lib_ietool)

Returns the level of a character with the given amount of XP. num_classes should be 1 for single-class PCs, 2 for double-class, 3 for triple-class. Class should be class.ids entry.

log_this(repeat:b=1, new:b, file:s, input:s, arguments:s, path:s="weidu_external/data/MOD_FOLDER", location:s, locbase:s)=() dimorphic (lib_sfo)

Dump the string 'input' (synonym:'arguments') into the text file 'file' (by default placed in the weidu_external/data/MOD_FOLDER directory, but you can override), creating it if necessary.

If repeat=0, only do this if it's not already there. If new=1, wipe any existing contents

make_elemental_spell_lists(adjust_description:i=1, tra:s=sfo_lua, tra_path:s="DEFAULT")=(earth_names:s, air_names:s, fire_names:s, water_names:s, all_names:s) action (ui_spell_system_elemental)

Create LUA spell lists for each element.

make_extended_spell_line(add_to_clab:i, resref:s, include_class:s, exclude_kit:s, exclude_alignment:s)=(line:s, no_align_restrictions:s, lua_array:s, class_bar:a, kit_bar:a, align_bar:a) patch (ui_extra_spells)

make_force_spell_lists()=() action (ui_spell_system_elemental)

Create LUA lists of all force spells.

make_label(label:s)=() dimorphic (lib_sfo)

Set a marker file, which can be checked by check_label.

Do not use make_label or check_label in your base tp2 to decide whether to install a component, as they are invisible to Project Infinity and the like.

make_passback(global_passback_say:i="-1", dialog_main:s, stack_base:s)=(passback_state_number:s) action (lib_interject)

math_transitive_closure(relation:s)=(relation:a) dimorphic (lib_math)

Given a 2 place relation defined by a 2D array s.t. array(k,v)=1 iff Rkv, construct its transitive closure.

math_traverse_directories(directory:s)=(array:a) action (lib_math)

Given a directory, recursively return itself and its subdirectories and their subdirectories, etc. as an array in k=>_ form

math_traverse_graph(max_iterations:i=1000, silent:b, telemetry:b, start:s, relates_to:f, parameter:s)=(visited_nodes:a) dimorphic (lib_math)

Given: - a string 'start' - an action function 'relates_to' that maps strings ('arguments') to arrays of strings 'array', and takes an optional argument 'parameter'

return an array 'visited_nodes' of all strings connected to the original string by the transitive closure of the 'relates_to' relation.

Gives up after 'max_iterations' iterations. Set silent to 1 to be told when it's finished, and telemetry to 1 to get a bit more information.

merge_scripts(top:s, bottom:s, script:s)=(script:s) dimorphic (lib_ietool)

If both 'top' and 'bottom' exist, merge them into a new script, and give it a guaranteed-unique name unless a name is specified. In any case, return the name. If one doesn't exist, return the name of the other.

mos_install_v2(lowest_mos_index:i, mos_name:s, mos_loc:s, pvrz_loc:s)=() action (lib_mos)

Installs a v2 MOS file. You need to supply the filename and path of the MOS file, and the path to wherever we find the PVRZ files. The PVRZ files should be in the format 'mosxxxx', where 'xxxx' is an integer padded to 4 digits. PVRZ files will be used sequentially, starting with lowest_mos_index (default=0), and will be renumbered dynamically when installed.

NAME_NUM_OF_SPELL_RES(silent:i, spell_res:s)=(spell_name:s, spell_num:s, success:s) dimorphic (resolve_spell)

Given a resref intended to be the resref of a spell in the main spell namespace (e.g. SPWI304), returns 'spell_name' (the id assigned to the spell by spell.ids, e.g. WIZARD_FIREBALL) and 'spell_num' (the 4-digit number assigned to the spell by spell.ids, e.g. 1304). The function can look up entries in the SFO-lua extended spell system and returns spell_name in that case (and -1 for spell_num).

We return 'success', which is 1 if the lookup is successful and 0 otherwise. By default, the function throws a WARNING if success=0; you can suppress this by putting silent=1.

This is an overwrite of the core WEIDU function of the same name; the differences are (i) SFO's version can handle the SFO-lua extended spell namespace, and (ii) SFO's version throws a warning instead of failing hard if the spell can't be looked up.

new_file(no_log:i, arguments:s, file:s, location:s, locbase:s, path:s)=() dimorphic (lib_tools)

Create a new file 'file' at locations given by location/locbase/path (use only 'path' if you want to run self-contained), containing only the string 'arguments' with no spacing or line breaks

no_int_max_spells()=() action (ui_spell_system)

opcode_extract(opcode:i="-1", header:i="-1", saves:b, probs:b, include_primary:b, sectype:i, file:s, eff:s)=(subspell:s) action (lib_opcode)

Given 'file', which should be an item or spell, and 'opcode', we extract the any associated effects from file and cast them as a subspell. If header is set to a nonnegative number, we do this only for that header (header is required for item files). Returns the resref of the subspell.

By default, the saving throws/MR checks and probabilities are checked when the subspell is called. Set saves=1 and probs=1 to keep those checks with the individual effects in the subspell.

Also by default, the opcode itself is not extracted. Set include_primary=1 to extract it.

If you set sectype to a positive number, the subspell receives that sectype. (Otherwise it has sectype 0).

opcode_load_data action_macro (lib_opcode)

Load the library data used by lib_opcode functions. The default assumption is that the data is in MOD_FOLDER/sfo/data, but you can override by setting sfo_opcode_data_path to the (full) path to the data. Returns sfo_opcode_data_loaded=1.

opcode_secondary_effects(primary_opcode:i="-1", fx_off:i, include_all_cosmetics:b, include_primary:b, parent_resource:s="lib_opcode")=(value:s, opcode:s) patch (lib_opcode)

Given the offset of an fx block, and a primary opcode, returns 1 if the opcode of the block is associated with the primary opcode (that is: it is an appropriate icon or other cosmetic effect, or else is a reliably-associated secondary effect). If include_primary is set to 1 (default=0) return the primary opcode itself here. If include_all_cosmetics is set to 1 (default=0), returns true for any cosmetic effect (except icons and strrefs, which always get checked).

For convenience, we also return the opcode itself (as 'opcode').

parent_resource should be set to the file in which the block is assumed to be (defaults to SOURCE_RES)

override_chargen_kit_name(override:i, use_tra:b=1, kit:s, class:s, lua:s="m_dw_vcd")=() action (ui_virtual_class)

This function overrides the name displayed on the character-generation selection screen (but not on the character sheet) for a specific kit, which is specified both by 'kit' (a rowname entry from kitlist.2da) and 'class' (an entry in class.ids).

The integer 'override' specifies the new name. By default it's an entry drawn from the current tra file. If use_tra is set to 0, it's instead a strref.

'lua' lists where the override instruction is stored. (Don't change this off the default unless you know what you're doing and have a very good reason.)

override_class_kit_menu(virtual:b, title_tra:i="-1", class:s, title:s)=() action (ui_virtual_class)

This changes the title of a kit-selection menu in character generation. 'Class' should be an entry from class.ids, or the ID of a virtual class (in the latter case, you need to set virtual=1). You can set the title either directly as a string ('title') or indirectly as the tra number of a line in the current tra file ('title_tra').

override_class_kit_menu_setup()=() action (ui_virtual_class)

override_hla_description(strref:i, resref:s)=() action (ui_externalize_hlas)

This replaces the description shown on the HLA select screen for HLA 'resref' with the strref 'strref'. It requires ui_externalize_hlas to have been run.

override_hla_name(strref:i, resref:s)=() action (ui_externalize_hlas)

This replaces the name shown on the HLA select screen for HLA 'resref' with the strref 'strref'. It requires ui_externalize_hlas to have been run.

override_kit_desc_data(old_1_strref:i, new_1_strref:i, old_2_strref:i, new_2_strref:i, old_3_strref:i, new_3_strref:i, kit:s, swap:s, old_1:s, new_1:s, old_2:s, new_2:s, old_3:s, new_3:s)=() action (ui_virtual_class)

This function overrides the displayed class names on a character's character sheet. Its main use is for multiclass kits, so that the names of the kit components show up instead of the class components. The 'kit' variable (which should be a kitlist 2da rowname) specifies the kit for which the override should be applied.

You can specify the override in several ways. The simplest (which is usually sufficient) is to set 'swap' to a list of class=>kit swaps, e.g. swap="fighter=>kensai thief=>assasin". Here 'class' should be a class.ids entry, and 'kit' should be a kitlist.2da rowname. You can also directly specify strings to be swapped out and in, either by strref (old_[123]_strref is swapped for new_[123]_strref) or directly by string (old_[123] is swapped for new_[123]).

parse_ict_command()=() patch (lib_interject)

parse_ict_line(line:s)=(dialog:s, condition:s, say:s, do:s, success:s) patch (lib_interject)

parse_spell_lists(allow_learn:s, block_learn:s, allow_priest:s, block_priest:s, type:s="priest")=(spell_array:a) action (ui_spell_system)

Given the 'allow_learn', 'block_learn', 'allow_priest', 'block_priest' values for some (actual or fictional) kit, and its type (either 'priest' or 'mage') return an array of all usable spells, in the format resref=>1

point_in_bounds(x:i, xmin:i, xmax:i, y:i, ymin:i, ymax:i)=(in_bounds:s, x_in_bounds:s, y_in_bounds:s) dimorphic (lib_fn)

Determine whether (x,y) are inside the rectangle with bottom left point (xmin,ymin) and top right point (xmax,ymax)

pro_copy(allow_missing:i, debug:i=1, pro:s, source_path:s, source_location:s, source_locbase:s, path:s, location:s, locbase:s, edits:s, missile_entry:s)=(value:s) action (lib_pro)

General copier for pro files. Each pro file copied over will be added to projectl.ids if not already present. The function returns the ids number of the last pro to be added. 'missile' is the entry in missile.ids (defaults to the projectile name); only really works when adding only one missile at a time.

pro_edit(allow_missing:i, debug:i=1, pro:s, path:s, location:s, locbase:s, edits:s)=() action (lib_pro)

General editor for pro files, in the normal lib_struct format.

pro_make(debug:i=1, pro:s, version:s, path:s, location:s, locbase:s, edits:s)=(value:s) action (lib_pro)

General maker for pro files. Each pro file made will be added to projectl.ids if not already present. The function returns the ids number of the last pro to be added.

process_extended_spells()=() action (ui_extra_spells)

Get the extended-namespace spells into lua and into auto-grant spells.

process_favored_enemy_ini(file:s, location:s, locbase:s, path:s)=() action (ui_ranger_favored_enemy)

Process an ini file identifying various ranger favored enemies, in SFO's format. The format is a series of blocks like:
[SKELETAL_UNDEAD]
race=LICH,DEMILICH
primary_race=SKELETON
name=3
desc=4

Here: SKELETAL_UNDEAD is an internal identifier. 'primary_race' is the race that actually gets set as a favored enemy in the game engine. 'race' is a comma-separated list of other races that SFO fakes up as additional favored enemies. 'name' and 'desc' are TRA references to the name and description of the favored enemy as shown on the selection screen.

process_main_state_transitions(needs_passback:i, dialog_main_state:i, dialog_main:s)=(stack_base:s) patch (lib_interject)

process_scroll_usability_now()=() action (ui_spell_system)

prof_adjust(reexternalize:b=1, kit:s, patch:s, arguments:s)=() action (lib_prof)

Apply the SFO standard function 'patch' to all proficiencies of a certain type belonging to the kit 'kit' (can be a space-separated list). Possible arguments are (i) a space-separated list of standard prof names; (ii), 'melee', 'ranged', or 'all'. In each case prof_lookup is used to try to preserve compatibility, and each proficiency will be patched only once even if multiple BG2-style proficiencies point at the same proficiency. You can use the anonymous function construct.

prof_build_lookup(overwrite:b)=(prof_lookup_array:a, prof_lookup_rows:a, profs_used_array:a, profs_used_rows:a, unused_profs:a) action (lib_prof)

Construct

  1. a 2da lookup table (prof_lookup.2da) mapping vanilla BG2 proficiencies to actual proficiencies, using prototype weapons as a source. (e.g. we check BOW03, and map 'LONGBOW' to whatever proficiency is assigned to it). The table rows are indexed by old proficiency names, and has these columns: 'ID', 'NAME' (the weapprof row name for the new proficiency added), 'STRREF' (the name of the proficiency, taken from weapprof) 'WEAPON_STRREF' (the name of the actual prototype weapon in the BG2 proficiency - this is 'scimitar' for SCIMITARWAKISASHININJATO), 'TYPE' (either MELEE, MISSILE, or STYLE). Styles are automatically included.
  2. a 2da lookup table (profs_used.2da) listing the actual proficiencies in use. (A proficiency is in use if either (i) it is the proficiency of any of the prototype weapons, or (ii) in the case of styles, unkitted fighters have at least one pip in it.) The table rows are indexed by weapprof row names and have these columns: 'ID', 'STRREF' (the name of the proficiency, taken from weapprof) and 'TYPE' (either MELEE, MISSILE, or STYLE).
  3. a text table (profs_unused.txt) listing the unused profs in the format 'NAME ID'.
All these are stored in data_loc and are generated if present already only if the INT_VAR overwrite is set to 1. In any case, each is then read into memory, into the 2D arrays 'prof_lookup_array' and 'profs_used_array' and the k=>v array 'unused_profs', respectively. The rows of the two 2das are also returned as the arrays 'prof_lookup_rows' and 'profs_used_rows'.

prof_copy(only_if_nonzero:b, reexternalize:b=1, copy_to:s, copy_from:s)=() action (lib_prof)

Copy the proficiencies from kit 'copy_from' to kit 'copy_to'. If 'only_if_nonzero' is set to 1, proficiencies set to 0 in kit copy_to remain at zero. Only weapon proficiencies, not weapon styles, are done.

If the proficiencies have been externalized (i.e. if weapprof.2da is present in data_loc_shared) then copy it over to override first. If reexternalize=1, reexternalize afterwards.

read_whatever(length:i, offset:i)=(value:s) patch (lib_tools)

Given an offset in the current file, and given a length that is 1,2,or 4, read the appropriate-length integer from that offset.

rebuild_spell_hotkeys()=() action (rebuild_spell_hotkeys)

Regenerates the (EE) hotkey list, to contain options for all the wizard and priest spells installed.

rechoose_ranger_favored_enemy(levels:s)=() action (ui_ranger_favored_enemy)

Given a space-separated list of numbers, allow rangers to rechoose their favored enemy at every level in the list.

regexp_warning(file:s, parent:s="")=() dimorphic (lib_sfo)

Special case of 'warning' used specifically as a soft-fail for regexps.

remove_erase_bg()=() patch (ui_spell_system)

remove_erase_iwd()=() patch (ui_spell_system)

remove_joinable_spells(remove_known:i=1, remove_memorized:i=1, exclude_array:s)=() action (lib_ietool)

Deletes the 'known' and 'memorized' spell lists (SPPR/SPWI namespace only) from all party-joinable NPCs, making an exception for any NPC with a dv on the k=>_ list 'exclude_array'

require_gender(kit:s, gender:[male|female])=() action (ui_virtual_class)

This forces 'kit' (a rowname entry from kitlist.2da) to require the character to be male or to be female, according to what 'gender' is set to.

RES_NUM_OF_SPELL_NAME(silent:i, spell_name:s)=(spell_res:s, spell_num:s, success:s) dimorphic (resolve_spell)

Given a string intended to be the IDS name of a spell (e.g., WIZARD_FIREBALL), returns 'spell_res' (the (uppercase) resref assigned to the spell by spell.ids, e.g. SPWI304) and 'spell_num' (the 4-digit number assigned to the spell by spell.ids, e.g. 2304). The function can look up entries in the SFO-lua extended spell system and returns spell_res in that case (and -1 for spell_num).

We return 'success', which is 1 if the lookup is successful and 0 otherwise. By default, the function throws a WARNING if success=0; you can suppress this by putting silent=1.

This is an overwrite of the core WEIDU function of the same name; the differences are (i) SFO's version can handle the SFO-lua extended spell namespace, and (ii) SFO's version throws a warning instead of failing hard if the spell can't be looked up.

resolve_dv(creature:s, default:s)=(dv:s) dimorphic (lib_ietool)

Given a creature's file resref, returns the scriptname (death variable) of the creature. If it has none, assign it one. By default, the assigned scriptname is its resref; you can override this by setting 'default'.

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

Given a sectype 'sectype' (synonym: 'arguments') and associated string (STR_VAR 'string') or tlk entry (INT_VAR 'strref'), check if the sectype is already present. If it is, set its string to the specified string. If it isn't, add it, with that string. Either way, return its value as 'sectype_value' (synonym: 'value').

If both the strref and string are specified, default to the string (and log a warning). If string is "" (the default), use strref=-1 rather than an actual blank string.

resolve_splprot_entry(stat:i, val:i, value:i, relation_number:i, relation:s, description:s)=(value:s) dimorphic (lib_ietool)

Given 'stat', 'val' (legacy synonym: 'value'), and either 'relation_number' or 'relation', return an appropriate entry in splprot.2da, adding it if necessary. (see IESDP opcode documentation for opcode 324).

'Relation' can be:

  • 'equals'/'equal' (resolves to 1)
  • 'less'/'less_than' (resolves to 2)
  • 'greater'/'greater_than' (resolves to 3)
  • 'less_than_equal'/'less_than_equals'/'less_equal' (resolves to 0)
  • 'greater_equal'/'greater_equals' (resolves to 4)
  • 'not_equal' (resolves to 5)

'description', if set, is added to the bare number of a new entry, following the 2.6 EE style. (Existing entries do not gain descriptions.)

resolve_statdesc(check_first:i="-1", strref:i="-1", string:s, bam:s, bam_name:s, path:s, location:s, locbase:s)=(stat_num:s) dimorphic (lib_ietool)

Adds a new line to statdesc.2da. You need to specify:

  • the strref of a string, 'strref' (or you can specify the string directly, 'string', and it will be added to dialog.tlk)
  • the name ('bam') of the bam file to be used for the line. (It can be an in-game file, or one to be copied over from your mod directory - SFO will see if it exists, and try to copy it if it doesn't.)
  • the location of the bam file, if it is in your mod directory, specified SFO-style via 'location','locbase','path'.
  • the actual name to be used for the bam file, 'bam_name'. If you leave it blank, 'bam' will be used instead (always do this if you are using an in-game file). If you set it to 'auto', a unique name will be auto-generated.
If you set the INT_VAR 'check_first', SFO will first check that line number to see if it exists. If it does, its line will be returned and no new line will be added; however, the description will be set if it is currently blank.

If you set the bam to "****", no bam will be used.

return_first_entry(list:s, separator:s=" ")=(entry:s, list:s) dimorphic (lib_fn)

Given a list of strings (separated by spaces, either bare or in "" or ~~ quotes), return the first entry, and the rest of the list. If 'separator' is specified, use it instead of space to separate strings. (It should be a single character.) If you use ' ' as a separator, newlines and tabs are replaced by spaces in processing the string

return_first_pair(list:s, arrow:s=":s=>", separator:s=" ")=(list:s, key:s, value:s) dimorphic (lib_fn)

Given a string in the form 'key=>value list', return the first key, the first value, and the rest of the string. if 'arrow' is specified, its value is used in place of '=>' to separate key-value pairs. 'key' and 'value' can be bare strings or can be wrapped in "" or ~~.

run(marker:b=1, literal:b, file:s, files:s, location:s, locbase:s, path:s, version:s, tra:s)=() dimorphic (lib_include)

'file' (synonym: 'files') is a list of tpa files (leave off the suffix), located at the location specified by 'location', 'locbase', and 'path'. (If none are set, assume location is component_loc.) Each is loaded, and then each file's name is run as an action function, with the STR_VAR argument 'version' fed to it, and with the tra file 'tra' (english and local-language versions) loaded. (So each tpa should conform to the SFO convention of containing an action function of its own name.Unless literal=1, SFO syntactic sugar is applied.

When used outside an extant 'run' instruction (in the intended setup, this should be from your tp2 as the only instruction in the component, though I can't enforce that), it sets 'component_loc' to 'location', defaults to using 'location' as the name of the tra file, and sets a marker file with name '%marker_prefix%function_index'. 'marker_prefix' defaults to 'sfo_marker_prefix', which in normal usage should have been set in your 'always' file. If INT_VAR marker is set to 0, this marker is not placed.

script_alter_block(only_once:b, recompile:b=1, script:s, patch:f, function:f, match_function:f, match:s, match1:s, match2:s, match3:s, match4:s, match5:s, swap_out:s, swap_out1:s, swap_out2:s, swap_out3:s, swap_out4:s, swap_out5:s, swap_in:s, swap_in1:s, swap_in2:s, swap_in3:s, swap_in4:s, swap_in5:s)=() action (alter_script)

Up to 6 regexps ('swap_out', and 'swap_out1'-'swap_out5') are specified. Each is matched against any matched block via REPLACE_TEXTUALLY, and on a match, is swapped for (respectively) 'swap_in' or 'swap_in1'-'swap_in5'.

script_array_to_block(array:s)=(block:s) patch (alter_script)

Given 'array'', which should be of the form created by script_block_to_array, return the BAF block as 'block'.

script_clone_block(insert_above:b, only_once:b, recompile:b=1, script:s, patch:f, function:f, patch_original:f, function_original:f, match_function:f, match:s, match1:s, match2:s, match3:s, match4:s, match5:s, swap_out:s, swap_out1:s, swap_out2:s, swap_out3:s, swap_out4:s, swap_out5:s, swap_in:s, swap_in1:s, swap_in2:s, swap_in3:s, swap_in4:s, swap_in5:s, original_swap_out:s, original_swap_out1:s, original_swap_out2:s, original_swap_out3:s, original_swap_out4:s, original_swap_out5:s, original_swap_in:s, original_swap_in1:s, original_swap_in2:s, original_swap_in3:s, original_swap_in4:s, original_swap_in5:s)=() action (alter_script)

Any matched block is copied directly below (or, if insert_above=1, directly above) the matched block. Up to 6 regexps ('swap_out', and 'swap_out1'-'swap_out5') are specified. Each is matched against the (decompiled) copied block via REPLACE_TEXTUALLY, and on a match, is swapped for (respectively) 'swap_in' or 'swap_in1'-'swap_in5'. The same happens to the original block, using 'original_swap_out'/'original_swap_out[1-5]' and 'original_swap_in'/'original_swap_in[1-5]'.

script_custom_hotkeys()=() patch (lib_script)

On the current file (assumed to be a BAF file) swap any hotkey commands with user-set hotkey remaps, as set in the ini.

script_delete_block(only_once:b, recompile:b=1, script:s, match_function:f, match:s, match1:s, match2:s, match3:s, match4:s, match5:s)=() action (alter_script)

Any matched block is deleted.

script_difficulty_ini()=() patch (lib_script)

On the current file (assumed to be a BAF file) swap difficulty GLOBAL checks for INI checks, but only if m_dw_did.lua is present.

script_disjunctive_delete(arguments:s)=() patch (alter_script)

Acting on the current file, which should be a single BAF block, delete any line that contains "arguments", so as to preserve logical structure (i.e. this can safely delete a line that might be inside an OR()).

script_ee_to_vanilla()=() patch (lib_script)

On the current file (assumed to be a BAF file) replace EE-only script commands with more-or-less-adequate oBG2 alternatives. If ToBEX is installed, replace CheckSpellState with CheckStat; if it isn't, replace it with False(). Do nothing if we're on EE.

script_insert_block(insert_above:b, only_once:b, recompile:b=1, script:s, match_function:f, match:s, match1:s, match2:s, match3:s, match4:s, match5:s, insert:s, location:s, locbase:s, path:s)=() action (alter_script)

'insert' should be either a complete path to a BAF file, or the filename of a BAF file with location given in sfo fashion by 'location'/'locbase'/'path'. That file is inserted after (or, if insert_above=1, before) any matched block.

script_install(silent:i, arguments:s, script:s, scripts:s, location:s, locbase:s, path:s)=() action (lib_script)

Given a list 'arguments' (syonyms: 'script', 'scripts') of script names (without .baf suffix), located at the location given by location/locbase/path, compile them into the override, first applying script_ee_to_vanilla if we're on a non-EE game. If a script is missing, whine unless silent=1.

If instead input is a list of k=>v pairs, compile k.baf to v.bcs (we don't check variables in this case).

If the script name is a set variable, compile it to the value of that variable, e.g. if script_for_wyrms is set to dw#wyrm1, compile script_for_wyrms.baf to dw#wyrm1.bcs.

script_replace_block(only_once:b, recompile:b=1, script:s, match_function:f, match:s, match1:s, match2:s, match3:s, match4:s, match5:s, insert:s, location:s, locbase:s, path:s)=() action (alter_script)

'insert' should be either a complete path to a BAF file, or the filename of a BAF file with location given in sfo fashion by 'location'/'locbase'/'path'. That file is substituted in to replace any matched block.

scroll_spec_bonus_setup()=() action (ui_spell_system)

Build the various resources needed for the externalized bonus-spell setup.

set_basic_hla_progression(min:i, step:i)=() action (ui_externalize_hlas)

This function sets the HLA progression (prior to crossing the threshold for 'genuine' HLAs, i.e. 3M XP in BG2, so that you start getting them at level min and then get another one every step levels.

set_dual_class_kit_components_for_proficiencies(kit:s, components:s)=() action (ui_externalize_proficiencies)

I don't 100% recall what this does. It's described in my notes like this: 'set the single-class kits that override multiclass kit proficiencies for dc purposes'.

set_kit_display_priority(priority:i, kit:s)=() action (ui_virtual_class)

This assigns an integer 'priority' to the kit 'kit' (specified by a kitlist.2da rowname). This determines the order in which the kits are displayed at character selection: kits are displayed in increasing order of order priority, with kits that have no order priority displayed last and kits displayed in alphabetical order within each priority.

set_signalling_stat(stat:i)=() action (ui_externalize_proficiencies)

This sets up the stat 'stat' to be used by other UI libraries to send a signal from the chargen screen to the main game. Don't use unless you're confident you know what you're doing.

set_signalling_stat_helper(stat:i)=() patch (ui_externalize_proficiencies)

set_specialist_spells(number_required:i=1, list_spells:b=1, update_description:b, learn_more_line:s, memorize_more_line:s, old_line:s, new_line_1:s, new_line_2:s, string_school:s, string_school2:s, kit:s, spell_list:s, tra:s="sfo_lua", tra_path:s)=() action (ui_spell_system)

Given a kit 'kit' (a rowname entry in kitlist.2da), an optional integer 'number_required' (default=1) and a space-separated list of spell-list keys 'spell_list', require that kit to learn at least number_required spells from that list.

The optional 'learn_more_line' and 'memorize_more_line' are displayed to players in character generation if they don't learn/memorize an appropriate number of speciality spells. (If not set, the component autogenerates a default warning.)

The variables 'tra' and 'tra_path' specify where some standard strings are drawn from (the default assumption is that they're from sfo/lua/lang//sfo_lua.tra").

If update_description is set to 1, the function will attempt to update the kit description. (This only works with kits in a relatively standard format.) The various other variables control how this works:

  • 'old_line' is by default 'May cast one additional spell per level.', but it can be any 'advantage' or 'disadvantage' line from the kit description. It is deleted from the kit description if present.
  • 'school_string' is by default 'from the LISTNAME school', where LISTNAME is the name-string of the specialist list, or the last such list if there is more than one.
  • 'new_line_1' is by default 'May cast one additional spell per level. This additional spell must be SCHOOL_PLACEHOLDER'; SCHOOL_PLACEHOLDER is swapped for 'string_school'. It is added to the top of the 'Advantages' list if number_required=1.
  • 'new_line_2' is by default 'May cast one additional spell per level. At least NUMBER_PLACEHOLDER memorized spells of each level must be SCHOOL_PLACEHOLDER'; SCHOOL_PLACEHOLDER is swapped for 'string_school' and NUMBER_PLACEHOLDER is swapped for the value of 'number_required'. It is added to the top of the 'Advantages' list if number_required>1.
  • list_spells, if set, includes a list of all spells from the preferred list. In this case, 'string_school2' (default value: the name-string of the specialist list, or the last such list if there is more than one) is substituted into the string 'The full list of SCHOOL_PLACEHOLDER_2 spells is:'.

set_spell_learn_modifiers(kit:s, modifiers:s)=() action (ui_spell_system)

Set the externalized modifications to the chance of learning a spell. Specify 'kit' (a rowname from kitlist) and 'modifiers' (the actual LUA list of modifiers). 'modifiers' should be a comma-separated list with elements of form 'list=integer', where 'list' is either a spell list or 'default' (applies to all spell lists not called out.)

set_spell_list(chargen_choose_spells:b, update_scroll_usability:b=1, update_scrolls_later:b, silent:b, import_parent_blocks:b=1, class:s, kit:s, kit_clastext:s, block_learn:s="no_change", allow_learn:s="no_change", block_priest:s="no_change", allow_priest:s="no_change")=() action (ui_spell_system)

Set the spell lists available to, or blocked for, a given class or kit (either set class to a class.ids entry, or kit to a kitlist.2da rowname). Each of block_learn, allow_learn, block_priest, or allow_priest can be a space-separated or comma-separated list of spell-list keys, or 'no_change' (the default) in which case whatever it's currently set to will be unchanged.

If chargen_choose_spells=1, the lua also sets dwChargenChooseSpells to 1 for that class or kit. That causes the UI to try to learn spells from the character's specialist list, if any. [This is an odd place to put this, on reflection.]

If update_scroll_usability=1, we also go through all scrolls and mark them up as usable/unusable for that class/kit.

If the spell system is not already set up, we set it up.

set_up_externalized_proficiencies()=() action (ui_externalize_proficiencies)

This function is a general setup for the ui_externalize_proficiency functions.

sfo_batch_set action_macro (lib_sfo)

sfo_batch_update()=() action (lib_sfo)

sfo_crossplatform action_macro (lib_sfo)

Load appropriate values of the various variables for different versions of BG.

sfo_fix()=() action (lib_sfo)

A few basic fixes:

  • make sure dir.ids is present and working
  • add some possibly-missing entries to gtimes.ids and spell.ids
  • remove + from missile.ids
  • on BGEE, add some spell.ids entries missing that are present on SoD
  • on EE, fix a couple of UI errors

sfo_load(library:s)=() action (lib_sfo)

Given a list of space-separated sfo library functions, load those functions and (recursively) any libraries they depend on.

sfo_path(path:s, location:s, locbase:s, file:s)=(file_path:s, path:s) dimorphic (lib_sfo)

Given some or all of 'file', 'path', 'location' and 'locbase', return a full filespec file_path (e.g. MOD_FOLDER/lib/lib_soundset.tph) and the path bit (e.g. "MOD_FOLDER/lib).

Determined as follows:

  • 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 ""

sfo_setup action_macro (lib_sfo)

Set up the SFO directories and variables.

shared_color_changes()=() action (ui_shared_code)

Installs the LUA function 'dwUpdateColors' into m_dw_shr.lua (this function is used to force the starting colors during CHARGEN, e.g. for subrace or portrait-match purposes)

spell_system_extension_setup()=() action (ui_extra_spells)

spellstyle_apply_style(arguments:s, style_file:s)=() patch (lib_spellstyle)

As a patch function, apply a style to a spell. 'arguments' is the style, 'style_file' contains the style data.

spellstyle_collect_styles(prototypes:s=".../stratagems-inline/spell_style_prototypes.txt", output_loc:s, output:s)=() action (lib_spellstyle)

Get the actual styles from a list of prototypes (probably not run in live-distributed code). The default for prototypes is inline in this library.

spellstyle_get_style_data()=(vvc:s, wav:s, glow:s) patch (lib_spellstyle)

Get the style data (if any) for the current spell, as defined by its 'vvc' (an actual vvc played by opcode 215, or a lighting type from opcode 141), its wav (played by opcode 174), and its glow (played by opcode 50 or 61).

spellstyle_identify_style(style_file:s)=(style:s) patch (lib_spellstyle)

Identify a style for the current spell, using 'style_file' as the list of styles

spellstyle_load_styles(file:s)=(style_vvc:a, style_wav:a, style_glow:a) dimorphic (lib_spellstyle)

Load in the data in a style list

spellstyle_update_style(old_style:s, new_style:s)=() patch (lib_spellstyle)

Update the currently-being-patched spell's style, and log it as updated.

spl_basic_ability_localcopy()=() patch (ui_extra_spells)

spl_cd_scroll_name(spell_resref:s)=(scroll_resref:s) dimorphic (lib_spl)

Given a (wizard or priest) spell resref, assign it Cam's standard scroll name, i.e. CDIDxyz for SPPRxyz, CDIAxyz for SPWIxyz

spl_copy(tv:b, allow_missing:b, debug:b=1, is_ids:b="-1", standard_icons:b=1, create_scroll:b=1, overwrite:b, overwrite_on_mismatch:b, force_extended:b, spl:s, path:s, location:s, locbase:s, source_path:s, source_location:s, source_locbase:s, icon_base_name:s, icon_path:s, icon_location:s, icon_locbase:s, edits:f, type:s)=(value:s, scroll:s, spell_resref:s, scroll_resref:s) action (lib_spl)

General copier for spl files.

'spl' is a list of k=>v pairs. Each spell k.spl is copied to v.spl. source_path/source_location/source_locbase and path/location/locbase respectively define the locations of k.spl and v.spl, with an empty path interpreted as the override in both cases. (If k is missing, we whine unless allow_missing=1.) The function 'edits' is applied to each file during copy. You can use the anonymous function construct; if you do, the spl will be read into the struct 'm' beforehand and written back afterward.

Alternately, spl can be a list of strings k..., in which case k.spl is copied to k.spl as above.

If the dest file is an ids entry (determined heuristically unless is_ids is set) then we add it to spell.ids, use it as a resref, add icons in the standard format (unless standard_icons=0) and (unless create_scroll=0) build a scroll. In this case, we use icon_path, icon_location, icon_locbase (if set) to determine where the spell icons are, and copy them over if they're in the format [ABC].bam. (If icon_base_name is unset, we use the ids name.)

By default, if the dest file is an IDS entry we put the spell in the SPWI, SPPR or SPIN namespace based on the type in the copied spell. You can override this by setting 'type' to 'wizard','priest','innate' or 'class'. (But this only determines where the spell ends up; it doesn't change its internal type.

If tv=1, we prepend "_" onto each k and v on BGTUTU installs.

If debug=1, we try to debug the anonymous function to detect any nonexistent field writes.

If overwrite=1, we install the spell if it's already present, overwriting any previous copy (or giving that previous copy a new placeholder IDS if it's at the wrong level/of the wrong type). If overwrite=0 and overwrite_on_mismatch=1, we overwrite only if there's a level or type mismatch. If overwrite=0 and overwrite_on_mismatch=0, we never overwrite.

Returns the resref of the last spell created (value; synonym:spell_resref), and its scroll (scroll; synonym:scroll_resref) if any. (Only really useful if you're copying to an ids, and only if you're only doing it once.)

spl_deabbreviate(spell:s)=(resref:s, type:s, level:s) dimorphic (lib_spl)

Take either a spell resref, or a spell ids name, or an abbreviated spell ids name (without the WIZARD_/CLERIC_ prefix). Return the spell resref, the level, and the type (innate/priest/wizard)

spl_edit(tv:i, allow_missing:b, debug:b=1, edit_strrefs_in_place:b, spl:s, path:s, location:s, locbase:s, edits:s)=() action (lib_spl)

General editor for spl files. spl is a list of resrefs, each with location defined by path/location/locbase, or in override/a game file if all are blank. Each is copied, and edits is applied as a patch function. You can use the anonymous function construct; if you do, the spl will be read into the struct 'm' beforehand and written back afterward.

If tv=1, we prepend "_" onto each k and v on BGTUTU installs.

If debug=1, we try to debug the anonymous function to detect any nonexistent field writes.

spl_enforce_school(preloaded:b, sounds_colors:b=1, struct:s, arguments:s)=(struct:a) dimorphic (lib_spl)

Forces a spell to fit the rules for the specified school, or the current school if none is specified, adjusting casting colors, casting sounds, and for wizard spells, barred schools. Barred schools are read in from data_loc_shared/mageschools.tpa, or use the BG2/IWD defaults if it doesn't exist.

If preloaded=1, assume that the array 'unusable_array' already contains the unusuable spell types (good for batch application). If sounds_colors=0, don't do the sounds and colors.

Note that this also works on items (i.e., scrolls), though the sounds/colors bit is skipped.

spl_generate_smtable(2da:s, contents:s, hit_animation:s, area_hit_animation:s)=() action (lib_spl)

Streamlined generator for smtable 2das - assumes animations are constant (as is true in all vanilla entries)

spl_hide_spells(scrolls_only:i, spells:s, spell_array:s)=() action (lib_spl)

Given a list of spells (as IDS), removes those spells entirely from the game: the spells themselves hidden in HIDESPL; all copies of the scrolls are removed from stores, creatures, areas and 2da rndtres/rndscrol; the scroll itself is replaced by a minor gem. The actual spells are still present.

You can also specify the spells as a k=>blank array. If you set the INT_VAR scrolls_only to 1, the scrolls are removed but the spells are still available to select at chargen and at sorcerer/shaman levelup, and are still given to priests.

spl_make(debug:b=1, is_ids:b="-1", standard_icons:b=1, create_scroll:b=1, force_extended:b, overwrite:b, overwrite_on_mismatch:b, spl:s, edits:f, location:s, locbase:s, path:s, icon_location:s, icon_locbase:s, icon_path:s, icon_base_name:s)=(value:s, scroll:s, spell_resref:s, scroll_resref:s) dimorphic (lib_spl)

General maker for spl files.

We create a new spell 'spl'.spl, location specified by path/location/locbase, or 'override' if all are blank. The function 'edits' is applied to the file after creation. You can use the anonymous function construct; if you do, the spl will be read into the struct 'm' beforehand and written back afterward.

If spl is an ids entry (determined heuristically unless is_ids is set) then we add it to spell.ids, use it as a resref, add icons in the standard format (unless standard_icons=1) and (unless create_scroll=0) build a scroll.

If debug=1, we try to debug the anonymous function to detect any nonexistent field writes.

If force_extended=1, we add the spell to the extended namespace even if there's space in standard namespace.

If overwrite=1, we install the spell if it's already present, overwriting any previous copy (or giving that previous copy a new placeholder IDS if it's at the wrong level/of the wrong type). If overwrite=0 and overwrite_on_mismatch=1, we overwrite only if there's a level or type mismatch. If overwrite=0 and overwrite_on_mismatch=0, we never overwrite.

Returns the resref of the spell, and its scroll if any.

spl_make_all_scrolls(name_function:s="spl_cd_scroll_name", price_table:s="MOD_FOLDER/sfo/data/scroll_prices.2da")=() action (lib_spl)

Construct a scroll for any SPPR/SPWI spell that doesn't have one and should (i.e. exclude spells unavailable to the player, as well as the Wild Mage spells)

spl_make_aura(spell:s, payload:s, effect:s)=() action (lib_spl)

Make an aura power. 'spell', when applied, permanently causes the caster to cast 'payload' 1/sec. The intermediate effect has its name autogenerated unless set explicitly.

spl_make_hla_class_ability(force_innate:b=1, resref:s, id:s)=(new_resref:s) action (lib_spl)

Copy a spell (assumed to be a wizard/priest spell, though we don't enforce this) to the SPCL namespace. and optionally make it innate. (Set force_innate=0 if you don't want to.) You need to specify the resref. Optionally you can specify the id; if not we'll infer it or make it up. Strip it from any clone (i.e., in simulacr/projimag) in the process. The new spell will have id HLA_[old_id]

Because we expect to use this in bulk applications, it adds new IDS entries unsorted. (Sort at the end using ids_sort.)

spl_make_innate_repeating_spell(new_is_ids:i=1, cooldown:i, overwrite:i, allow_missing:i, patch_description:i=1, override_description:i, delete_casting:i, desc_strref_cd:i=100404, the, string, to, be, appended, to, the, description,, with, 99999, replaced, by, the, actual, repeat, time., Default, value, is, from, SCS, shared.tra, desc_strref_atwill:i=100405, this, string, says, 'use, at, will', and, gets, appended, instead, if, cooldown:i, arguments:s)=() action (lib_spl)

Make a spell (from a template) that you get back after a certain period

spl_make_scroll(price:i="-1", spell_resref:s, scroll_resref:s, icon:s="%spell_resref%A", name_function:s="spl_cd_scroll_name", price_table:s="MOD_FOLDER/sfo/data/scroll_prices.2da")=(scroll_resref:s) dimorphic (lib_spl)

Given a (wizard or priest) spell, generate a scroll for that spell, using CamDawg's naming conventions by default

spl_make_summoning_2da(filename:s, monsters:s, anim_hit:s="msumm1h", anim_area:s="msumm1x")=() action (lib_spl)

Given a list of monsters, a filename of a 2da (omitting '2da'), and an optional choice of anim_hit and anim_area animations (defaulting to 'msumm1h' and 'msumm1x'), make a standard-format summoning 2da

spl_remap_level_9_wizard_spells()=() action (lib_spl)

Move the spells in the SPWI926-SPWI949 range into the SPWI0xyz range.

spl_resolve_ids(level:i, force_extended:b, replace_on_mismatch:b, ids_sort:i=1, ids:s, type:[wizard|priest|innate|class|1|2|3|4|5])=(resref:s, mismatch:s, already_present:s, lua_line_needed:s) dimorphic (lib_spl)

Given an ids entry (WIZARD_THIS_SPELL, etc) find its id resref (or dw_ext_spell resref), if necessary adding it to spell.ids. Need to supply the spell type and the level.

On EE, if full, or if force_extended=1, add it to the extended namespace instead.

If it's already present, return already_present=1. Check if its level and type match the requested level and type. If not, return 'mismatch=1' (otherwise, return 0). If 'replace_on_mismatch' is set to 1, instead replace the old id with a placeholder and add the new id in the correct place (and return mismatch=0, already_present=0).

By default the ids file is resorted, so that the new entry appears in the right place. Skip this by setting ids_sort=0. (This is sensible for bulk additions, where you'll want to sort once at the end.)

spl_resolve_smtables_entry(name:s, 2da:s)=(value:s) action (lib_spl)

Return the number corresponding to a given named smtable.2da entry (we search for [0-9]+_name), adding it if necessary (for the latter, need to supply a 2da name too.)

spl_resref_to_type_level(resref:s)=(type:s, level:s) dimorphic (lib_spl)

Take a spell resref of a spell in spell.ids. Return its type (priest/innate/class/wizard) and level (assuming it fits standard conventions, and using 1 for innate and class).

splconv(spell:s, new_type:s, tra_path:s="MOD_FOLDER/sfo/lua/lang", tra:s="sfo_lua", new_resref:s)=(new_resref:s, splconv_array:a) dimorphic (lib_splconv)

Input a wizard/priest spell 'spell' in the SPWI/SPPR range (either a resref, or an ids name (from normal or extended namespace), or an abbreviated ids name) and a spell type 'new_type' (innate/priest/wizard). Convert the spell, unless old and new types match. Return the resref of the new spell. Delete any headers where min_level>50, to remove scroll-making code & the like.

If 'tra_path' and 'tra' are set ('tra' does not include the .tra extension) then the function attempts to load strings @100-@105 from it and then to update the description to include new casting time, remove reference to spheres, and add a note saying that this is an innate version of a spell normally available to wizards (or whatever). See lua/lang/english/sfo_lua.tra in the sfo-lua template for the format.

'splconv_array' is a 2D array reporting all spells/items that reference SPPR/SPWI spells (other than themselves) via 206/318/321/324. We output it to avoid having to regenerate it each time.

ssl_core(inline:b, silent:b, script:s, variables:s, booleans:s, location:s, locbase:s, path:s)=(value:s) action (lib_ssl)

Take 'script'.ssl, located at locbase|location|path (or located at .../stratagems-inline if inline=1), and compile it to a baf file of the same name, living in workspace/ssl_out. If it's missing, whine unless silent=1. Return 1 if file exists to compile, 0 otherwise. SSL input: variables are the concatenation of the external string 'sslvariables' and the function argument 'variables'; booleans are given by the eponymous function argument; libraries are the concatenation of the external string 'ssllibrary' and 'weidu_external/data/MOD_FOLDER\autolib'. SSL itself should be located at the path given by the external string 'ssl_loc'.

ssl_to_baf(inline:b, silent:b, evaluate_variables:b=1, script:s, arguments:s, scripts:s, variables:s, booleans:s, location:s, locbase:s, path:s)=() action (lib_ssl)

Take a list of ssl files 'script' (synonyms: 'arguments', 'scripts'), each located at locbase|location|path (or located at .../stratagems-inline if inline=1), and compile them to baf (leaving them at [workspace]/ssl_out), processing SSL line_if commands and carrying out EE-to-oBG2 swaps and custom-hotkey swaps as appropriate. If any are missing, whine unless silent=1. Evaluate variables unless evaluate_variables=0.

If input is instead in the form "k=>v list", compile each k.ssl to v.baf.

SSL input: variables are the concatenation of the external string 'sslvariables' and the function argument 'variables'; booleans are given by the eponymous function argument; libraries are the concatenation of the external string 'ssllibrary' and 'weidu_external/data/MOD_FOLDER\autolib'. SSL itself should be located at the path given by the external string 'ssl_loc'.

ssl_to_bcs(inline:b, silent:b, script:s, arguments:s, scripts:s, variables:s, booleans:s, location:s, locbase:s, path:s)=() action (lib_ssl)

Take a list of ssl files 'script' (synonyms: 'arguments', 'scripts'), each located at locbase|location|path (or located at .../stratagems-inline if inline=1), and compile them all the way to bcs, processing SSL line_if commands and carrying out EE-to-oBG2 swaps and custom-hotkey swaps as appropriate. If any are missing, whine unless silent=1.

If input is instead in the form "k=>v list", compile each k.ssl to v.bcs.

SSL input: variables are the concatenation of the external string 'sslvariables' and the function argument 'variables'; booleans are given by the eponymous function argument; libraries are the concatenation of the external string 'ssllibrary' and 'weidu_external/data/MOD_FOLDER\autolib'. SSL itself should be located at the path given by the external string 'ssl_loc'.

sto_copy(tv:i, allow_missing:i, debug:i=1, sto:s, source_path:s, source_location:s, source_locbase:s, path:s, location:s, locbase:s, edits:s)=() action (lib_sto)

General copier for sto files. No special features.

sto_edit(tv:i, allow_missing:i, debug:i=1, edit_strrefs_in_place:i, sto:s, path:s, location:s, locbase:s, edits:s)=() action (lib_sto)

General editer for sto files. No special features.

sto_make(debug:i=1, sto:s, version:s, path:s, location:s, locbase:s, edits:s)=() action (lib_sto)

General maker for sto files. No special features.

strdoc_document_all_strtypes(path:s="MOD_FOLDER/sfo/structures", style_path:s="../../../doc/files", template_path:s="MOD_FOLDER/sfo/resource/strdoc_template.html", doc_path:s="MOD_FOLDER/sfo/doc")=() action (lib_strdoc)

strdoc_format_array_as_table(array:s)=(data:s) patch (lib_strdoc)

strdoc_format_header_data(filter1:f, filter2:f, filter3:f, filter1_title:s, filter2_title:s, filter3_title:s, title:s, description:s)=(data:s, data_alphabetical:s) patch (lib_strdoc)

strdoc_get_data(filter1:s, filter2:s, filter3:s)=(main_keys:a, meta_keys:a, special1_keys:a, special2_keys:a, special3_keys:a) patch (lib_strdoc)

strref_2da(function:s)=() patch (lib_strref)

Acting on the current file, assumed to be a 2da file, apply 'function' to each of the strrefs in it.

Note that detection of which numbers in a 2da file are strrefs is of necessity a bit heuristic. We assume that

  • no integer lower than 160 is a strref (the actual values from 1-100 in BG2 are dialog strings not referenced in 2das)
  • no integer larger than 299999 is a strref (this is way bigger than any actual dlg)
  • no integer in the following 2das is a strref: banttimg clearair extanim extspeed happy pplane raisdead randcolr repmodst splashs2 splashsc startare startbp startpos strmod strmodex xpbonus xpcap xplevel xplist
  • no integer <10000 in the following is a strref: end15fps endmve1 endmve2 endmve3 intro intro15f melissan xnewarea
  • no integer in columns 0-2 of clastext is a strref
  • nothing in hex notation is a strref
  • all other integers are strrefs

strref_apply(function:f)=() patch (lib_strref)

Apply the (SFO standard patch) function 'function' to every strref in the current file, replacing it with the output. Currently doesn't do wmp files.

strref_script(function:s)=() patch (lib_strref)

Acting on the current file, assumed to be a decompiled script, apply 'function' to each of the strrefs in it

struct_add(insert_point:i="-1", number:i=1, auto_open:b=1, auto_close:b=1, equip:b=1, replace:b, debug:b=1, struct:s, type:s, patch:f, match_parent:f, vertices:s, slots:s)=(struct:a) dimorphic (lib_struct)

Adds 'number' new entries to the list of extended headers of type 'type', and then applies 'patch' to them. Entries are inserted before header 'insert_point', or after the last header if there is no such header (this is the default). 'struct' is the struct in which the data is stored and is passed back at the end of the function.

If 'type' is of form 'parent_child' (e.g. 'ab_fx'), instead add entries of type 'child' as children of type 'parent' in the same fashion, whenever the parent returns true to 'match_parent'.

You can use the anonymous function construct with 'patch', and 'match_parent'. By default (i.e. unless altered by 'auto_open' and 'auto_close'), the anonymous function starts by opening the header into the struct 's', and finishes by writing it back. Also by default, the parent (if any) is opened into the struct 'p'.

We can't enforce it, but to be meaningful the in and out 'struct' need to be the same (since we only write the changes).

If appropriate (e.g. for adding known spells to CRE files) the header array is sorted before reinsertion.

struct_alter(auto_open:b=1, auto_close:b=1, debug:b=1, equip:b, replace:b, struct:a, type:s, match:f, patch:f, match_parent:f, vertices:s, slots:s)=(struct:a) dimorphic (lib_struct)

Applies a patch function 'patch' to every extended header of type 'type' that returns true (1) to function 'match', or to every such header if no function is specified. 'struct' is the struct in which the data is stored and is passed back at the end of the function.

If 'type' is of form 'parent_child' (e.g. 'ab_fx'), instead apply 'patch' to every child header of type 'child' whose parent is type 'parent', and where the child returns true to 'match' and the parent returns true to 'match_parent'.

You can use the anonymous function construct with 'patch', 'match', and 'match_parent'. By default (i.e. unless altered by 'auto_open' and 'auto_close'), the anonymous function starts by opening the header into the struct 's', and finishes by writing it back. Also by default, the parent (if any) is opened into the struct 'p'.

We can't enforce it, but to be meaningful the in and out 'struct' need to be the same (since we only write the changes).

struct_apply_regexp(auto_open:i, write:i=1, report_back:i=1, ext:s, regexp:s=".*", function:s, type:s, strtype:s)=(array:a) action (lib_struct)

Quickly apply the function 'function' to every header of the specified type where the file matches 'regexp'. Return an array of all entries where the fn returns 'value' for at least one entry. (Note that this does not use the struct system: the file is edited directly, not opened into a data structure.

struct_clone(number:i=1, multi_match:i=9999, clone_above:i, auto_open:b=1, auto_close:b=1, open_on_match:b, open_parent:b, debug:b=1, struct:s, type:s, match:f, patch:f, match_parent:f, vertices:s)=(struct:a) dimorphic (lib_struct)

Adds 'number' copies of any extended header of type 'type' which return true to the function 'match', and then applies 'patch' to them. 'struct' is the struct in which the data is stored and is passed back at the end of the function.

If 'type' is of form 'parent_child' (e.g. 'ab_fx'), instead clone entries of type 'child' as children of type 'parent' in the same fashion, under the additional requirement that the parent returns true to 'match_parent'.

You can use the anonymous function construct with 'patch', 'match', and 'match_parent'. By default (i.e. unless altered by 'auto_open' and 'auto_close'), the anonymous function starts by opening the header into the struct 's', and finishes by writing it back. Also by default, the parent (if any) is opened into the struct 'p'.

We can't enforce it, but to be meaningful the in and out 'struct' need to be the same (since we only write the changes).

struct_copy(allow_missing:i, tv:i, debug:i=1, file:s, ext:s, edits:s, path:s, location:s, locbase:s, source_path:s, source_location:s, source_locbase:s)=() action (lib_struct)

'file' is a string of k=>v pairs (or string of single entries, treated as k=>k). Copy each k to v (or k.default_ext to v.default_ext if it is set). k and v are located at SFO-standard locations defined by, respectively, (source_path/source_location/source_locbase) and (path/location/locbase), with 'override' as the default in each case.

Apply 'edits' as an anonymous patch function in the process, opening k into struct m in the process and writing it at the end.)

struct_delete(auto_open:b=1, debug:b=1, struct:s, type:s, match:f, match_parent:f)=(struct:a) dimorphic (lib_struct)

Delete any extended headers of type 'type' which return true to the function 'match'. 'struct' is the struct in which the data is stored and is passed back at the end of the function. (If 'match' is left blank, it always returns true.)

If 'type' is of form 'parent_child' (e.g. 'ab_fx'), instead delete entries of type 'child' as children of type 'parent' in the same fashion, under the additional requirement that the parent returns true to 'match_parent'.

You can use the anonymous function construct with 'match' and 'match_parent'. By default (i.e. unless altered by 'auto_open' and 'auto_close'), the anonymous function starts by opening the header into the struct 's'. Also by default, the parent (if any) is opened into the struct 'p'.

We can't enforce it, but to be meaningful the in and out 'struct' need to be the same (since we only write the changes).

struct_display_lookups(struct:s)=() dimorphic (lib_struct)

Display the lookup table for the extended-header types. (For debugging.)

struct_echo(struct:s, strtype:s)=() dimorphic (lib_struct)

Display the contents of the 'header' part of an IE struct. (For debugging.)

struct_edit(allow_missing:b, tv:b, debug:b, edit_strrefs_in_place:b, open_extended:b="-1", file:s, ext:s, edits:s, path:s, location:s, locbase:s)=() action (lib_struct)

'object' is a list of strings s, interpreted as files. Locate each file at path|location|locbase, defaulting to in-game if all are empty. COPY or COPY_EXISTING COPY_EXISTING each s (or s.default_ext if it is set) over itself. Apply 'edits' as an anonymous patch function in the process, opening s into struct m in the process and writing it at the end.

struct_expand_slots(slots:s)=(slots:s) patch (lib_struct)

struct_extract(array:a, struct:a)=(array:a) dimorphic (lib_struct)

Given an array of keys, for each key which also keys the struct, set the corresponding array value to the struct value.

struct_get(arguments:s)=(value:s) patch (lib_struct)

Returns the contents of a field. (Use for quick lightweight edits where it's not worth reading in the struct.)

struct_get_offset_array2(offset:i, type:s)=(array:a) patch (lib_struct)

Returns the secondary offset array at offset 'offset' for parent-child type type of the currently-being-patched strtype

struct_get_offset_array(type:s)=(array:a) patch (lib_struct)

Returns the offset array for type type of the currently-being-patched strtype

struct_initialize action_macro (lib_struct)

This reads in all the defining data for the various game files that lib_struct can patch. It's a macro because there are lots of them and they're dispersed over lots of arrays; it's not convenient to bundle them up.

Here's what we do:

  1. Read in the various lookups from str_lookup.ini and str_versions.ini, both forwards (for read) and backwards (for write).
  2. For each file type:
    1. Read in its main header definitions (see struct_read_main_definitions)
    2. Read in the contents of the 2da file that gives the offsets and the like for its various extended components
    3. For each one of those, read in the header for it
    4. Read in the parent/child data (see struct_parent_child_definitions)
  3. Read in the default file versions for each filetype and each game

struct_inject(array_in:a, struct:s)=(struct:a) dimorphic (lib_struct)

Put each k1...k_n=>v pair in array_in into the struct.

struct_iter(struct:s, type:s)=(blockcount:s, length:s, iter_array:a) patch (lib_struct)

For given subtype 'type', return an array k=>v, where k is the index of a 'type' element and v is its lookup in the struct; also return 'length', the number of 'type' elements, and 'blockcount', then length of the data block in the struct for that element.

(The struct version of GET_OFFSET_ARRAY)

struct_iter_child(parent_index:i, struct:s, pc_id:s)=(blockcount:s, length:s, iter_array:a) patch (lib_struct)

For given For given parent_child type 'pc_id' and for given parent index 'parent_index', return an array k=>v, where k is the index of a 'type' element and v is its lookup in the struct; also return 'length', the number of 'type' elements, and 'blockcount', then length of the data block in the struct for that element.

(The struct version of GET_OFFSET_ARRAY2)

struct_make(debug:i=1, file:s, ext:s, edits:s, version:s, path:s, location:s, locbase:s)=() action (lib_struct)

Make a new object of type ext. Open it into a struct m, apply the contents of 'edits' as an anonymous function, and then write m back in again. If 'version' is unset, make the current game version.

struct_new(strtype:s, ext:s)=(struct:a) dimorphic (lib_struct)

struct_read(open_header:b=1, open_extended:b=1, strtype:s, file:s, path:s)=(struct:a) action (lib_struct)
struct_read(open_header:b=1, open_extended:b=1, strtype:s)=(struct:a) patch (lib_struct)

From 'path/file', or from existing game file 'file' if path is unspecified, or in patch context the currently-open file, read in its contents according to its structure definition, into the structure 'struct'.The file type is specified by 'strtype'; note that we might well be editing an extended header in an INNER_PATCH, not the main file.

If strtype isn't specified, we assume we're patching a base file and we try to infer it from the file itself.

If open_header=0, we don't break up the header, we just read it in as one big string. (It takes maybe 5-10ms to process, so this is unlikely to be necessary - if you're doing bulk patching, struct is too slow anyway.)

If open_extended=0, just read in the header, don't bother with the extended contents.

struct_read_macro patch_macro (lib_struct)

struct_test(fraction:i=20)=() action (lib_struct)

Process a fraction of all are/cre/itm/spl/sto files through struct_read/struct_write and see which ones change. The fraction is one in (INT_VAR fraction).

struct_write(write_header:b=1, write_extended:b=1, telemetry:b, overwrite:b, edit_strrefs_in_place:b, strtype:s, struct:s, file:s, path:s)=() action (lib_struct)
struct_write(write_header:b=1, write_extended:b=1, edit_strrefs_in_place:b, telemetry:b, overwrite:b, struct:s, strtype:s)=() patch (lib_struct)

Given a structure 'struct', write it to 'path/file', or to existing game file 'file' if path is unspecified, or to the currently-open file in patch context. The file type is specified by 'strtype'; note that we might well be editing an extended header in an INNER_PATCH, not the main file. If strype isn't specified, we assume we're patching a base file and we read it in from the struct itself.

if write_header=0, leave the header alone; similarly for write_extended.

sugar_apply(patch_only:i)=() patch (lib_sugar)

Apply SFO's syntactic sugar to the current file. If 'patch_only' is set to 1, don't bother with the action parts.

sugar_semicolons()=() patch (lib_sugar)

Replace double semicolons with line breaks.

sugar_test()=() action (lib_sugar)

Copy over the inline file 'sugartest.tp2' (at the end of this library) and apply syntactic sugar to it, then INCLUDE it to check it's well-formed.

systemcall_patch()=() patch (ui_system_call)

tolower(arguments:s)=(value:s) dimorphic (lib_fn)

Given a string, return it in lower case. (This is for use in functional programming - WEIDU's TO_LOWER suffices for first-order code.)

tolower_safe(arguments:s)=(value:s) dimorphic (lib_fn)

Convert the (assumed mixed-case) string to lowercase, skipping any accented characters

toupper(arguments:s)=(value:s) dimorphic (lib_fn)

Given a string, return it in upper case.

trim_string(require_both:i, character:s=" ", string:s)=(string:s) dimorphic (lib_fn)

Given a single string, and a character (space by default) remove any occurrences of the character from the beginning and end of the string. If require_both=1, only strip if the character appears at beginning and end (and only strip one).

UI_add_function(function:s, location:s, locbase:s, path:s)=() patch (lib_ui)

Adds the contents of the file 'function' (optionally located at the SFO location given by location/locbase/path) immediately before the first function definition in the current file. (Use to add functions to UI.menu - but 99% of the time it's better to define them in your own custom lua file.)

ui_add_portraits(force_cd:i, portrait_path:s, portrait_table:s, disable:s, enable:s)=() action (ui_add_portraits)

Add some new portraits into the system, or modify existing ones (EE only)

The portraits should be listed in a 2DA table of form
skin hair major minor race sex class disabled
EXAMPLE INT INT INT INT human f fighter no
(any of these columns can be missing, in which case defaults are assumed.) The function generates a master table of this form which lives in the 'shared' subdirectory of weidu_external (specifically, at weidu_external/data/shared/dw_portraits.2da)

  • 'skin', 'hair', 'major' and 'minor' are the colors - as used by the UI - that should be assigned by default if the portrait is selected. (You can edit these
  • 'race' can be any of human, halfelf, elf, dwarf, halfling, gnome, halforc, special (the default).
  • 'class' can be fighter, wizard, cleric, thief, bard, barbarian, special (the default).
  • 'sex' can be f, m, fm (the default).
  • 'disabled' can be yes or no (the default).

The bmps themselves should be named as follows:

  • EXAMPLEhires.bmp (for large high-resolution portraits) - copied to the L slot
  • EXAMPLE330.bmp - also copied to the L slot
  • EXAMPLEL.bmp - copied direct to the L slot
  • EXAMPLE269.bmp - copied to the M slot
  • EXAMPLEM.bmp - copied to the M slot

The function takes these variables:

  • portrait_table: the full path to a new table, of the above form, to be added
  • portrait_path: the path to the directory containing any new portrait BMPs listed in the table (or, if no table is given, containing any
  • portrait BMPs just to be copied over directly - mostly applies to the core IWD/BG/BG2 portraits)
  • disable: a list, separated by spaces, of portrait base names to disable from the system
  • enable: a list, separated by spaces, of portrait base names to enable in the systems

On a non-EE install we default to CamDawg's cd_portrait_copy (which is assumed as a dependency); you can force this even on EE by setting INT_VAR force_cd=1.

UI_add_string(tra_entry:i, string_id:s, string:s)=() action (lib_ui)

Add a string to the lua string file appropriate for the current game language (e.g., L_EN_US.lua if you're playing in English). 'string_id' is the lua string ID for the string; you can specify the string either via the INT_VAR tra_entry (an entry in your current .tra file) or directly as the STR_VAR 'string'.)

UI_add_string_array(array:a)=() dimorphic (lib_ui)

Given a k=>v array 'array' with entries like MY_TEXT_STRING=>1234, go through each entry, and for each, get the string with tra ref 1234 in the current tra file and set TEXT_STRING equal to it in the current EE language file (e.g., l_en_us.lua).

Exits with a warning if not on EE.

UI_alter_function(function:s, patch:f)=() patch (lib_ui)

Find the lua function 'function' in the current file. Extract it, apply the patch function 'patch' to it (you can use the anonymous function construct) and put it back.

Note that the heuristic used to find the function is fairly crude: it assumes that the function finishes at the first 'end' that starts a new line. (This matches most, but not quite all, functions in ui.menu.)

UI_alter_object(silent:i, object:s, object_type:s, object_name_field:s="name", patch:f)=() patch (lib_ui)

Apply the patch function 'patch' to the matched ui object in the current file, and substitute the result back in. You can use the anonymous function construct.

UI_analyze_object(object:s, object_type:s, object_name_field:s="name", patch:f)=(patch_output:a) patch (lib_ui)

Apply the patch function 'patch' to the matched ui object in the current file. 'patch' should return an array called 'patch_output, which is in turn returned by UI_analyze object return 'patch_output' The actual result of the patch is discarded (i.e., the file being patched isn't changed), so the purpose of this is to extract information from ui.menu. You can use the anonymous function construct.

ui_copy_spell_lists(from:s, to:s)=() action (ui_spell_system)

Read in the spell lists associated with a given kit, and copy it to a different kit

ui_deolvynize()=(value:s) action (ui_deolvynize)

This attempts to reverse the effect of OlvynChuru's ClassSpellTool function on menu.ui and to move all spells added by ClassSpellTools to the SFO-lua system. It's not been extensively tested in the wild.

Idempotent (you can run it as many times as you like).

ui_detect_class_kit(version:s)=() action (ui_detect_class_kit)

Set up all the resources to detect the class and kit (etc). Note that unlike other SFO-lua libraries, this one just reinstalls itself every time you build it (since new classes and kits may have been added). 'Version', if set to 1, is used for debugging.

ui_dualclass_specialist_bonus_spells()=() action (ui_dual_class_kits)

ui_externalize_hlas(min:i, step:i, activate_feats:i=1)=() action (ui_externalize_hlas)

This (idempotent) function sets up the HLA system to grant HLAs every few levels (starting with level min, and then after every step levels) and to externalize requirements for various HLAs. In doing so it sets up an HLA menu if (as in IWD or BG) none is active; it also removes the icon column from the BG2 HLA menu, since that requires multiple icons for applied powers that aren't really needed and which push the art requirements.

If activate_feats=0, the low-level feats are not enabled (only the other externalizations are enabled).

ui_externalize_usability_descriptions(strref:i="-1", kit:s, item:s, item_array:s="sfo_reserved_usability_array")=() dimorphic (ui_externalize_usability_descriptions)

ui_externalize_usability_descriptions_setup()=() action (ui_externalize_usability_descriptions)

UI_get_coordinates()=(patch_output:a) patch (lib_ui)

Look through the current file for the coordinates of an object (specified by four integers separated by spaces) and/or a bam file (specified by 'BAM [whatever]'). Return them in an array with keys bam, xloc, yloc, width, height. (Use in conjuction with UI_analyze_object).

ui_inn_label()=() action (ui_inn_label)

After running this function, once you stay in an inn the room type (from 1 to 4, with - iirc - 1 as worst) is stored in the global variable dw_inn_roomtype. It gets reset once you rest again, so pick it up and change it to zero quickly if you want to interact with it.

UI_insert_into_object(object:s, object_type:s, object_name_field:s="name", insert:s, insert_data:s, path:s, location:s, locbase:s)=() patch (lib_ui)

Insert the contents of the file 'insert' (optionally located at the location picked out by path/location/locbase in usual SFO fashion) at the end of the matched ui object in the current file. Alternately, insert the string 'insert_data' in this way.

UI_install_function(inline:b, lua_file:s, new_function:s, new_function_path:s, location:s, locbase:s, path:s, search_string:s)=() action (lib_ui)

Add the contents of the file at 'new_function' (legacy synonym: new_function_path), optionally located at the location specified SFO-style by location/locbase/path (or inline if inline=1), to the end of the in-game lua file 'lua_file', creating it if necessary. If 'search_string' is set, only add the function if that string is not present in the lua already.

ui_on_open_setup()=() action (ui_on_open)

Sets up the ui_on_open library's systematic hijacking of ui.menu's onOpen command. This function is called automatically by other sfo-lua libraries: it is highly unlikely that any mod will need to call it directly.

ui_read_all_spell_lists()=(block_learn:a, block_priest:a, allow_learn:a, allow_priest:a, list_names:a, kit_names:a) dimorphic (ui_spell_system)

Read in all the spell lists, and store them in arrays block_learn, block_priest, allow_learn, allow_priest, in this format: $allow_priest(OHTEMPUS combat)=1. Also return a k=>_ array of all list names, and a k=> array of all kit names.

ui_read_spell_lists(literal:b, kit:s)=(learn:s, priest:s, block_learn:s, block_priest:s) action (ui_spell_system)

Read in the spell lists associated with a given kit. If literal=1, leave in the commas and quote marks; otherwise remove them.

UI_remove_function(function:s)=() patch (lib_ui)

Find the lua function 'function' in the current file. Remove it.

Note that the heuristic used to find the function is fairly crude: it assumes that the function finishes at the first 'end' that starts a new line. (This matches most, but not quite all, functions in ui.menu.)

UI_replace_function(inline:b, function:s, new_function:s, new_function_path:s, location:s, locbase:s, path:s)=() patch (lib_ui)

Find the lua function 'function' in the current file. Replace it with the contents of the file at 'new_function' (legacy synonym: new_function_path), optionally located at the location specified SFO-style by location/locbase/path (or inline if inline=1.

Note that the heuristic used to find the function is fairly crude: it assumes that the function finishes at the first 'end' that starts a new line. (This matches most, but not quite all, functions in ui.menu.)

UI_replace_object(object:s, object_type:s, object_name_field:s="name", replace:s, path:s, location:s, locbase:s)=() patch (lib_ui)

Replace the matched ui object in the current file with the contents of the file 'replace' (optionally located at the location picked out by path/location/locbase in usual SFO fashion).

UI_return_object(object:s, object_type:s, object_name_field:s="name", patch:f)=(object_data:s) patch (lib_ui)

Apply the patch function 'patch' to the matched ui object in the current file, and then return that object, as the value of a string variable.

ui_spell_system_schools(force_rebuild:b, tra:s=sfo_lua, tra_path:s="DEFAULT")=() action (ui_spell_system_schools)

Sets up the spell system to include all the schools of magic as defined spell lists.

ui_spell_system_setup()=() action (ui_spell_system)

Set up the externalized spell system. (Idempotent.)

ui_spell_system_spheres(path:s="sphere", list:s="sphere_list.2da", tra:s="sphere", tra_path:s="MOD_FOLDER/lang", base_tra:s=sfo_lua, base_tra_path:s="DEFAULT", 3p_folder:s=dw_tof3p)=() action (ui_spell_system_spheres)

Sets up a sphere system. See ToF for format; by default we read a 2da of spheres at sphere/sphere_list.2da, and then look for text files listing the various spells in each sphere in 'sphere'.

ui_stress_test()=() action (ui_shared_code)

Run a bunch of SFO-LUA's core UI edits and report when each is installed. For use in testing SFO-LUA on different UI mods.

ui_subrace_item_use()=() action (ui_add_subraces)

Goes through all items in the game to make sure that they're appropriately marked as unusable by new races. We assume that the new race can use something iff humans can (except that if only humans are prohibited from using it, we assume new races can use it). This is idempotent but not called automatically.

ui_system_call_setup()=() action (ui_system_call)

Set up the system-call system, making appropriate menu edits and collecting data for lua libraries.

virtual_class_setup()=() action (ui_virtual_class)

Set up the various lua files and menu edits for virtual classes

vtable_debug(function:s, vtable:s, struct:s)=() dimorphic (lib_vtable)

Checks an expression (assumed to be an anon function) for apparent references to nonexistent keys.

vtable_generate_aliases(vtable:s)=() action (lib_vtable)

vtable_initialize action_macro (lib_vtable)

Loads a bunch of data used by the vtable system.

vtable_read(silent:b, row:s, vtable:s)=(struct:a) dimorphic (lib_vtable)

Read a virtual-table row into an array.

This is hardcoded for speed - even using 2daq functions slows things down a bit, and that matters for bulk editing of kits.

vtable_write(edit_strrefs_in_place:i, vtable:s, row:s, struct:s)=() dimorphic (lib_vtable)

vvc_copy(allow_missing:i, debug:i=1, vvc:s, source_path:s, source_location:s, source_locbase:s, path:s, location:s, locbase:s, edits:s)=() action (lib_vvc)

General copier for vvc files. No special features.

vvc_edit(allow_missing:i, debug:i=1, vvc:s, path:s, location:s, locbase:s, edits:s)=() action (lib_vvc)

General editer for vvc files. No special features.

vvc_make(debug:i=1, vvc:s, version:s, path:s, location:s, locbase:s, edits:s)=() action (lib_vvc)

General maker for vvc files. No special features.

warning(repeat:i, warning:s, arguments:s)=() dimorphic (lib_sfo)

Dump the string 'warning' (synonym: 'arguments') into data_loc/sfo_warnings.txt, prepended with the component number and mod name. If repeat=0, only do this if it's not already there.

wed_add_door(name:s, tiles:s)=() patch (lib_wed)

Add a door to the current wed file. Supply the name (to match the are file) and a string of k=>v pairs identifying the tile maps when the door is closed

wed_build(width:i, height:i, tis:s, wed:s)=() action (lib_wed)

Build a new, blank WED file of the specified height and width.

wed_crop_area(x:i, xlen:i, y:i, ylen:i, area_old:s, area_new:s)=() action (lib_wed)

Copy an area, cropping it to the desired size (in tiles).

After doing it, you might want to rebuild the TIS in NI, as it's quite inefficiently stored in this algorithm

*Mostly* polygons need to be ordered with the lowest point first (as per IESDP). But this is NOT always correct and I can't work out the true algorithm.

EE only

wed_delete_polygons(area:s, polygons:s)=() action (lib_wed)

Delete wall polygons by number.

wed_duplicate_area(area_old:s, area_new:s)=() action (lib_wed)

Make an identical but differently-named copy of an area

wed_flip_area(area:s, file_loc:s, script_array:s)=() action (lib_wed)

Flip an area along its vertical axis

You need to supply (in 'file_loc') a properly-named TIS file and associated PVRZ files, plus (for legacy use) a legacy TIS file with a 'v' name suffix, plus the height,search and light maps

DO NOT do the inversion in Paint, as it apparently scrambles transparency layers. Photoshop works.

You can also pass the program an array of scripts (in the form $[script_array]("[script]")="") whose coordinates need to be inverted.

We are probably assuming ARE v1.0 in some places.

wed_get_polygons(xmin:i, xmax:i, ymin:i, ymax:i, wed:s, output_file:s)=() action (lib_wed)

Extract all the polygons in a specified region of a wed file.

wed_remap_search_map_colors(map:s, reference:s, map_out:s)=() action (lib_wed)

Take a 4-bit spat out by Photoshop and remap it to the standard 4-bit SR palette. (Use L^2 distance in color space if no exact match)

(I have tried to add 8bit=>4bit to this, but it's erratic and not a priority to fix)

wed_swap_searchmap_code(old:i, new:i, map:s)=() action (lib_wed)
wed_swap_searchmap_code(old:i, new:i)=() patch (lib_wed)

??map IWD to BG2 searchmap?? NB not dimorphic

wed_transcribe_polygons(delta_x:i, delta_y:i, st, retch_numerator:i=1, st, retch_denominator:i=1, st, retch_base_x:i, st, retch_base_y:i, from_area:s, to_area:s, polygons:s)=() action (lib_wed)

Transcribe the wall polygons with the associated numbers from one wed file to another, optionally adjusting their vertices and bounding boxes by delta and stretching around a base point.

Polygons must be listed sequentially.

write_whatever(length:i, write:i, offset:i)=() patch (lib_tools)

Given an offset in the current file, a length that is 1,2,or 4, and an integer, write the integer to an appropriate-length slot at that offset.