Skip to content

[Tool] UI-based Spell Learning for Sorcerers/Shamans

kjeronkjeron Member Posts: 2,367
edited March 2021 in General Modding
This function is designed to implement a UI-based Spell selection system, as an alternative to dialog-based Spell Selection, utilizing the Sequencer/Contingency spell selection menu to learn arcane or divine spells. Innate abilities "can" be learned this way as well, but cannot currently be tracked, and thus not supported.

I have kept the changes it makes to UI.MENU to an absolute minimum, to reduce the chances of incompatibility, but any UI mod that outright replaces UI.MENU will obviously not be compatible.

The function requires a 7-character filename and creates the following files:
  • '%filename%.SPL' - Usable at-will innate ability to access spell menu.
  • '%filename%D.SPL' - Sequencer activation subspell, activated automatically.
  • '%filename%X.EFF' - Sequencer creation EFF
  • '%filename%X.2DA' - Primary spell list, identical in form to the spell lists for Opcode 214 (Select Spell). If specified, the function will automatically populate the list with appropriate "default" spells (SPPR/SPWI), based on HIDESPL.2DA and the specified exclusion flag. Spells may be manually appended to the list as desired, and spell level is automatically detected and filtered by the UI. Spells omitted or removed from this list will not count towards the characters number of spells known (such as the Shaman's bonus spells).
All spells that can be learned will require an "L" suffixed subspell, using the same spellbook Icon and containing a single opcode 171 effect referencing the main spell. These will be created automatically for SPWI/SPPR spells added to the primary spell list by the function.

The function call is as such:

INCLUDE	~%filepath%/SEQUENCER_MENU.TPA~
LAF CREATE_SEQUENCER_MENU
  INT_VAR
    tip = stringRef
      //  String Reference from dialog.tlk, this is the Instructions displayed in the Sequencer Screen
    name = stringRef
      //  String Reference from dialog.tlk, this is the name of the ability used to open the Sequencer Menu
    desc = stringRef
      //  String Reference from dialog.tlk, this is the description for the ability used to open the Sequencer Menu
    class = value
      //  1 for arcane, else divine, doesn't matter if using custom spell lists.
    maxlevel = value
      //  If non-zero, only spells up to this level will be automatically added to the primary spell list.  
      //  If zero, no spells will be added (to add them manually or use custom mutlilists).
      //  If omitted, default maximum is used (9 for arcane, 7 for divine).
    exclude = value
      //  If any exclusion flags at offset 0x1e of a spell match this value, it will be excluded from the primary spell list.
    multilist = value
      //  If non-zero, function assumes that primary spell list is instead a list of other 2da files each with their own spell lists, all of which will be made available.
      //  Intended for School/Sphere systems, so that each school/sphere can have it's own list, while kits would just have a list of those lists as appropriate.
      //  Such sub-lists 'must' have an accompanying spell of the same name.
      //  If "Sphere1.2da" is a list of spells, "Sphere1.spl" must exist as a spell file, though it's contents are irrelevant.
    column = value
      //  for use with custom spelllists, column of file to use.
    alignment = value
      //  If non-zero, function enforces that spell selection should respect alignment restrictions (currently only if they are provided by an external source - it cannot read them through the UI).
    global = value
      //	If non-zero, function will read known spells in LOCALS and GLOBAL variables.  Activating ability will take up to 1 second before menu pops up while variables are checked/set.
      //	If 1, Subspells to learn spells will need to be setup manually, since this mode is only intended for spells that cannot be "known" in the normal manner.
      //	If 2, subspells are still created as normal.
      //	If the spells LOCALS variable is set to 1, it will be considered "known" and count towards the known spell limit.
      //	If the spells LOCALS variable is set to 2, it will be considered "known" but not count towards the known spell limit.
      //	The LOCALS/GLOBAL variable for each spell is it's RESREF.

  STR_VAR
    resref = ~filename~
      //  7-character or less filename for Spell Selection ability
    icon = ~filename~
      //  Icon for Spell Selection ability
    spelltable = ~filename~
      //  2DA table (no extension) for Known Spells, defaults to SPLSRCKN(.2DA) or SPLSHMKN(.2DA) if not specified.
      //  If used, SPLSRCKN.2DA and/or SPLSHMKN.2DA will be cloned as SPLSRCKN_BACKUP.2DA and SPLSHMKN_BACKUP.2DA for future referencing,
      //  while the main files are zeroed out so that normal spell selection is disabled.
      //  The specified 2DA File must exist in game.
    spelllist = ~filename~
      //  2da file (no extension) with custom spell list, only spells in this file will be added, still subject to maxlevel.
      //  First column can contain either IDS label or RESREF of a spell
      //  Other columns contain either '1' (yes) or '0' (no).
      //  The specified 2DA File must exist in game.
    title = ~string~
      //  String to display in main "Title" slot of Sequencer Screen, added to appropriate "L_%EE_LANGUAGE%.LUA" file.
    label = ~string~
      //  String to display in sub "Title" slot of Sequencer Screen, added to appropriate "L_%EE_LANGUAGE%.LUA" file.
    attribute = ~value/string~
      //  1, STR, STRENGTH, 2, DEX, DEXTERITY, 3, CON, CONSTITUTION, 4, INT, INTELLIGENCE, 5, WIS, WISDOM, 6, CHA, CHR, CHARISMA
      //  Is not case sensitive.  Omit if not needed.
    attrtable = ~filename~
      //  2DA table (no extension) with bonus/penalty to Known spells based on specified attribute.
      //  Format is identical to the priests bonus wisdom spell table, MXSPLWIS.2DA.
      //  Table can contain postive and negative values.  A sufficiently large penalty can be used to prevent selecting spells at the specified attribute score.
      //  The specified 2DA File must exist in game.
      //  Omit if not needed.
    subspell = ~character~
      //  Default = ~L~
      //  Subspells for each learnable spell will be created, appended with this character (SPPR101.SPL -> SPPR101L.SPL)
      //  Must be a single character, must not already be in use by spells (A,B,C,D,F,P are not permitted)
    blacklist = ~array name~
      //	Array ~%blacklist%~ will be scanned over for IDS spell labels as well as spell resrefs.
      //	All valid spells listed will be ommitted from the auto-generated spell lists.
      //	No function when using pre-built spell lists.
    custom = ~lua function string~
      //       If this variable is set, only the core function is installed, and the sequencer "resref" is set to call the function defined in this variable.
      //       No files are created related to "resref".
      //       This is only meant to open access to the sequencer/contingency spell lists to other ideas.
END
The following will mimic the default sorcerer/shaman spell options (provided proper tra references):

OUTER_SPRINT	title @293	OUTER_SPRINT	action @294
LAF	CREATE_SEQUENCER_MENU
	INT_VAR	class = 2	maxlevel = 7	exclude = 0x80000000	tip = RESOLVE_STR_REF	(@295)	name = RESOLVE_STR_REF	(@293)	desc = RESOLVE_STR_REF	(@293)
	STR_VAR	resref = ~SPLSHMK~	icon = ~SPPR316B~	title	action
END
OUTER_SPRINT	title @290	OUTER_SPRINT	action @291
LAF	CREATE_SEQUENCER_MENU
	INT_VAR	class = 1	maxlevel = 9	exclude = 0x00004000	tip = RESOLVE_STR_REF	(@292)	name = RESOLVE_STR_REF	(@290)	desc = RESOLVE_STR_REF	(@290)
	STR_VAR	resref = ~SPLSRCK~	icon = ~SPPR316B~	title	action
END
The following is optional code to flag a spell that is on the list of available choices has been given for "free", not counting against their known limit. Apply (w/ Timing Mode 9) the EFF file to the creature when the spell is gained, to prevent it from counting against their spell limit. "SPELL_RES" should be the resref of this "bonus" spell. "%resref%" should be the same string that was passed to the CREATE_SEQUENCER_MENU function. Note that using this will result in the character having a permanent "Spell Sequencer Active" portrait icon.
APPEND	~BGEE.LUA~	~mageBookStrings['%resref%'].title = mageBookStrings['%resref%X'].title~
CREATE	EFF	~(anything)~	WRITE_LONG	0x10	256	WRITE_LONG	0x2c	100	WRITE_ASCII	0x30	~SPELL_RES~
	WRITE_LONG	0x60	1	WRITE_ASCIIE	0x94	~%resref%~

Screenshots:



Not implemented, but possible:
  • Multiclass spell learning. Currently it only reads the first-class Level, the others are accessible, but more complicated to convert to an integer.
Implemented, but not pictured: Update "Spells: X/Y" to display the current/maximum spells per level during selection. Unfortunately this line is not present in IWDEE's sequencer screen.

I have tested the function to work for the current patch cycle (v2.5.17.0, 2.5.16.6). However, this bug is still preset in IWDEE, limiting its viability.
Post edited by kjeron on
«134

Comments

  • [Deleted User][Deleted User] Posts: 0
    edited March 2019
    The user and all related content has been deleted.
    Post edited by [Deleted User] on
  • kjeronkjeron Member Posts: 2,367

    Except, it would be nice to arbitrarily define the available spells on a per-kit basis, and have the code generate the redulting .2da lists. Say, from an easy-to-read file.

    I can easily add support for such an arbitrary spell list, such that you just feed it the list filename and a column number. 1st/header column an IDS label or resref, all other columns a 1 or 0.
  •  TheArtisan TheArtisan Member Posts: 3,277
    O_O

    OH. MY GOD. This is what I've needed since forever. Thank you so much!
  • [Deleted User][Deleted User] Posts: 0
    edited November 2018
    The user and all related content has been deleted.
  • kjeronkjeron Member Posts: 2,367

    Adapting this to multiclass sorcerers will be more difficult, since they need to do more than just 'know' a spell. But it will be worth the effort!

    The 'L' suffix subspells it creates will not overwrite an existing file, so you can safely create such file before, or patch it after, with whatever other effects it may need.

    Exactly! That way something like Tome & Blood can add a bunch of sorcerer kits - Favored Soul, Sylvan Disciple, Revenant Disciple, different dragon disciples - and control their available spells by defining them in a single 2da table. :smiley: Then fire the function and they will all automatically assemble themselves. (Including priest/innate spells will add a wrinkle but we can inline a little function to create a mage spell that casts the priest spell.)

    It's already in, and will automatically create the 'L' suffixed subspells for such lists. However, it does not distinguish spell class for these lists, as I do not want to decide how such spells get cloned. So you would need to:
    - Clone all spells in the manner desired. (Wizard -> Priest or Priest -> Wizard)
    - Replace the labels in the table with the cloned resrefs. (SET_2DA_ENTRY or REPLACE_TEXTUALLY)
    - Run the function on the table for each kit, specifying their column.
  • kjeronkjeron Member Posts: 2,367
    I'll be gone til the end of next week, but I'll look into any issues or suggestions when I get back.
  • kjeronkjeron Member Posts: 2,367
    Added support for attribute-based modifiers (or minimums) for known spells by spell level, details in first post.
    Reworked some code, but using the new version should not be mandatory unless you want the attribute options. The old and new should be able to function side-by-side in different mods for now.

    @Artemius_I Integration into your shadow magic mod looks quite clean, very nice : B) .
  • GrammarsaladGrammarsalad Member Posts: 2,582
    @kjeron
    What about using the hla selection screen? In the back of my head, I've been thinking about repurposing that for custom spell selection for a while. Would that be feasible?
  • kjeronkjeron Member Posts: 2,367

    @kjeron
    What about using the hla selection screen? In the back of my head, I've been thinking about repurposing that for custom spell selection for a while. Would that be feasible?

    I've made attempts into the HLA screen, but it has a few serious issues:
    - Resources are pulled from an internal index, so you can't just specify whatever you want on the fly, as the sequencer/contingency screen allows.
    - Each class/kit has a hard limit of 256 HLA resources, which populate that index.
    - HLA's cannot be chosen at CHARGEN, only LEVELUP.

    It is certainly be an option for total conversions (3E/4E/5E/other), where these issues can be worked around, but not much else.
  • GrammarsaladGrammarsalad Member Posts: 2,582
    kjeron said:

    ...
    - HLA's cannot be chosen at CHARGEN, only LEVELUP.

    ...

    Ahhh. I ran into this issue as well, but I was convinced that I was doing something wrong and gave up. Bummer
  • AquadrizztAquadrizzt Member Posts: 1,065
    Fantastic work as always kjeron. Much less tedious than the dialog method hack I made for TnB.
  •  TheArtisan TheArtisan Member Posts: 3,277
    Is there any way to make some sort of in-game bonus to spells learned per level? I'm not sure how to explain it, but a user of my Shadow Magic mod brought up this issue (which I was aware of but didn't know how to fix)
    When using the new spell learning system (quite better than the old one, kudos!), there is an issue with shadow scrolls, as they will give you learned spells over the accepted limit so when you level up, you're basically stuck on the spell learning interface as all the results will be incorrect. I made a backup save before using the scrolls, knowing it could break the new system, and it did in the end. Right now those scrolls are simply not possible to use unless you have max level, making them basically useless until the very end of BGEE/BG2EE (I'm assuming the same bug would appear in BG2EE if you used any scrolls and went over the spell limit during BGEE).
  • kjeronkjeron Member Posts: 2,367
    edited January 2019
    AionZ said:

    Is there any way to make some sort of in-game bonus to spells learned per level? I'm not sure how to explain it, but a user of my Shadow Magic mod brought up this issue (which I was aware of but didn't know how to fix)

    There isn't much to work with, but I'll look into it.

    The only methods that I know work involve a permanent 'Spell Sequencer Active' Portrait Icon (Stored Sequencers can be read through the UI).
  • The user and all related content has been deleted.
  • kjeronkjeron Member Posts: 2,367
    Can this do more than just learn spells?  Glancing at the first post, it looks like the learning happens via the L-suffix subspell which has a 171 effect... can we inject some stuff into the function to add more to that spell?  (Specifically, in my case, add an additional 321 effect.)
    You can patch the L-Suffix subspells to do whatever, these are the spells actually getting stored and activated by the sequencer.
    Also: you say that it cannot "track" innate spells, I take it that means it cannot keep track of which spells you know and don't know?  So it wouldn't know which spells <i>not</i> to offer?  In this case, if I want to use this with - totally hypothetically - a thief kit that can cast spells, could I simply add the appropriate wizard spells to the thief?  The mage spellbook is inaccessible and thieves have no casting slots, but they can still have "known spells..." could this function recognize those?
    Yes, mage/priest known spells are tracked whether or not the character can normally cast them.
  • [Deleted User][Deleted User] Posts: 0
    edited January 2019
    The user and all related content has been deleted.
  • kjeronkjeron Member Posts: 2,367

    Also: this is getting further afield, but what does it mean that <i>"Stored Sequencers can be read through the UI"</i> ?

    Would there be some kind of way, via spell/opcode, to read the contents of a sequencer and perform an action on the character?  Like, read the spell levels and if a Minor Sequencer contains two 1st-level spells, apply 2 hit points damage; if it contains two 2nd-level spells, apply 4 hit points damage; etc.?
    The UI can read the contents(quantity, level, name, icon, resref) of any spells stored in a sequencer/contingency that shows up in the "Contingency" button on the Mage spellbook, even if the character doesn't have a mage spellbook.  This means only "proper" sequencers/contingencies, not direct opcode 232/256 effects, unless you flag them as such (requires using EFF fields).
    I not sure how you intend to apply damage to the creature though.
  • RaduzielRaduziel Member Posts: 4,714
    Can this be implemented to DoF's sphere system for Shamans?
  • kjeronkjeron Member Posts: 2,367
    AionZ said:
    Is there any way to make some sort of in-game bonus to spells learned per level? I'm not sure how to explain it, but a user of my Shadow Magic mod brought up this issue (which I was aware of but didn't know how to fix)
    I have not found any alternatives, but I have added support for the aforementioned false sequencers to flag a spell as a "bonus/free" spell.  Instructions are in the file & first post.
    Raduziel said:
    Can this be implemented to DoF's sphere system for Shamans?
    It could be, yes, it would just require building the necessary 2da files instead of a dialogue.
    Each sphere would need an unused filename, and all spells it allows would be added to that 2da file.
    Each kit (and base class) would need an unused filename, and all spheres available to them would have their filename added to that 2da file.  (This 2da would be passed to the function.)
  • [Deleted User][Deleted User] Posts: 0
    edited February 2019
    The user and all related content has been deleted.
  • kjeronkjeron Member Posts: 2,367
    Is this triggered by an innate ability? (Or have I inferred that incorrectly?) If so, could it be made to be more similar to regular level-up menus?
    The spell can be given for manual use or forced. AionZ's Shadow Magic mod forces the spell at level up (I believe it summons an invisible creature that scripts the call, so that it is forcibly repeats until enough spells have been chosen for the current level.)
  • [Deleted User][Deleted User] Posts: 0
    edited February 2019
    The user and all related content has been deleted.
    Post edited by [Deleted User] on
  • kjeronkjeron Member Posts: 2,367
    edited February 2019
    EDIT - the innate ability has a 321 effect referencing a nonexistent spell with an "X" suffix ("resrefX.spl" in the parlance of the .tpa file), and both "resref.spl" and "resrefD.spl" have their ability type set to "unknown (0)." The D spell also has "unknown (0)" for its ability location and target, and its spell type is 0 as well.
    Normal.
    Did I not call the function properly for it to patch UI.menu and/or create the M_xx.lua files?
    A hard CTD when using the ability means the entry to the mageBookStrings is missing (which should be created by the code). This should be towards the end of BGEE.lua (with resref replaced, and tip's number will be different):
    mageBookStrings['resrefX'] = {tip = 73494, title = 'resref_SEQUENCER_TITLE', action = "resref_SEQUENCER_LABEL"}
    
    If it IS present, check UI.menu for this line (it shouldn't be there, it was moved to BGEE.LUA in v2.5):
    mageBookStrings = {
    	// entries
    }
    
    EDIT 2 - hmm, my spelllist.2da file, of necessity, has an extra row with one fewer column than col_count, with references to each kit. That's why my code needs the "(cols - 1)" bit near the beginning.
    Also it might not like the "level" column. Maybe I need to just make a new, separate spelllist for each kit.
    Feed it the column relevant to the highest column count(starting from zero). D5_BARD = col 2.
    EDIT 4 - okay, did that, and it's still crashing. And I noticed a weird thing. When I fed it this spelllist as d5bl48.2da:
    ...the function created this .2da file for use with the sequencer (d5bl48X.2da):
    It omits SPCL701. Dunno why.
    SPCL701.spl doesn't exist in the unmodded game, are you sure it's present in this install?
  • [Deleted User][Deleted User] Posts: 0
    edited February 2019
    The user and all related content has been deleted.
  • kjeronkjeron Member Posts: 2,367
    edited February 2019
    I'm somewhat at a loss then for SPCL701 - The tool only checks if ~%spell_res%.spl~ exists in game, and if it has a valid spell level (1-7/priest or 1-9/wizard).

    edit - try specifying "maxlevel = 9" in the function call, I think it's defaulting to priest level range(1-7) for custom lists.
    Post edited by kjeron on
  • The user and all related content has been deleted.
  • kjeronkjeron Member Posts: 2,367
    Found the issue (case mismatch), but lead to another: both your mod and the tool use -L suffix subspells.
    I'm working on fixing both.
  • kjeronkjeron Member Posts: 2,367
    edited February 2019
    @subtledoctor Tool updated, the only change needed for the function call should be another STR_VAR:
    subspell = ~(character)~
    
    Single alphanumeric character, and cannot be any existing defaults (A,B,C,D,E,F,P). Defaults to "L".
    This will be the subspells it creates to grant the spell.

    Not sure how much of the mod was still WIP, but the priest spells did not show up through the item-ability, as the wizard spells did.
  • The user and all related content has been deleted.
  • kjeronkjeron Member Posts: 2,367
    edited February 2019
    Actually I added -L subspells specifically to be used by the function. I already had spells for "learning" magic, easier to name them for use by your function than to have the function generate them and then patch them into shape.
    When I ran it with the L-suffix spells it came with, no matter what spell I selected, after a second it populated the Innate Ability bar with 2x all level 1 spells. (Casting any of them reduced the rest by 1). This is the spontaneous casting system you created IIRC.
Sign In or Register to comment.