Jump to content

Toss your semi-useful WeiDU macros here


Recommended Posts

I may toss one here, but flawed I think.

Since TobEx expands the use of STATS.IDS to support new hard-coded stats and custom stats, this function aims ease-to-use and work in EE engine as well.
It should be used along with script trigger:
CheckStat(O:Object*,I:Value*,I:StatNum*Stats) for vanilla games (so ToBEx must be installed to make this happen) and CheckSpellState(O:Object*,I:State*splstate) for EE games.

If anyone already have a better function to do this job please let me know.

Codes are here: https://github.com/Sebastian-c4/Cabob_Utility/blob/master/Cabob_Utility/lib/c4_unique_mark.tpa

Borrowed the FJ_SPL_ITM_REINDEX function for file correction, thanks to authors.

Function name: C4_FIND_STAT_SLOT and C4_ADD_UNIQUE_MARK

 

C4_FIND_STAT_SLOT is an action function, checks all creature/item/spell files in game which have effect using opcode #318 (vanilla game) or opcode #328 (EE game), also values in STATS.IDS or SPLSTATE.IDS, find an unused one from 501 to 32767, and append a line with an user defined "identifier"(requred). Return "available_stat" for further usage.

 

LAF ~C4_FIND_STAT_SLOT~ STR_VAR identifier=~requred~ RET available_stat END

 

 

 

C4_ADD_UNIQUE_MARK is a patch function, calls C4_FIND_STAT_SLOT, and uses function CLONE_EFFECT to add an effect to creature/item/spell file with an user defined "identifier"(also requred), other parameters of the "match_" part are same with CLONE_EFFECT.

 

LPF C4_ADD_UNIQUE_MARK
INT_VAR // all CLONE_EFFECT default
check_globals = 1
check_headers = 1
match_opcode = ~-1~
match_target = ~-1~
match_power = ~-1~
match_parameter1 = ~-1~
match_parameter2 = ~-1~
match_timing = ~-1~
match_resist_dispel = ~-1~
match_duration = ~-1~
match_probability1 = ~-1~
match_probability2 = ~-1~
match_dicenumber = ~-1~
match_dicesize = ~-1~
match_savingthrow = ~-1~
match_savebonus = ~-11~
match_special = ~-1~
STR_VAR
identifier=~requred~
match_resource=~SAME~

END

 

 

 

Take mage spell Blur for example:

 


COPY_EXISTING ~spwi201.spl~ ~override~
LPF C4_ADD_UNIQUE_MARK INT_VAR match_opcode=65 STR_VAR identifier=~bluring~ END
BUT_ONLY

in script:

IF
CheckStat(Player1, 1, %bluring%) //vanilla game or
CheckSpellState(Player1, %bluring%) // EE game
THEN
......

 

 

 

Note the I:Value* must be fixed to 1, since CheckSpellState has no value parameter.

Edited by c4_angel
Link to comment

 

C4_FIND_STAT_SLOT is an action function, checks all creature/item/spell files in game which have effect using opcode #318 (vanilla game) or opcode #328 (EE game), also values in STATS.IDS or SPLSTATE.IDS, find an unused one from 501 to 32767, and append a line with an user defined "identifier"(requred). Return "available_stat" for further usage.

SPLSTATE values 256+ are set by the engine for other purposes. (STATS, Bardsong, Backstab, New Opcodes, some pulled straight from the EXE).

Setting them manually has no effect(mostly), but checking them is not reliable.

Link to comment

 

 

C4_FIND_STAT_SLOT is an action function, checks all creature/item/spell files in game which have effect using opcode #318 (vanilla game) or opcode #328 (EE game), also values in STATS.IDS or SPLSTATE.IDS, find an unused one from 501 to 32767, and append a line with an user defined "identifier"(requred). Return "available_stat" for further usage.

SPLSTATE values 256+ are set by the engine for other purposes. (STATS, Bardsong, Backstab, New Opcodes, some pulled straight from the EXE).

Setting them manually has no effect(mostly), but checking them is not reliable.

 

I did test in my mod at 500+, no bug found by now, is there any range already found? 32767 is top...

 

And are there any reliable way?

Edited by c4_angel
Link to comment

There are two gaps of 32 splstates that I haven't been able to map, one at 1664-1695 and one at 4224-4255, and then several semi-random gaps beyond 6240(where it starts pulling from the EXE).

 

SPLSTATE 500, for example, is set by BIT4 of the RESISTFIRE stat.

496 is set by BIT0 of RESISTFIRE, 497 of BIT1, 498 by BIT2, 499 by BIT3, 501 by BIT5 502 by BIT6, 503 by BIT7, 504 by BIT 8, 510 by BIT14, 511 by BIT15, then 512 is BIT0 of RESISTCOLD, and it continues on.

 

