Jump to content

guyudennis

Members
  • Posts

    82
  • Joined

  • Last visited

Posts posted by guyudennis

  1. I first observed this when switching mages to sorcerers, that the mage’s known spells are treated as the sorcerer’s known spells, so the sorcerer would end up with more spells that he/she should legally have.

    A more severe case happened to me recently when I tried switching Faldorn to a shaman (I was at level 6-7ish), that she ran out of spells to choose during the leveling process after the class change, and the UI hang there with no way to go out. I had modded my game so druids have a slightly lower number of spells available, which could be a contributing factor, but on the other hand I also had IWD spells and SCS new spells installed, which should more than even out my tweak to the Druid spell availability. So it looks like a bug that can trigger for edge cases.

    Maybe for sorcerers and shamans an additional background action can be performed to first clear the known spells before leveling up?

  2. Wouldn't an ini file moot the problem of ACTION_READLN?

    I somehow always thought of ini files as a hard perquisite for batch-mode...

    for what's its worth, my own private mod is structured in such a way that in the tp2 only the top-level installation choices are present (ie. "Druid Revisions"), then all the class features are individually turned on/off or chosen (when more than one choices are presented) through a settings.ini file. In this sense the ini file becomes mandatory rather than optional or "only for advanced users".

  3. Here is yet another "spell-2-innate" type of function, which is based on Subtledoctor's original function, while the op146/148 target matching portion is based on Luke's MAKE_SPELL-LIKE_ABILITY.

    Spoiler
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // Borrowed and adapted from Subtledoctor's 5E Spellcasting: create wrapper spell from any existing payload spell //
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    DEFINE_ACTION_FUNCTION PAYLOAD_SPELL_TO_WRAPPER_SPELL
      INT_VAR
        wrapper_type = 4 // default to innate (0 - special, 1 - wizard, 2 - priest, 3 - psionic, 4 - innate, 5 - bard song)
        wrapper_location = 4 // default to ability (0 - none, 1 - weapon, 2 - spell, 3 - item, 4 - ability)
        op148_fix = 0 // default to no fix (0 - no fix, 1 - fix spell range for op148)
      STR_VAR
        payload_spell = ~~
        wrapper_spell = ~~
        wrapper_name = ~~ // default to empty (payload spell name)
        wrapper_desc = ~~ // default to empty (payload spell description)
      RET
        debug_msg
    BEGIN
      COPY_EXISTING ~%payload_spell%.spl~ ~override/%wrapper_spell%.spl~
        READ_STRREF NAME1 payload_name
        PATCH_IF (STRING_LENGTH ~%wrapper_name%~ > 0) BEGIN
          SAY NAME1 ~%wrapper_name%~
        END
        READ_STRREF NAME1 wrapper_name
        PATCH_IF (STRING_LENGTH ~%wrapper_desc%~ > 0) BEGIN
          SAY UNIDENTIFIED_DESC ~%wrapper_desc%~
        END
        WRITE_SHORT 0x1c wrapper_type
        PATCH_IF wrapper_type = 4 BEGIN
          WRITE_BYTE 0x27 0 // no sectype for innates
        END
        LPF ALTER_SPELL_HEADER INT_VAR location = wrapper_location END
        READ_LONG 0x64 abil_offset
        READ_SHORT 0x68 abil_number
        READ_BYTE (abil_offset + 0x0c) abil_target
        READ_SHORT (abil_offset + 0x0e) abil_range
        WHILE (abil_number > 1) BEGIN
          SET abil_number = (abil_number - 1)
          READ_SHORT (abil_offset + 0x10 + (0x28 * abil_number)) abil_minlv
          LPF DELETE_SPELL_HEADER INT_VAR header_type = "-1" min_level = abil_minlv END // delete all but the 1st ability header
        END
        PATCH_IF wrapper_type = 4 BEGIN
          WRITE_SHORT (abil_offset + 0x12) 0 // instant casting (speed) for innates
        END
        WRITE_SHORT (abil_offset + 0x26) 1 // set projectile to none
        LPF DELETE_EFFECT END // delete all existing spell extended effects
        PATCH_MATCH abil_target WITH
          4 BEGIN // any point within range
            LPF ADD_SPELL_EFFECT INT_VAR opcode = 148 target = 1 parameter2 = 1 timing = 1 STR_VAR resource = EVAL ~%payload_spell%~ END // cast spell at point: payload spell
            PATCH_IF (op148_fix = 1) BEGIN
              PATCH_IF (abil_range < 35) BEGIN
                PATCH_IF (abil_range > 4) BEGIN
                  LPF ALTER_SPELL_HEADER INT_VAR range = (abil_range - 3) END
                END
                PATCH_IF (abil_range < 5) BEGIN
                  LPF ALTER_SPELL_HEADER INT_VAR target = 5 END
                END
              END
            END
          END
          1 BEGIN // living actor
            LPF ADD_SPELL_EFFECT INT_VAR opcode = 146 target = 2 parameter2 = 1 timing = 1 STR_VAR resource = EVAL ~%payload_spell%~ END // caste spell at creature: payload spell
          END
          5 7 BEGIN // caster
            LPF ADD_SPELL_EFFECT INT_VAR opcode = 146 target = 1 parameter2 = 1 timing = 1 STR_VAR resource = EVAL ~%payload_spell%~ END // caste spell at creature: payload spell
          END
          DEFAULT
            PATCH_FAIL ~PAYLOAD_SPELL_TO_WRAPPER_SPELL: ability target matching failure~
        END
      BUT_ONLY
      OUTER_TEXT_SPRINT debug_msg ~Wrapper spell %wrapper_spell% (%wrapper_name%) of type %wrapper_type% created at location %wrapper_location% to launch payload spell %payload_spell% (%payload_name%).~
    END

     The function would make a "wrapper spell" that would in turn launch a "payload spell", regardless if it's uninterruptible or not.

    For the wrapper spell, one can further define spell type, spell location, spell name, and spell description. Otherwise, it would retain the payload spell's most information intact (sectype would be changed to none and casting speed to zero in case of innate type), have only one ability header without any projectile, and have only one extended effect to launch the payload spell (via 146/148 depending on the payload spell's targeting).

    There is an additional boolean variable to enable Subtledoctor's original fix to op148's targeting. I could not reproduce the intended issue in game myself, so I made it default to no-fix.

    Sample usage:

    Spoiler
    LAF PAYLOAD_SPELL_TO_WRAPPER_SPELL
      INT_VAR wrapper_type = 4
              wrapper_location = 4
      STR_VAR payload_spell = EVAL ~sppr731~ // fire elemental transformation
              wrapper_spell = EVAL ~spdr701~
              wrapper_name = EVAL ~optional unique wrapper name~
              wrapper_desc = EVAL ~optional unique wrapper description~
      RET debug_msg
    END
    PRINT ~%debug_msg%~

    Thanks go to @subtledoctor and @jmerry for their patience and help.

  4. Final check before I post it in the other thread? @subtledoctor @jmerry

    /////////////////////////////////////////////////////////////////////////////////////////////////////
    // Borrowed and adapted from 5E Spellcasting: create wrapper spell from any existing payload spell //
    /////////////////////////////////////////////////////////////////////////////////////////////////////
    
    DEFINE_ACTION_FUNCTION PAYLOAD_SPELL_TO_WRAPPER_SPELL
      INT_VAR
        wrapper_type = 4 // default to innate (0 - special, 1 - wizard, 2 - priest, 3 - psionic, 4 - innate, 5 - bard song)
        wrapper_location = 4 // default to ability (0 - none, 1 - weapon, 2 - spell, 3 - item, 4 - ability)
      STR_VAR
        payload_spell = ~~ // default to empty
        wrapper_spell = ~~ // default to empty
        wrapper_name = ~~ // default to empty (payload spell name)
        wrapper_desc = ~~ // default to empty (payload spell description)
      RET
        debug_msg
    BEGIN
      COPY_EXISTING ~%payload_spell%.spl~ ~override/%wrapper_spell%.spl~
        READ_STRREF NAME1 payload_name
        PATCH_IF (STRING_LENGTH ~%wrapper_name%~ > 0) BEGIN
          SAY NAME1 ~%wrapper_name%~ // set spell name
        END
        READ_STRREF NAME1 wrapper_name
        PATCH_IF (STRING_LENGTH ~%wrapper_desc%~ > 0) BEGIN
          SAY UNIDENTIFIED_DESC ~%wrapper_desc%~ // set spell description
        END
        WRITE_SHORT 0x1c wrapper_type // set spell type
        PATCH_IF wrapper_type = 4 BEGIN
          WRITE_BYTE 0x27 0 // no sectype for innates
        END
        LPF ALTER_SPELL_HEADER INT_VAR location = wrapper_location END // set ability location
        READ_LONG 0x64 abil_offset
        READ_SHORT 0x68 abil_number
        READ_BYTE (%abil_offset% + 0x0c) abil_target
        READ_SHORT (%abil_offset% + 0x0e) abil_range
        WHILE (%abil_number% > 1) BEGIN
          SET abil_number = (%abil_number% - 1)
          READ_SHORT (%abil_offset% + 0x10 + (0x28 * %abil_number%)) abil_minlv
          LPF DELETE_SPELL_HEADER INT_VAR header_type = "-1" min_level = abil_minlv END // delete all but the 1st ability headers
        END
        WRITE_SHORT (%abil_offset% + 0x26) 1 // set projectile to none
        LPF DELETE_EFFECT END // delete all existing spell extended effects
        PATCH_IF (%abil_target% = 4) BEGIN // if ability target is any point within range
          LPF ADD_SPELL_EFFECT INT_VAR opcode = 148 target = 1 parameter2 = 1 timing = 1 STR_VAR resource = EVAL ~%payload_spell%~ END // cast spell at point: payload spell
          PATCH_IF (%abil_range% < 35) BEGIN
            PATCH_IF (%abil_range% > 4) BEGIN
              LPF ALTER_SPELL_HEADER INT_VAR range = (%abil_range% - 3) END // workaround to engine wierdness
            END
            PATCH_IF (%abil_range% < 5) BEGIN
              LPF ALTER_SPELL_HEADER INT_VAR target = 5 END	// set ability target to self; might need to carve out exceptions, e.g. burning hands
            END
          END
        END ELSE BEGIN
          LPF ADD_SPELL_EFFECT INT_VAR opcode = 146 target = 2 parameter2 = 1 timing = 1 STR_VAR resource = EVAL ~%payload_spell%~ END // caste spell at creature: payload spell
        END
      BUT_ONLY
      OUTER_TEXT_SPRINT debug_msg ~Wrapper spell %wrapper_spell% (%wrapper_name%) of type %wrapper_type% created at location %wrapper_location% to launch payload spell %payload_spell% (%payload_name%).~
    END

    The function would make a "wrapper spell" that would in turn launch a "payload spell".

    For the wrapper spell, one can further define spell type, spell location, spell name, and spell description. Otherwise, it would retain the payload spell's most information intact (sectype would be changed to none in case of innate type), have only one ability header without any projectile, and have only one extended effect to launch the payload spell (via 146/148 depending on the payload spell's targeting).

    I toyed around the idea of also giving the option to change wrapper spell flags and exclusion flags, but could not get my head around how the default value would be set, since both none and all could be potentially what one wants to set exactly. Distinguishing BAND or BOR is another complication.

    I hope I finally got the targeting of 146 and 148 correct respectively... please help me double check.

    Sample usage:

    ACTION_FOR_EACH filename IN ~sppr731~ ~sppr732~ BEGIN // payload spells: fire elemental transformation, earth elemental transformation
      LAF PAYLOAD_SPELL_TO_WRAPPER_SPELL
        INT_VAR wrapper_type = 4
                wrapper_location = 4
        STR_VAR payload_spell = EVAL ~%filename%~
                wrapper_spell = EVAL ~%filename%w~
                wrapper_name = EVAL ~%filename% wrapper~
                wrapper_desc = EVAL ~%filename% blah blah blah~
        RET debug_msg
      END
      PRINT ~%debug_msg%~
    END
  5. Thanks.

    But here I think you meant to say 146 instead of 148 (which according to IESDP should just have target = 1) ?

    5 hours ago, jmerry said:

    You're talking about the target of the 148 effect itself, which does matter.

    Target = 2 means "cast the subspell on the ability's target".

    Target = 1 means "cast the subspell on the original caster, no matter what the ability's target is".

    For this application of replacing spell X with a wrapper spell Y that casts X as a subspell, we definitely want the former. For example, if the spell we're converting is Magic Missile, using target = 2 would mean that we click on an enemy, cast the original Magic Missile at them, and the missiles go and do damage. Using target = 1 would mean that we click on an enemy, cast the original Magic Missile on ourselves, and ouch.

    Other than that, two more observations/questions regarding @subtledoctor's example code:

    1. The LPF DELETE_EFFECT INT_VAR match_probability2 = 0 END bit does not clean out all existing spell effects as intended, as there's spells (ie conjure XXX elemental) whose spell effects have non-zero probability2 settings. (Probably a reading of effect# offset coupled with a loop to delete them all would be a more robust solution?)
    2. In case the payload spell has multiple ability headers for different levels, should the wrapper spell inherit all these headers (with each header just a 146/148)? I guess it doesn't really matter in practice, but in principle we only need one ability header for the wrapper spell, right?
  6. On 2/6/2023 at 12:25 AM, subtledoctor said:

    Only if it uses op148. No need for this with op146. 

    EDIT - and the difference is not necessarily [AoE vs. non-AoE]. You use op148 specifically when the original spell has a value of 4 in the header target field. (Some AoE spells use different targeting. e.g. Hold Person. To do this with Hold Person you would use op146.)

    EDIT 2 - here’s some code that does this in Weidu. Lines 2179-2210. 

    Coming back to this as I finally got around to implement this; one question regarding your example code: for op146, why do you use target = 2 (instead of 1) and timing = 9 (instead of 1)? If I look at IESDP spell file format, it says the target does not matter here because it's cast as a subspell. The timing bit I guess comes down to my lack of understanding of the differences between 1 and 9...

  7. Thanks for the quick reply.

    I'm tweaking druid class, and have a bunch of ini options to enable/disable certain class features.

    So in the druid tweak tpa file, which is run fairly early since it's modifying a base class,I have this kind of code:

    LAF GET_KIT_STRREF_EX STR_VAR kit_name = ~DRUID~ RET kit_strref END // custom function to return druid class description
    OUTER_SPRINT help_druid @15 // my custom druid description as the basis
    OUTER_PATCH_SAVE help_druid ~%help_druid%~ BEGIN // becasue we want later components to be able to inherit an already-changed description
      // do some textual replacements here based on different ini options
    END
    STRING_SET_EVALUATE %kit_strref% ~%help_druid%~ // set it here already in case only this particular component is installed

    Then in another component, say an item tweak component, which is run towards the tail-end of the installation process to ensure all items are patched, I have this kind of code:

    ACTION_IF (druid_short_bow = 1) BEGIN
      LAF GET_KIT_STRREF_EX STR_VAR kit_name = ~DRUID~ RET kit_strref END
      ACTION_IF (NOT (VARIABLE_IS_SET ~help_druid~)) BEGIN // in case the druid class tweak component from earlier was not installed
        ACTION_GET_STRREF %kit_strref% help_druid // so we pull the default one instead
      END
      OUTER_PATCH_SAVE help_druid ~%help_druid%~ BEGIN
        REPLACE_TEXTUALLY ~\(May only use the following weapons: scimitar, dagger, club, spear, quarterstaff, dart, sling\)~ ~\1, shortbow~
      END
      STRING_SET_EVALUATE %kit_strref% ~%help_druid%~
    END

    This runs okey, since the item tweak code can basically detect any previous changes to the druid class description made by any earlier components and have the default one as fallback, and dynamically modify the description further, and still saves the description as an variable just in case any other component might want to modify the same description later again.

    However, as you can see, even though it's utilizing a tra, the code in the item tweak section is not really translation-friendly. I know I could set up the two strings in the REPLACE_TEXTUALLY portion as two further references in the tra file, but then I need to SPRINT another two variables against these two references just to satisfy syntax compatibility for REPLACE_TEXTUALLY. So I'm wondering if there's a more elegant way of doing it.

    PS. I am aware I could simply break down the original class description (@15) in my tra into several pieces, then I can have all scenarios covered and perhaps avoid the need to do any textual modifications at all, but it involves even more (in my opinion needlessly consumed) variables here:

    LAF GET_KIT_STRREF_EX STR_VAR kit_name = ~DRUID~ RET kit_strref END
    OUTER_SPRINT help_druid1 @15
    OUTER_SPRINT help_druid2 @16
    OUTER_SPRINT help_druid3 @17
    ACTION_IF (druid_3e_alignments = 1) BEGIN
      OUTER_SPRINT help_druid4 @19
    END ELSE BEGIN
      OUTER_SPRINT help_druid4 @18
    END
    OUTER_SPRINT help_druid5 @20
    OUTER_SPRINT help_druid ~%help_druid1%%WNL%%help_druid2%%WNL%%help_druid3%%WNL%%help_druid4%%WNL%%help_druid5%~
    STRING_SET_EVALUATE %kit_strref% ~%help_druid%~

     

  8. Apart from using (OUTER)SPRINT my_var @tra_ref, are there any other ways to treat tra references as variables directly?

    I'm trying to manipulate tra references conditionally at install time, using SPRINT and OUTER_PATCH_SAVE with a variable acting as a manipulateable placeholder for a tra reference, but I'm wondering if there're any other ways to let my alter the tra ref directly without the intermediate variable.

  9. Say, if I wanted to change a particular (existing/new) priest kit so that his/her *divine* enchantment spells are harder to save the same way as his/her fellow wizardary specialist enchanter functions in that respect, how would I do it? There does not seem to have a particular opcode to apply these saving-throw-per-school-bonus to creatures directly...

  10. 9 hours ago, subtledoctor said:

    Okay, I have added this to the various options in Tome & Blood. Regardless which opposition school system you prefer (IWD/BG2/PnP), there is an option to keep spells of up to 3rd level as universal and available to anyone.

    This also means for the majority of BG1 portion, players don't see much difference (apart from saving throws) between the specialist mages, unless he/she also install the specialist tweaks component. Don't know if you want to mention that in the description of this component.

  11. Ok...call me stupid, or I'm not reading IESDP right...but op326 seems to only reference SPLPROT.2DA, while completely ignoring STATS.IDS?

    Let's say proficiency showbow is listed as 105 in STATS.IDS, so I did this:

    LPF ADD_SPELL_EFFECT INT_VAR opcode = 326 target = 1 parameter1 = 0 parameter2 = 0x00000069 timing = 1 resist_dispel = 2 STR_VAR resource = ~MY_CUSTOM_SHORTBOW_233_SUBSPL~ END

    But when I check my main spell with 326 in NI later. it shows para2 = Class, as is listed in SPLPROT.2DA.

    What am I missing?

  12. Regarding op233...

    I want to apply an ability through CLAB that (a) gives a kit 1 proficiency point if he doesn't yet have it, but (b) at the same time do nothing to an existing proficiency if he already has it.

    When I use Set if Higher in para2, it achieves (a) but at the same time force overwrites any existing proficiency also back to 1. On the other hand, if I use Increment it increments everything, effectively also achieving (a), but it has the undesired side-effect of increasing any existing proficiency by 1, possibly exceeding what he can legally achieve through normal means.

    What I am missing? Or is op233+Set_if_higher somehow bugged?

    LPF ADD_SPELL_EFFECT INT_VAR opcode = 233 target = 1 parameter1 = 0x00000001 parameter2 = 0x00000059 timing = 9 resist_dispel = 2 END // modify proficiencies: bastard sword (89)
  13. Hi @argent77,

    I'll post here since I don't have an account in any other BG modding forums.

    I think there's an incompatibility between the mods in the title. What I found out was: if I install Reveal Hidden Gameplay Options with the 1st choice "install all options at once" (no need to install the hotkey update component), and afterwards install IWDification and/or SCS, then when I start a game later the executable (SoD on MacOS) would complain about syntax errors within bgee.lua and error out. WeiDu changelog pointed out that all three mods make changes to bgee.lua, and at least with SCS it's unavoidable since the change is tied to its "initialize" process.

    The errors are usually in the "cheat menu" section for arcane and divine spells, usually with missing comma or the additional rows named "DW_INSERT1/2/3"... Sometimes either IWDification or SCS together with Reveal Hidden Gameplay Options is enough to bring out the syntax errors, while in other times it would need all three mods installed to bring out the errors.

    I could "fix" it by manually adding back the commas and deleting the "DW_INSERT..."s in bgee.lua, but the remaining content still looked messy and clearly missing entries.

    I also found out if the "improved cheat menu" component alone from Reveal Hidden Gameplay Options is installed *after* IWDification's arcane/divine spell packs and/or SCS, there's no longer any syntax errors in bgee.lua.

    I thought you might want to know this and perform further analysis, since the incompatibility seems to be brought up only with the v4.x of Reveal Hidden Gameplay Options with which the "improved cheat menu" component was introduced.

    Cheers~

  14. Thank you, both.

    So in a nutshell, I should:

    1. duplicate an existing HLA spl
    2. edit the duplicate so that:
      1. its spell type is changed to innate
      2. optional: change its location to 4 (innate)
      3. keep only the 1st extended header and delete the rest if there are more than 1 headers; this preserves the original extended header's target, range, casting time and projectile (if any), etc
      4. modify the extended header so that its range is (original range minus 3)
      5. delete all the (extended) spell effects in the remaining extended header, and add an op146 (if the original HLA was targeting a creature [including self-targeting]) or an op148 (if the original HLA was an AoE)
      6. make sure the op146/op148 has SET target = 1 (self) parameter2 = 1 timing mode = 1 SPRINT resource = ~original HLA~

    Correct? Did I miss anything else that's worth changing?

  15. On 1/29/2023 at 7:58 PM, jmerry said:

    The one possible "fix" I can think of is to create a new innate spell (which is then granted by the HLA) that casts the original spell using opcode 146/148 in mode 1. The original spell is still a mage or priest spell, so it uses the mage or priest caster level.

     

    On 1/30/2023 at 12:28 PM, subtledoctor said:

    This is actually a perfectly reasonable solution and probably worth doing. I would only caution DavidW if he decides to do this, that op148 has weird issues with spell range, so the innate ability should have a range 3 less than the underlying wizard or priest spell it casts. 

    Not to hijack the SCS thread, so I ask my questions here.

    @jmerry by “mode 1” you meant timing mode = 1 or para2 = 1?

    @subtledoctor I don’t really catch what you meant here?

    Within the scope of what we were talking about, making innate wrappers to cast the original HLA spells, I would guess the target of 146/148 would always be Self? If that’s correct, then what’s the difference between those Opcodes, again within the scope as mentioned above?

    And what about para1 and para2? My guess is para1 = 0 and para2 = 0, no matter what opcode (146/148) we use or if the actual HLA is self-targeting or AoE, correct?

×
×
  • Create New...