A course on SFO - chapter 3.1

ui_extra_spells - more space for priest/wizard spells

Back to contents

This SFO-LUA library adds space for additional spells, outside the space limits of the normal namespace.

3.1.1 What ui_extra_spells does

In the unmodded Infinity Engine, only the first 49 spell slots of each level (e.g., SPPR101-SPPR149 for first level cleric spells) are available for player-usable spells. Spells placed in the 50th-99th slots, or outside the SPPR/SPWI namespace, aren’t available for mages to learn at character generation, for sorcerers and shamans to learn when levelling up, or for clerics, druids, rangers, and paladins to memorize.

The ’ui_extra_spells’ library extends the namespace, in two ways.

  1. Spells in the 50th-99th spell slots of each level are now available for player-usable spells (with the exception of the spells already present in those slots in the unmodded game, which are NPC-only or for-internal-use-only spells).
  2. More significantly, the SPWI and SPPR namespace is extended to allow for spells in the form (SPWI/SPPR)[0-9][0-9A-Z][0-9A-Z], such as SPWI1A9 and SPPR3GB. This adds 936 extra spell slots at each level (in addition to the extra slots between 50 and 99), which should be enough to be getting on with.

An alternate tool, OlvynChuru's ClassSpellTool function, also adds space for extra spells, using similar but distinct UI edits and very different conventions. The two are not compatible. If you install ui_extra_spells after a mod that uses ClassSpellTool, SFO will try to uninstall ClassSpellTool and map any spells it added over to SFO's method. If you install a mod that uses ClassSpellTool after one that uses ui_extra_spells, you will definitely break your game.

3.1.2 Using ui_extra_spells via add_extended_spell

Assuming that you are not using SFO's 'struct paradigm', the simplest, and recommended, way to use ui_extended_spells is via the 'add_extended_spell' function, which you can think of as an extension of WEIDU's native ADD_SPELL command. You use it like this:

LAF add_extended_spell 
	STR_VAR 
		id=WIZARD_AWESOME_SPELL 
		file=”awesomespell.spl” 
		path=”%MOD_FOLDER%/spl” 
    RET resref 
END

This takes the spell at 'path/file' (normally, as here, a spell in your mod directory), finds a slot for it in either spell.ids or the extended namespace, sets ‘resref’ to the resref of that slot, copies the spell over, and if necessary installs it in the extended system. 'id' is the identifier, which is stored in either spell.ids or, for extended spells, in weidu_external/data/dw_shared/dw_ext_spell.ids. The various resources needed for extended spells are installed the first time you use the function.

Note that add_extended_spell infers the level, exclusion flags etc from the spell file. So if you’re going to edit it during installation (e.g. if you’re making a spell in place) you need to do those edits first. (Copy the spell to a temporary workspace and install it from there.)

add_extended_spell takes two optional INT_VAR arguments:

  • If force_extended=1 (default=1), the spell is added to the extended namespace even if there’s room in the ordinary namespace. (This is the default because in normal circumstances it’s better to leave the ordinary namespace free for mods that need it (e.g.IWDification) and/or that don’t use this tool.)
  • If replace=1 (default=0), then if that id entry is already present, it will be replaced by a placeholder entry before the spell is added. (If replace=0, the spell just won’t be added if this ids entry is there already.)

    e.g. if you try to add WIZARD_ENORMOUS_KABOOM (a level 8 spell), and some other mod has already added WIZARD_ENORMOUS_KABOOM to the 7th level spells as SPWI768, then:

    • If replace=0, your version of the spell won’t be installed
    • If replace=1, the IDS entry for SPWI768 will be changed to SPWI768_OLD, and then your spell will be added as an 8th level spell.

(In addition, add_extended_spell takes additional arguments that allow you to restrict which classes, kits, and alignments can use a given spell; these are described in section 5 below.)

3.1.3 Using ui_extra_spells via struct functions

If you are using SFO's 'struct paradigm', ui_extra_spells is automatically integrated into lib_spl and specifically into the spl_copy and spl_make functions. When you use these functions, if there is no space in the standard namespace then spells will automatically be placed in the extended namespace. You can force resolution in the extended namespace by setting the INT_VAR force_extended=1.

3.1.4 Using ui_extra_spells via bulk addition

The alternative way to use add_extended spells is to install the spells manually in the permitted namespace, and then do

LAF spell_system_extension_setup END
This will go through all spells in the 50-99 and extended namespace, and process them. (The main reason you might want to do this if other mod(s) have already added spells in the usual way which have spilled into the 50-99 namespace.)

3.1.5 Finding the resrefs for added spells