There are some usable SPLSTATE ranges within the stats, as some stats cannot carry values other than zero or one (BIT0), leaving the other 15 bits open, such as HELD (1409-1439), PLYMORPHED (1441-1471), ENTANGLE (1473-1503), SANCTUARY (1505-1535), MINORGLOBE (1537-1567), SHIELDGLOBE (1569-1599), GREASE (1601-1631), and WEB (1633-1663).

 

I made a table of all this here, excluding the values above 6240, as it varies depending on game version.

Link to comment

 

I made a table of all this here, excluding the values above 6240, as it varies depending on game version.

 

That's a great table. Thank you very much!

 

And I still may need help in two issues:

1. You mentioned "Spellstates 6240+ are dependent on the game campaign/version, read directly from specific offsets of the EXE. Pattern is 32(set) - 160(emtpy) - 32(set), and repeats semi-consistently up to the max (32767)". If I skip 6240-6271, use 6272-6431, then skip 6432-6463 ...etc, will function as expected?

 

2. Can I use splprot.2da to check them in script ? And how?

Link to comment

 

 

I made a table of all this here, excluding the values above 6240, as it varies depending on game version.

 

That's a great table. Thank you very much!

 

And I still may need help in two issues:

1. You mentioned "Spellstates 6240+ are dependent on the game campaign/version, read directly from specific offsets of the EXE. Pattern is 32(set) - 160(emtpy) - 32(set), and repeats semi-consistently up to the max (32767)". If I skip 6240-6271, use 6272-6431, then skip 6432-6463 ...etc, will function as expected?

 

2. Can I use splprot.2da to check them in script ? And how?

The semi-consistent part is that it occasionally has an extra multiple of 160 empty SPLSTATES between sets. For example, BGSOD v2.3.67.3, starting from 6240, has:

3x(32 - 160 - 32) - 160 - 7x(32 - 160 - 32) - 1600 - (32 - 160 - 32) - (32 - 160 - 32) etc...

But I don't know if every game version has the same pattern off extra space or not. I didn't go very far with it, other than to check from the other end (32767) to see if it was still in effect. The second 32 of each such set has always had the exact same value though, 0x0000000a, using only bits 1 and 3.

 

SPLPROT only works in spells/items/projectiles, and comes with an entry for checking SPLSTATES, entry 110, using Parameter1 to specifcy the spellstate.

Scripts still have to use CheckSpellState().

Edited by kjeron
Link to comment

 

 

 

I made a table of all this here, excluding the values above 6240, as it varies depending on game version.

 

That's a great table. Thank you very much!

 

And I still may need help in two issues:

1. You mentioned "Spellstates 6240+ are dependent on the game campaign/version, read directly from specific offsets of the EXE. Pattern is 32(set) - 160(emtpy) - 32(set), and repeats semi-consistently up to the max (32767)". If I skip 6240-6271, use 6272-6431, then skip 6432-6463 ...etc, will function as expected?

 

2. Can I use splprot.2da to check them in script ? And how?

The semi-consistent part is that it occasionally has an extra multiple of 160 empty SPLSTATES between sets. For example, BGSOD v2.3.67.3, starting from 6240, has:

3x(32 - 160 - 32) - 160 - 7x(32 - 160 - 32) - 1600 - (32 - 160 - 32) - (32 - 160 - 32) etc...

But I don't know if every game version has the same pattern off extra space or not. I didn't go very far with it, other than to check from the other end (32767) to see if it was still in effect. The second 32 of each such set has always had the exact same value though, 0x0000000a, using only bits 1 and 3.

 

SPLPROT only works in spells/items/projectiles, and comes with an entry for checking SPLSTATES, entry 110, using Parameter1 to specifcy the spellstate.

Scripts still have to use CheckSpellState().

 

 

a little confused with the pattern, but try start from 32767 will be a good idea, thank you.

Link to comment

Here are a couple of macros to take advantage of some new EE features.

 

Opcode 331 can be used to randomly summon monsters off a custom 2da table as defined in smtables.2da. cd_new_summon_table, an action function, will add a new reference for you and then return the value for use in your spells:

DEFINE_ACTION_FUNCTION cd_new_summon_table
  STR_VAR descript = "foo"
          2da_file = "foo"
  RET     table
  BEGIN

  COPY_EXISTING ~smtables.2da~ ~override~
    COUNT_2DA_ROWS 2 count
    READ_2DA_ENTRY (count - 1) 0 2 table
    INNER_PATCH_SAVE table ~%table%~ BEGIN
      REPLACE_TEXTUALLY ~^\([0-9]+\).+$~ ~\1~
    END
    SET table += 1

  APPEND ~smtables.2da~ ~%table%_%descript% %2da_file%~

END

Sample usage:

LAF cd_new_summon_table STR_VAR descript = "Giant_insect" 2da_file = cdiinsct RET table END

ADD_SPELL ~iwdification/spl/cdid418.spl~ 1 4 CLERIC_GIANT_INSECT
  LPF ALTER_EFFECT INT_VAR match_opcode = 331 parameter2 = table END

