# lib_struct function library

Dependencies: lib_2da, lib_anon, lib_array, lib_fn, lib_ini, lib_sfo, lib_sugar, lib_tools, lib_ui

## Description

Functions to interact with IE resources under the general 'ie-struct' paradigm. i.e. we read in the struct, edit it, and then write it back again.

## Public functions

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

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

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

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

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

'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

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

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

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

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

'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

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

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

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

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

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

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

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

struct_initialize action_macro

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

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

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

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

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

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

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

struct_test(fraction:i=20)=() action

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
struct_write(write_header:b=1, write_extended:b=1, edit_strrefs_in_place:b, telemetry:b, overwrite:b, struct:s, strtype:s)=() patch

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.

## Internal functions

These should not be called from outside the library itself.

struct_add_child(auto_open:b=1, auto_close:b=1, debug:b=1, insert_point:i="-1", number:i=1, struct:s, pc_id:s, match_parent:f, patch:f)=(struct:a) patch

struct_alter_child(auto_open:b=1, auto_close:b=1, debug:b=1, struct:s, pc_id:s, match:f, match_parent:f, patch:f)=(struct:a) patch

struct_apply_to_strrefs_helper(function:s, struct:s)=(struct:a) patch

struct_clone_child(clone_above:b, number:i=1, auto_open:b=1, open_on_match:b, open_parent:b, debug:b=1, multi_match:i=9999, struct:s, pc_id:s, match:f, match_parent:f, patch:f)=(struct:a) patch

struct_debug(function:s, strtype:s, struct:s)=() dimorphic

Checks an expression (assumed to be an anon function) for apparent references to nonexistent keys.

struct_delete_child(auto_open:b=1, debug:b=1, struct:s, pc_id:s, match:f, match_parent:f)=(struct:a) patch

struct_delete_orphaned_items(struct:s)=(struct:a) patch

struct_get_default_version(ext:s)=(strtype:s, version:s) dimorphic

Returns the game-default strtype for a given extension.

struct_get_strtype(strtype:s)=(strtype:s) patch

struct_identify_slots(resref:s)=(slot:s, twohanded:s) patch

Given a resref (assumed to be of an item), deduce which creature inventory slot(s) it ought to go into

struct_init(type:s="toplevel", strtype:s)=() patch

Used internally by struct_add to set some default values when adding structs. (Use only if you can't set the default values via the struct-defining 2das.)

struct_parent_child_definitions(file:s, path:s)=(parent_child_data:a, parent_list:a, child_list:a, parent_child_id:a) action

Read in one of the 2das that defines the parent/child relations in a file type. (This stores where the child offsets are in the parent, and also serves to tell us that the parent/child relation exists). The 2da isn't ordered. Return the 2da as a 2d array. Also return lists, in the form k=>_, of the parents, the children, and the relation IDs.

struct_process_vertices(vertices:s)=(vertex_data:a) patch

Take as input a string in form

(identifier (int,int) list ) list

Return an array of form

vertex_data(identifier x n)=nth x-coordinate under identifier n vertex_data(identifier y n)=nth y-coordinate under identifier n vertex_data(identifier count)= how many x,y pairs under identifier n vertex_data(identifier box [left|right|top|bottom])=BB data for identifier-n coordinates

identifiers are 'base', 'open', 'closed', 'impeded_open', 'impeded_closed'. 'base' can be omitted if it's the first entry.

struct_read_fx_hardcode patch_macro

struct_read_main_definitions(file:s, path:s)=(has_strrefs:s, has_resrefs:s, offsets:a, types:a, flags:a, asciis:a, ids:a, lookups:a, commalists:a, multiples:a, resrefs:a, defaults:a) action

Read in one of the files that defines a header and return its contents in a bunch of arrays (all with the entry label as key):

- 'offsets': the offset of the entry in the header
- 'types': the type of the entry (short, long, byte, lookup, etc)
- 'flags' (flag labels only): the bit position of the flag in the byte
- 'asciis' (ascii labels only): how long the ascii is
- 'ids' (id labels only): the id file being used
- 'lookups' (lookup labels only): the section header in str_lookup.ini
- 'old': 1 for any entry where we keep the old value and only patch if it changes (useful for certain overlapping entries, e.g. parameter2 vs parameter2a/2b)

struct_read_vartypes(strtype:s, type:s, data:s)=(data:s) patch

struct_set_bb()=() patch

Not-at-all-encapsulated helper function that just sets the bounding box of a region, door or container according to its vertices

struct_set_slot(item_number:i, actual, struct, number,, not, the, lookup, replace:i, force_inv:i, equip:i=1, struct:s, slots:s, item_data:s)=(displaced_item_number:s, slot_number_assigned:s, struct:a) patch

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

struct_write_all_vartypes()=() patch

struct_write_postprocess(strtype:s)=() patch

ARE files need a bit of postprocessing to update the offsets for headers only used in saved-game data. PRO files need to be deflated if they don't use the extended header

struct_write_strtype(strtype:s)=() patch

## Technical notes

The ie struct is organized like this:

- General entries from the header are included in the obvious way, i.e. struct_[whatever].
- $struct("strtype") stores the actual strtype
- Data on extended headers are in struct([type] n), unexpanded (i.e. just raw data).
- struct ([type] blockcount) says how many entries there are for each type.
- struct ([type] lookup n) is a lookup from the nth actual header in the file, to entry n in the extended-header data. (The point of doing it this way is that it makes it way more efficient to delete or add data.)
- struct ([parent] n [child] m) is the data for child types of the parent type
- Again struct ([parent] n [child] blockcount) counts the blocks of this data
- struct ([parent] n [child] lookup m) is a child-level lookup table.