c4_angel Posted November 27, 2017 Share Posted November 27, 2017 (edited) 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~ ENDBUT_ONLYin script:IF CheckStat(Player1, 1, %bluring%) //vanilla game or CheckSpellState(Player1, %bluring%) // EE gameTHEN ...... Note the I:Value* must be fixed to 1, since CheckSpellState has no value parameter. Edited December 22, 2017 by c4_angel Quote Link to comment
kjeron Posted November 27, 2017 Share Posted November 27, 2017 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. Quote Link to comment
c4_angel Posted November 27, 2017 Share Posted November 27, 2017 (edited) 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 November 27, 2017 by c4_angel Quote Link to comment
kjeron Posted November 27, 2017 Share Posted November 27, 2017 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. Quote Link to comment
c4_angel Posted November 28, 2017 Share Posted November 28, 2017 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? Quote Link to comment
kjeron Posted November 28, 2017 Share Posted November 28, 2017 (edited) 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 November 28, 2017 by kjeron Quote Link to comment
c4_angel Posted November 28, 2017 Share Posted November 28, 2017 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. Quote Link to comment
CamDawg Posted January 3, 2018 Author Share Posted January 3, 2018 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. Quote Link to comment
argent77 Posted January 27, 2018 Share Posted January 27, 2018 (edited) 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 January 27, 2018 by argent77 Quote Link to comment
subtledoctor Posted March 2, 2018 Share Posted March 2, 2018 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 Quote Link to comment
subtledoctor Posted March 2, 2018 Share Posted March 2, 2018 (edited) Also: WeiDU's CREATE can make spells. Whaaaaaaaaat I'm never using NI again. (Well, okay, that's a lie.) Edited March 2, 2018 by subtledoctor Quote Link to comment
K4thos Posted July 29, 2018 Share Posted July 29, 2018 (edited) 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 July 29, 2018 by K4thos Quote Link to comment
CamDawg Posted August 30, 2018 Author Share Posted August 30, 2018 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. Quote Link to comment
Luke Posted August 30, 2018 Share Posted August 30, 2018 (edited) 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 August 30, 2018 by Luke Quote Link to comment
kjeron Posted August 30, 2018 Share Posted August 30, 2018 (edited) 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 August 30, 2018 by kjeron Quote Link to comment
Recommended Posts
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.