This appends "XX_Giant_insect cdiinsct" (where XX is the next entry available on the table) to smtables.2da, and then allows you to use the variable 'table' to patch it into your opcode 331 calls. Note that it only adds the reference to the 2da file (cdiinsct in this case) and that you will still need to copy over the file yourself.

 

EE also supports new portrait icons, as defined in statdesc.2da. cd_new_portrait_icon, another action function, will find the next available entry and add your strref and bam file to the list.

DEFINE_ACTION_FUNCTION cd_new_portrait_icon
  INT_VAR string = 0
  STR_VAR bam_file = "****"
  RET     icon
  BEGIN

  COPY_EXISTING ~statdesc.2da~ ~override~
    COUNT_2DA_ROWS 3 count
    READ_2DA_ENTRY (count - 1) 0 3 icon
    SET icon += 1

  APPEND ~statdesc.2da~ ~%icon% %string% %bam_file%~

END

And a sample use case:

LAF cd_new_portrait_icon INT_VAR string = RESOLVE_STR_REF(@3070) STR_VAR bam_file = cdia422d RET icon END

ADD_SPELL ~iwdification/spl/cdia422.spl~ 2 4 WIZARD_BELTYNS_BURNING_BLOOD
  LPF ALTER_EFFECT INT_VAR match_opcode = 142 match_parameter2 = 66 parameter2 = icon END

This will add "XX YY cdia422d" to statdesc.2da where XX is the next available entry and YY is the strref you've designated.

 

As above, it returns 'icon' with the value of the new entry for usage in your spells or items, and you still need to copy your BAM.

Link to comment

This macro can be used to sort arrays of numbers or strings in ascending or descending order.

 

 

/**
 * This patch macro sorts an array lexicographically or numerically.
 * Parameters:
 * INT_VAR sort_size        (Optional) Number of elements in the array. Specify negative size to 
 *                                     perform auto-detection. (Default: Auto-detect size)
 * INT_VAR sort_reverse     (Optional) Set to zero to sort in ascending order. Set to non-zero to 
 *                                     sort in descending order. (Default: Sort in ascending order)
 * INT_VAR sort_numeric     (Optional) Set to zero to sort array elements lexicographically. Set 
 *                                     to non-zero to sort numerically. (Default: Sort lexicographically)
 * INT_VAR sort_case        (Optional) Set to zero to ignore case. Set to non-zero to sort case-sensitive.
 *                                     Ignored when sorting numerically. (Default: Ignore case)
 * STR_VAR sort_array_name  (Mandatory) The array's base name.
 */
DEFINE_PATCH_MACRO SORT_ARRAY
BEGIN
  LOCAL_SET idx = 0 LOCAL_SET i = 0 LOCAL_SET j = 0
  LOCAL_SET v1 = 0 LOCAL_SET v2 = 0
  LOCAL_SPRINT s1 ~~ LOCAL_SPRINT s2 ~~
  LOCAL_SET c = VARIABLE_IS_SET ~sort_case~ ? sort_case : 0
  LOCAL_SET n = VARIABLE_IS_SET ~sort_numeric~ ? sort_numeric : 0
  LOCAL_SET r = VARIABLE_IS_SET ~sort_reverse~ ? sort_reverse : 0
  LOCAL_SET s = VARIABLE_IS_SET ~sort_size~ ? sort_size : "-1"

  PATCH_IF (NOT ~%sort_array_name%~ STR_EQ ~~) BEGIN
    // Auto-detect array size
    PATCH_IF (s < 0) BEGIN
      SET idx = 0
      WHILE (idx != "-1") BEGIN
        PATCH_IF (VARIABLE_IS_SET EVAL ~%sort_array_name%_%idx%~) BEGIN
          SET idx += 1
        END ELSE BEGIN
          SET s = idx
          SET idx = "-1"
        END
      END
    END

    // Sort array using "Selection Sort" algorithm
    FOR (j = 0; j < s - 1; ++j) BEGIN
      // Find and mark index of lowest/highest value in remaining sublist
      SET idx = j
      PATCH_IF (n) BEGIN SET v1 = EVAL ~%sort_array_name%_%idx%~ END ELSE BEGIN TEXT_SPRINT s1 EVAL ~%%sort_array_name%_%idx%%~ END
      FOR (i = j + 1; i < s; ++i) BEGIN
        PATCH_IF (n) BEGIN SET v2 = EVAL ~%sort_array_name%_%i%~ END ELSE BEGIN TEXT_SPRINT s2 EVAL ~%%sort_array_name%_%i%%~ END
        PATCH_IF (n && (r && v2 > v1) || (NOT r && v2 < v1)) ||
                 (NOT n && (r && c && ~%s1%~ STRING_COMPARE ~%s2%~ < 0) || 
                           (r && NOT c && ~%s1%~ STRING_COMPARE_CASE ~%s2%~ < 0) ||
                           (NOT r && c && ~%s1%~ STRING_COMPARE ~%s2%~ > 0) ||
                           (NOT r && NOT c && ~%s1%~ STRING_COMPARE_CASE ~%s2%~ > 0)) BEGIN
          SET idx = i
          PATCH_IF (n) BEGIN SET v1 = EVAL ~%sort_array_name%_%idx%~ END ELSE BEGIN TEXT_SPRINT s1 EVAL ~%%sort_array_name%_%idx%%~ END
        END
      END

      // Swap values if needed
      PATCH_IF (idx != j) BEGIN
        PATCH_IF (n) BEGIN
          SET v1 = EVAL ~%sort_array_name%_%idx%~
          SET EVAL ~%sort_array_name%_%idx%~ = EVAL ~%sort_array_name%_%j%~
          SET EVAL ~%sort_array_name%_%j%~ = v1
        END ELSE BEGIN
          TEXT_SPRINT s1 EVAL ~%%sort_array_name%_%idx%%~
          TEXT_SPRINT EVAL ~%sort_array_name%_%idx%~ EVAL ~%%sort_array_name%_%j%%~
          TEXT_SPRINT EVAL ~%sort_array_name%_%j%~ ~%s1%~
        END
      END
    END
  END