SFO stores the details for the extended spells in weidu_external/data/dw_shared/dw_ext_spell.ids. This is a 2-column file where the first entry in each row is the resref of a spell in the extended namespace (e.g., SPWI7A4) and the second row is its id (e.g., WIZARD_ENORMOUS_KABOOM). Think of this file as the extended-namespace version of spell.ids.

SFO also extends WEIDU's included functions RES_NUM_OF_SPELL_NAME and NAME_NUM_OF_SPELL_RES so that they resolve in the extended namespace as well as the normal namespace. (It doesn't bother with RES_NAME_OF_SPELL_NUM since IDS numbers aren't defined for the extended namespace.) The usage is the same as for the usual functions.

In addition, SFO automatically sets a spell-name variable for each spell in dw_ext_spell.ids, just as it does for spells in spell.ids. For instance, if you've added WIZARD_AWESOME_SPELL in a previous component, you can do

SpellRES("%WIZARD_AWESOME_SPELL%",Myself)
in a script. If you want SFO to reload these variables mid-component (say, if you've added a spell and want to use it in the same component), do this:
LAM data_spell_resrefs

3.1.6 Restricting spell use by kit, class and alignment

The 'add_extended_spell' function has some limited functionality that lets you fine-tune which kits, classes and alignments can use a spell. In general this is not recommended: the interface is clunky and the ui_spell_system functions offer much more power and flexibility. Still, here's how it works:

  • The STR_VAR 'exclude_kit' can be set to a space-separated list of the kit numbers (taken from column 0 of kitlist.2da) which are prohibited from using a spell. For instance, setting exclude_kit="12 13" will prevent the spell being used by blades and swashbucklers. Note that this is cumulative with the restrictions in the spell file itself, e.g. barring specific specialities.
  • The STR_VAR 'exclude_alignment' can be set to a space-separated list of forbidden alignments, in this format: L_G, L_N, L_E, N_G, N_N, N_E, C_G, C_N, C_E. This is again cumulative with any restrictions inside the spell file itself (and so usually will be useful only for wizard spells).
  • The STR_VAR 'include_class' can be set to a space-separated list of the class names (from class.ids, e.g. CLERIC, DRUID, MAGE) that can use the spell. This supersedes any information in the spell file. For instance, setting include_class=PALADIN means that only paladins can use the spell.

3.1.7 Limitations

  1. At character creation, clerics and druids can’t memorize spells in the extended namespace (or in SPPRx50-x99). (This is because those spells are added by the engine in character generation with no UI input, so we can’t tweak them.) They can memorize them in-game, of course.
  2. Also at character creation, the displayed list of spells known for clerics, druids etc (after you've chosen your spells, on the character sheet) will include all the new spells, not just those at their level. (This is purely cosmetic, especially as you only see that list right at the end; I could edit the UI to hide them, but it seems like overkill.)

3.1.8 How it works

There are two places the new spells need to show up: on the character-generation/levelling up screen where sorcerers/mages/shamans select their known spells, and in the spell books of clerics, druids, paladins, and rangers, who get all spells at a given level automatically.

As for the first: the UI interfaces with the engine during spell-learning through an array called chargen.choose_spells. We simply inject the new spells into that array when the spell-choose menu is opened, through a one-line UI edit (tied to a new function). (At install time, we read the alignment/kit/class unusability flags into a lua array; when we populate chargen.choose_spells, we check that array against the character’s alignment/kit/class, which we have to make some more insertions into ui.menu to detect.) We also have to populate another array that the UI uses to record the names, descriptions and icons of the new spells.

As for the second: we build a bunch of spells that grant the new cleric/druid spells (for each of cleric and druid, one for each alignment and one master spell) and apply the master spell in the CLAB files at level 1. (The master spell grants all spells without alignment restrictions, and then applies the spell of the appropriate alignment.) Then, just for cosmetic purposes, we do a second little UI edit to hide cleric-spellbook spells that are too high level to be cast. (Normally there shouldn’t be any.)

(We could alternatively have granted the spells on a per-level basis, which would save us the UI tweak. I haven’t done this because (a) it’s a lot more hassle; (b) it runs some risk of a slight delay on level-up before you see the new spells in your spellbook; (c) we’re tweaking the UI anyway so there’s not much to be gained by abjuring UI tweaks here.

3.1.9 Edits to ui.menu

  • The 'on_open' section of the CHARGEN_CHOOSE_SPELLS menu is edited to run the function 'dwUpdateChooseSpells()', which adds the additional spells into the engine-created array chargen.choose_spells
  • The refreshPriestBook() function is edited so that priest spells are only shown at a given level if the character can memorize spells at that level.
  • The 'ui_detect_class_kit' function is run. This edits ui.menu so that the dwFindClassKit() function is called before the OnDualClassButtonClick() action is triggered and so that the character's alignment is recorded before the OnAlignmentButtonClick() action is triggered.