END

/** Action macro for sorting arrays. */
DEFINE_ACTION_MACRO SORT_ARRAY
BEGIN
  OUTER_PATCH ~~ BEGIN LPM SORT_ARRAY END
END

 

I had to resort to macros, since WeiDU functions can't return array content yet.

 

Sample code for numeric arrays:

 

 

RANDOM_SEED ~X~
OUTER_TEXT_SPRINT sort_array_name ~my_array~
OUTER_SET sort_numeric = 1  // sort numerically
OUTER_FOR (pass = 0; pass < 10; ++pass) BEGIN
  OUTER_SET sort_size = RANDOM(2 20)    // array size of 2 to 20 elements
  OUTER_SET sort_reverse = RANDOM(0 1)  // randomly sort in ascending or descending order

  // generate random number array
  OUTER_TEXT_SPRINT output ~~
  OUTER_FOR (idx = 0; idx < sort_size; ++idx) BEGIN
    OUTER_SET value = RANDOM(0 100)
    OUTER_SET EVAL ~my_array_%idx%~ = value
    OUTER_TEXT_SPRINT output ~%output%%value% ~
  END

  // print unsorted array
  PRINT ~Array(%sort_size%): %output%~

  // sort array
  LAM SORT_ARRAY

  // print sorted array
  OUTER_TEXT_SPRINT output ~~
  OUTER_FOR (idx = 0; idx < sort_size; ++idx) BEGIN
    OUTER_SET value = EVAL ~my_array_%idx%~
    OUTER_TEXT_SPRINT output ~%output%%value% ~
  END
  PRINT ~Sorted (size=%sort_size%,reverse=%sort_reverse%): %output%~
END

 

Edited by argent77
Link to comment

Action macro that generates JOINABLE_NPC_ARRAY table which can be used to patch joinable NPC CRE files (more reliable method than checking CRE BIO offset)

https://github.com/K4thos/IE-code-repository/blob/master/joinable_npc_array.tpa

Example usage:

LAM JOINABLE_NPC_ARRAY
ACTION_PHP_EACH JOINABLE_NPC_ARRAY AS cre => dv BEGIN
    PRINT ~%cre% => %dv%~ 
    COPY_EXISTING ~%cre%~ ~override~
    //your patching code
END

 

This is excellent, by the way, I'm using it in almost all of my mods now. And I've slightly extended the macro, so that it creates arrays of both joinable and non-joinable NPCs when the macro is run, so you can easily PHP_EACH through either exclusive set of .CRE files at a moment's notice:

 

DEFINE_ACTION_MACRO JOINABLE_NPC_ARRAYS BEGIN
    //PDIALOG.2DA exists in all games
    ACTION_DEFINE_ASSOCIATIVE_ARRAY JOINABLE_NPC_ARRAY_2da BEGIN ~PDIALOG~ => ~~ END
    //Check PDIALOG.2DA file variants referenced in CAMPAIGN.2DA
    ACTION_IF FILE_EXISTS_IN_GAME ~CAMPAIGN.2DA~ BEGIN
        COPY_EXISTING ~CAMPAIGN.2DA~ ~CAMPAIGN.2DA~
            COUNT_2DA_ROWS 32 "cntrow"
            FOR (i = 0; i < cntrow; i = i + 1) BEGIN
                READ_2DA_ENTRY i 11 32 file
                TO_UPPER file
                DEFINE_ASSOCIATIVE_ARRAY JOINABLE_NPC_ARRAY_2da BEGIN ~%file%~ => ~~ END
            END
        BUT_ONLY
    END
    //Generate array with joinable NPC DV
    ACTION_PHP_EACH JOINABLE_NPC_ARRAY_2da AS file => ~~ BEGIN
        ACTION_IF FILE_EXISTS_IN_GAME ~%file%.2da~ BEGIN
            COPY_EXISTING ~%file%.2da~ ~override~
                COUNT_2DA_ROWS 3 "cntrow"
                FOR (i = 1; i < cntrow; i = i + 1) BEGIN
                    READ_2DA_ENTRY i 0 3 "dv"
                    TO_UPPER dv
                    DEFINE_ASSOCIATIVE_ARRAY JOINABLE_NPC_ARRAY_dv BEGIN ~%dv%~ => ~~ END
                END
            BUT_ONLY
        END
    END
    //Generate array with joinable NPC cre files
    COPY_EXISTING_REGEXP GLOB ~.+\.CRE~ ~override~
        READ_ASCII DEATHVAR "dv" (32) NULL
        TO_UPPER dv
        PATCH_IF VARIABLE_IS_SET $JOINABLE_NPC_ARRAY_dv(~%dv%~) BEGIN
            DEFINE_ASSOCIATIVE_ARRAY JOINABLE_NPC_ARRAY BEGIN ~%SOURCE_FILE%~ => ~%dv%~ END
        END
        PATCH_IF NOT VARIABLE_IS_SET $JOINABLE_NPC_ARRAY_dv(~%dv%~) BEGIN
            DEFINE_ASSOCIATIVE_ARRAY NON_JOINABLE_NPC_ARRAY BEGIN ~%SOURCE_FILE%~ => ~%dv%~ END
        END
    BUT_ONLY
END
Link to comment

REPLACE_MULTILINE: Patch function that replaces set or all occurrences of the given regexp pattern in the file with the given string. Use EVAL to perform variable substitution on the string and/or the regexp pattern. Unlike REPLACE_TEXTUALLY the pattern can be multi-line text, even without using regexp. Just like REPLACE_BCS_BLOCK the function ignores pattern whitespace. The function can be also used as a COUNT_REGEXP_INSTANCES alternative with the above mentioned features. Optional PATCH_WARN message is printed if the task could not be performed (pattern not found or different amount of pattern matches than expected).

 

https://github.com/K4thos/IE-code-repository/blob/master/replace_multiline.tpa

 

Mostly useful for UI.MENU patching because REPLACE_BCS_BLOCK doesn't work with text files and we often need to replace multiple lines in this file, preferably ignoring whitespace differences (enters, spaces, tabs). Of course it works with any text file, so can be used on decompiled scripts and dialogs as well.

Edited by K4thos
Link to comment

Based on the discussion over yonder about cloning spells, I used a new function when updating Sword and Fist. This will clone a spell, updating any opcodes in both the original and clone that self-reference as appropriate. It can optionally also convert the newly cloned spell to an innate spell, finally ending the need for this very old, very outdated bit of code from the mists of time*.

 

Since it can potentially need to update the source spell, it's an action function, not a patch function. Usage is simple, just provide a source and destination string and (if desired) set the make_innate variable to 1 (default is zero), e.g. here's a whole mess of spells being converted to innates for the Hexblade kit:

LAF cd_clone_spell INT_VAR make_innate = 1 STR_VAR source = spwi612 destination = ~a#hex91~ END // pw silence
LAF cd_clone_spell INT_VAR make_innate = 1 STR_VAR source = spwi815 destination = ~a#hex92~ END // pw blind
LAF cd_clone_spell INT_VAR make_innate = 1 STR_VAR source = spwi715 destination = ~a#hex93~ END // pw stun
LAF cd_clone_spell INT_VAR make_innate = 1 STR_VAR source = sppr113 destination = ~a#hex03~ END // doom
LAF cd_clone_spell INT_VAR make_innate = 1 STR_VAR source = spwi104 destination = ~a#hex11~ END // charm person
LAF cd_clone_spell INT_VAR make_innate = 1 STR_VAR source = spwi116 destination = ~a#hex12~ END // sleep
LAF cd_clone_spell INT_VAR make_innate = 1 STR_VAR source = spwi412 destination = ~a#hex04~ END // greater malison
LAF cd_clone_spell INT_VAR make_innate = 1 STR_VAR source = spwi206 destination = ~a#hex21~ END // invisibility
LAF cd_clone_spell INT_VAR make_innate = 1 STR_VAR source = spwi212 destination = ~a#hex22~ END // mirror image
LAF cd_clone_spell INT_VAR make_innate = 1 STR_VAR source = spwi311 destination = ~a#hex31~ END // pfnm
LAF cd_clone_spell INT_VAR make_innate = 1 STR_VAR source = spwi314 destination = ~a#hex32~ END // vampiric touch
LAF cd_clone_spell INT_VAR make_innate = 1 STR_VAR source = spwi405 destination = ~a#hex41~ END // improved invisibility
LAF cd_clone_spell INT_VAR make_innate = 1 STR_VAR source = spwi415 destination = ~a#hex42~ END // polymorph other

The function itself:

 

 

DEFINE_ACTION_FUNCTION cd_clone_spell
  INT_VAR
    make_innate = 0
  STR_VAR
    source = ""
    destination = ""
BEGIN

COPY_EXISTING ~%source%.spl~ ~override/%destination%.spl~
              ~%source%.spl~ ~override/%source%.spl~
  READ_LONG  0x64 abil_off
  READ_SHORT 0x68 abil_num
  READ_LONG  0x6a fx_off  
  SET delta = 0
  FOR (index = "-1" ; index < abil_num ; ++index) BEGIN
    PATCH_IF index < 0 BEGIN
      SET offset = 0x6e
      SET abil_fx_idx = 0
    END ELSE BEGIN
      SET offset = (abil_off + 0x1e + (index * 0x28))     
      WRITE_SHORT  (abil_off + 0x20 + (index * 0x28)) (THIS + delta)
      READ_SHORT   (abil_off + 0x20 + (index * 0x28)) abil_fx_idx
    END
    READ_SHORT  offset abil_fx_num    
    FOR (index2 = 0 ; index2 < abil_fx_num ; ++index2) BEGIN     
      READ_SHORT (fx_off +        (0x30 * (index2 + abil_fx_idx))) opcode
      PATCH_IF ((opcode = 206) OR (opcode = 318) OR (opcode = 321) OR (opcode = 324)) BEGIN
        READ_ASCII (fx_off + 0x14 + (0x30 * (index2 + abil_fx_idx))) resref
        PATCH_IF ("%SOURCE_RES%" STRING_COMPARE_CASE "%resref%" = 0) BEGIN
          READ_BYTE (fx_off + 0x0c + (0x30 * (index2 + abil_fx_idx))) timing
          READ_LONG (fx_off + 0x0e + (0x30 * (index2 + abil_fx_idx))) duration
          PATCH_IF ((opcode != 321) AND (timing = 0) AND ((duration = 0) OR (duration = 1))) BEGIN
            PATCH_IF ("%SOURCE_FILE%" STRING_COMPARE_CASE "%DEST_FILE%") BEGIN
              WRITE_ASCIIE (fx_off + 0x14 + (0x30 * (index2 + abil_fx_idx))) ~%DEST_RES%~ #8
            END    
          END ELSE BEGIN // non-zero durations need cloning
            READ_ASCII   (fx_off +        (0x30 * (index2 + abil_fx_idx))) clone (48)
            INSERT_BYTES (fx_off +        (0x30 * (index2 + abil_fx_idx))) 48
              WRITE_ASCIIE (fx_off +        (0x30 * (index2 + abil_fx_idx))) ~%clone%~ #48
              WRITE_ASCIIE (fx_off + 0x14 + (0x30 * (index2 + abil_fx_idx))) ~%destination%~ #8
            SET delta += 1    
            SET abil_fx_num += 1    
            SET index2 += 1
          END    
        END
      END
    END      
    WRITE_SHORT  offset abil_fx_num    
    PATCH_IF make_innate AND (index >= 0) AND ("%SOURCE_FILE%" STRING_COMPARE_CASE "%DEST_FILE%") BEGIN
      WRITE_SHORT  (abil_off + 0x02 + (index * 0x28)) 4
      READ_ASCII  (abil_off + 0x04 + (index * 0x28)) bam
    END  
  END
  PATCH_IF make_innate AND ("%SOURCE_FILE%" STRING_COMPARE_CASE "%DEST_FILE%") BEGIN
    WRITE_SHORT  0x1c 4                        // sets spell type to innate (4)
    WRITE_LONG   0x34 1                        // sets spell level to 1 to avoid scripting issues
    WRITE_ASCIIE 0x3a ~%bam%~ #8               // writes the bam filename from abilities to spell icon
  END  
  BUT_ONLY  
 
  ACTION_IF ((FILE_EXISTS_IN_GAME ~7eyes.2da~) AND (FILE_CONTAINS_EVALUATED (~7eyes.2da~ ~[ %TAB%]%source%[ %TAB%%LNL%%MNL%%WNL%]~))) THEN BEGIN
 
    COPY_EXISTING ~7eyes.2da~ ~override~
      SPRINT mind ~*~
      SPRINT sword ~*~
      SPRINT mage ~*~
      SPRINT venom ~*~
      SPRINT spirit ~*~
      SPRINT fortitude ~*~
      SPRINT stone ~*~
        COUNT_2DA_COLS cols
      SET cols = cols - 2    
      REPLACE_EVALUATE     CASE_INSENSITIVE ~\(^EYEMIND[ %TAB%].+[ %TAB%]%source%[ %TAB%%LNL%%MNL%%WNL%]\)~ BEGIN
        SPRINT mind ~%destination%~
      END ~\1~  
      REPLACE_EVALUATE ~\(^EYESWORD[ %TAB%].+[ %TAB%]%source%[ %TAB%%LNL%%MNL%%WNL%]\)~ BEGIN
        SPRINT sword ~%destination%~
      END ~\1~  
      REPLACE_EVALUATE ~\(^EYEMAGE[ %TAB%].+[ %TAB%]%source%[ %TAB%%LNL%%MNL%%WNL%]\)~ BEGIN
        SPRINT mage ~%destination%~
      END ~\1~  
      REPLACE_EVALUATE ~\(^EYEVENOM[ %TAB%].+[ %TAB%]%source%[ %TAB%%LNL%%MNL%%WNL%]\)~ BEGIN
        SPRINT venom ~%destination%~
      END ~\1~  
      REPLACE_EVALUATE ~\(^EYESPIRIT[ %TAB%].+[ %TAB%]%source%[ %TAB%%LNL%%MNL%%WNL%]\)~ BEGIN
        SPRINT spirit ~%destination%~
      END ~\1~  
      REPLACE_EVALUATE ~\(^EYEFORTITUDE[ %TAB%].+[ %TAB%]%source%[ %TAB%%LNL%%MNL%%WNL%]\)~ BEGIN
        SPRINT fortitude ~%destination%~
      END ~\1~  
      REPLACE_EVALUATE ~\(^EYESTONE[ %TAB%].+[ %TAB%]%source%[ %TAB%%LNL%%MNL%%WNL%]\)~ BEGIN
        SPRINT stone ~%destination%~
      END ~\1~  
      BUT_ONLY
 
    APPEND_COL ~7eyes.2da~ ~$ $ %cols% %mind%    %sword% %mage% %venom% %spirit% %fortitude% %stone%~
 
  END    

END

 

 

* Yes, there was a time when READ/WRITE patching was so radical that we wrote tutorials for it.

Link to comment

Thanks to @argent77's help:

 

ALTER_STORE_ITEM -----> Alters properties of all store items matching the resource name specified by "match_resref".

 

 

// Alters properties of all store items matching the resource name specified by "match_resref".
DEFINE_PATCH_FUNCTION ALTER_STORE_ITEM
INT_VAR
  duration               = "-1"
  charges1               = "-1"
  charges2               = "-1"
  charges3               = "-1"
  flags                  = "-1"
  number_in_stock        = "-1"
  infinite_supply        = "-1"
  flags_mode   = 0  // 0=overwrite, 1=set bits, 2=clear bits
STR_VAR
  match_resref = ~~
  resref       = ~~
BEGIN
  PATCH_IF (NOT ~%match_resref%~ STR_EQ ~~) BEGIN
    READ_LONG 0x034 ofs_items_for_sales
    READ_LONG 0x038 num_items_for_sales
    FOR (idx = 0; idx < num_items_for_sales; ++idx) BEGIN
      SET ofs = ofs_items_for_sales + idx * 0x1c
      READ_ASCII ofs s (8) NULL
      PATCH_IF (~%match_resref%~ STR_EQ ~%s%~) BEGIN
        PATCH_IF (NOT ~%resref%~ STR_EQ ~~) BEGIN
          WRITE_ASCIIE ofs ~%resref%~ (8)
        END
        PATCH_IF (duration != "-1") BEGIN
          WRITE_SHORT (ofs + 0x08) duration
        END
        PATCH_IF (charges1 != "-1") BEGIN
          WRITE_SHORT (ofs + 0x0a) charges1
        END
        PATCH_IF (charges2 != "-1") BEGIN
          WRITE_SHORT (ofs + 0x0c) charges2
        END
        PATCH_IF (charges3 != "-1") BEGIN
          WRITE_SHORT (ofs + 0x0e) charges3
        END
        PATCH_IF (number_in_stock != "-1") BEGIN
          WRITE_LONG (ofs + 0x014) number_in_stock
        END
        PATCH_IF (infinite_supply != "-1") BEGIN
          WRITE_LONG (ofs + 0x018) infinite_supply
        END
        PATCH_IF (flags != "-1") BEGIN
          PATCH_IF (flags_mode = 1) BEGIN // set specific bits
            WRITE_LONG (ofs + 0x10) (THIS BOR flags)
          END ELSE PATCH_IF (flags_mode = 2) BEGIN  // clear specific bits
            WRITE_LONG (ofs + 0x10) (THIS BAND BNOT flags)
          END ELSE BEGIN  // default: overwrite all flags
            WRITE_LONG (ofs + 0x10) flags
          END
        END
      END
    END
  END
END

 

 

 

ALTER_CRE_ITEM -----> Alters properties of all inventory items matching the resource name specified by "match_resref".

 

 

// Alters properties of all inventory items matching the resource name specified by "match_resref".
DEFINE_PATCH_FUNCTION ALTER_CRE_ITEM
INT_VAR
  duration     = "-1"
  charges1     = "-1"
  charges2     = "-1"
  charges3     = "-1"
  flags        = "-1"
  flags_mode   = 0  // 0=overwrite, 1=set bits, 2=clear bits
STR_VAR
  match_resref = ~~
  resref       = ~~
BEGIN
  PATCH_IF (NOT ~%match_resref%~ STR_EQ ~~) BEGIN
    READ_LONG 0x2bc ofs_items
    READ_LONG 0x2c0 num_items
    FOR (idx = 0; idx < num_items; ++idx) BEGIN
      SET ofs = ofs_items + idx * 0x14
      READ_ASCII ofs s (8) NULL
      PATCH_IF (~%match_resref%~ STR_EQ ~%s%~) BEGIN
        PATCH_IF (NOT ~%resref%~ STR_EQ ~~) BEGIN
          WRITE_ASCIIE ofs ~%resref%~ (8)
        END
        PATCH_IF (duration != "-1") BEGIN
          WRITE_SHORT (ofs + 0x08) duration
        END
        PATCH_IF (charges1 != "-1") BEGIN
          WRITE_SHORT (ofs + 0x0a) charges1
        END
        PATCH_IF (charges2 != "-1") BEGIN
          WRITE_SHORT (ofs + 0x0c) charges2
        END
        PATCH_IF (charges3 != "-1") BEGIN
          WRITE_SHORT (ofs + 0x0e) charges3
        END
        PATCH_IF (flags != "-1") BEGIN
          PATCH_IF (flags_mode = 1) BEGIN // set specific bits
            WRITE_LONG (ofs + 0x10) (THIS BOR flags)
          END ELSE PATCH_IF (flags_mode = 2) BEGIN  // clear specific bits
            WRITE_LONG (ofs + 0x10) (THIS BAND BNOT flags)
          END ELSE BEGIN  // default: overwrite all flags
            WRITE_LONG (ofs + 0x10) flags
          END
        END
      END
    END
  END
END

 

 

 

ALTER_AREA_ITEM ----> Alters properties of all area items matching the resource name specified by "match_resref".

 

 

// Alters properties of all area items matching the resource name specified by "match_resref".
DEFINE_PATCH_FUNCTION ALTER_AREA_ITEM
INT_VAR
  duration     = "-1"
  charges1     = "-1"
  charges2     = "-1"
  charges3     = "-1"
  flags        = "-1"
  flags_mode   = 0  // 0=overwrite, 1=set bits, 2=clear bits
STR_VAR
  match_resref = ~~
  resref       = ~~
BEGIN
  PATCH_IF (NOT ~%match_resref%~ STR_EQ ~~) BEGIN
    READ_LONG 0x078 ofs_items
    READ_SHORT 0x076 num_items
    FOR (idx = 0; idx < num_items; ++idx) BEGIN
      SET ofs = ofs_items + idx * 0x14
      READ_ASCII ofs s (8) NULL
      PATCH_IF (~%match_resref%~ STR_EQ ~%s%~) BEGIN
        PATCH_IF (NOT ~%resref%~ STR_EQ ~~) BEGIN
          WRITE_ASCIIE ofs ~%resref%~ (8)
        END
        PATCH_IF (duration != "-1") BEGIN
          WRITE_SHORT (ofs + 0x08) duration
        END
        PATCH_IF (charges1 != "-1") BEGIN
          WRITE_SHORT (ofs + 0x0a) charges1
        END
        PATCH_IF (charges2 != "-1") BEGIN
          WRITE_SHORT (ofs + 0x0c) charges2
        END
        PATCH_IF (charges3 != "-1") BEGIN
          WRITE_SHORT (ofs + 0x0e) charges3
        END
        PATCH_IF (flags != "-1") BEGIN
          PATCH_IF (flags_mode = 1) BEGIN // set specific bits
            WRITE_LONG (ofs + 0x10) (THIS BOR flags)
          END ELSE PATCH_IF (flags_mode = 2) BEGIN  // clear specific bits
            WRITE_LONG (ofs + 0x10) (THIS BAND BNOT flags)
          END ELSE BEGIN  // default: overwrite all flags
            WRITE_LONG (ofs + 0x10) flags
          END
        END
      END
    END
  END
END

 

 

Edited by Luke
Link to comment

Based on the discussion over yonder about cloning spells, I used a new function when updating Sword and Fist. This will clone a spell, updating any opcodes in both the original and clone that self-reference as appropriate. It can optionally also convert the newly cloned spell to an innate spell, finally ending the need for this very old, very outdated bit of code from the mists of time*.

 

Since it can potentially need to update the source spell, it's an action function, not a patch function. Usage is simple, just provide a source and destination string and (if desired) set the make_innate variable to 1 (default is zero), e.g. here's a whole mess of spells being converted to innates for the Hexblade kit:

There's one more issue that wasn't mentioned:

Every other spell/item needs opcodes 321/206/318 of the original spell cloned for the new spell.

i.e. Remove Fear applies opcode 321 for the horror spell. Any duplicates of the Horror spell would need removal duplicated on every type of Remove/Resist Fear spell.

It think it would be more efficient to store all such changes in an array for later, then patch them all at once in a single COPY_EXISTING_REGEXP.

 

Also, 7eyes should check every entry, not just the defaults for the Seven Eyes spell.

Edited by kjeron
Link to comment

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...