DavidW Posted June 1, 2019 Share Posted June 1, 2019 I've been irritated for a while by how difficult it is to do fine-grained editing of existing BCS files. There are no very good options: - REPLACE_BCS_BLOCK is brittle, breaks easily, and only allows for wholesale replacement - Search-and-replace is insensitive to the file structure and often too crude - SSL is fine for writing complex scripts from scratch, but no good for editing existing scripts. This collection of five functions is an attempt to solve that problem. I'll describe them one at a time in the thread below. The library is alter_script.tpa, and can be found here in the current SCS repo. Quote Link to comment
DavidW Posted June 1, 2019 Author Share Posted June 1, 2019 DELETE_SCRIPT_BLOCK (INT_VAR only_once (default=0), STR_VAR script, match, match1, match2, match3, match4, match5) Set 'script' to the script you want to edit. Set 'match' to some string that appears only in the blocks you want to delete, and all blocks containing that string will be deleted. (Set only_once to 1 and only the first block will be deleted.) If you want finer-grained identification, set any of match1-match5 to some string; the only blocks deleted will be those that match all strings that you set. Example: VAMPANO.bcs contains a rudimentary use of vampire Domination, which it's convenient to remove on SCS. LAF DELETE_SCRIPT_BLOCK STR_VAR script=vampano match=VAMPIRE_DOMINATION END Quote Link to comment
DavidW Posted June 1, 2019 Author Share Posted June 1, 2019 ALTER_SCRIPT_BLOCK (INT_VAR only_once, STR_VAR match,match1-match5, swap_out, swap_in, swap_out1-swap_in5, swap_in1-swap_in5) Blocks are matched as in DELETE_SCRIPT_BLOCK. Then swap_out is replaced with swap_in (and swap_out1 with swap_in1, etc) in any block that's matched. Example: the EE summons AI script bdsum00 has a bug that causes it to stutter whenever its target goes invisible, because its attack blocks don't contain a See(LastSeenBy(Myself)) check. LAF ALTER_SCRIPT_BLOCK STR_VAR script=bdsum00 match="AttackOneRound" swap_out="THEN" swap_in="See(LastSeenBy(Myself)) THEN" END Quote Link to comment
DavidW Posted June 1, 2019 Author Share Posted June 1, 2019 INSERT_SCRIPT_BLOCK (INT_VAR insert_above (default=0), only_once, STR_VAR script match, match1-match5, insert) 'insert' needs to be a path to a BAF file. Blocks are matched as in DELETE_SCRIPT_BLOCK. The BAF code in 'insert' is then inserted below any block that matches. (Set insert_above=1 to have it inserted above instead.) Example: Suppose (recapitulating Ascension) that you want to add a 'reinforcements' block to the area script of Gromnir's throne room, to appear above the block that grants XP. <<<<<<<< .../ascension-inline/gromnir_insert.baf [the script] >>>>>>>> LAF INSERT_SCRIPT_BLOCK INT_VAR insert_above=1 STR_VAR script=ar5002 match=~SetGlobal("AddXP","AR5002",1)~ insert=".../ascension-inline/gromnir_insert.baf" END Quote Link to comment
DavidW Posted June 1, 2019 Author Share Posted June 1, 2019 REPLACE_SCRIPT_BLOCK (INT_VAR only_once, STR_VAR script, match, match1-match5, insert) 'insert' needs to be a path to a BAF file. Blocks are matched as in DELETE_SCRIPT_BLOCK. The BAF code in 'insert' then replaces any matched blocks. Quote Link to comment
DavidW Posted June 1, 2019 Author Share Posted June 1, 2019 CLONE_SCRIPT_BLOCK (INT_VAR only_once, insert_above, STR_VAR script, match, match1-match5, swap_in, swap_in1-swap_in5, swap_out, swap_out1-swap_out5, original_swap_in, original_swap_in1-original_swap_in5, original_swap_out, original_swap_out1-original_swap_out5) Blocks are matched as in DELETE_SCRIPT_BLOCK. Matched blocks are copied, then text swaps are carried out on the new block as per ALTER_SCRIPT_BLOCK, and on the old block as per ALTER_SCRIPT_BLOCK but using original_swap_in and original_swap_out. Example (again, recapitulating Ascension): suppose you want to alter the epilogues so that Sarevok gets different epilogues depending on his alignment. LAF CLONE_SCRIPT_BLOCK STR_VAR script=ar6200 match=~"sarevnd"~ swap_out=~Global("SarevokBio","GLOBAL",0)~ swap_in=~Alignment("Sarevok",MASK_EVIL)Global("SarevokBio","GLOBAL",0)~ swap_out1=~"sarevnd"~ swap_in1=~"sarevnd2"~ original_swap_out=~Global("SarevokBio","GLOBAL",0)~ original_swap_in=~!Alignment("Sarevok",MASK_EVIL)Global("SarevokBio","GLOBAL",0)~ END Quote Link to comment
DavidW Posted June 1, 2019 Author Share Posted June 1, 2019 For advanced users: you can also do functional matching and patching. - All these functions take an additional STR_VAR argument, 'match_function'. Set this to the name of a PATCH_FUNCTION that operates on the decompiled block as if it were a text file, and returns 'value'. A match is found if value=1 (and if all conventional 'match' variables also match). - ALTER_SCRIPT_BLOCK takes an additional STR_VAR argument, 'functionpatch'. Set this to the name of a PATCH_FUNCTION which operates on the decompiled contents of any matched block as if it were a text file. - CLONE_SCRIPT_BLOCK takes two additional STR_VAR arguments, 'functionpatch' and 'patch_original_function', which work as for ALTER_SCRIPT_BLOCK applied to the new and old blocks respectively. Quote Link to comment
Luke Posted June 2, 2019 Share Posted June 2, 2019 Do you know why INSERT_SCRIPT_BLOCK fails to correctly insert the following script block? Spoiler IF AttackedBy([ANYONE],DEFAULT) Allegiance(Myself,NEUTRAL) OR(2) SpellCastOnMeRES("",[ANYONE]) SpellCastOnMe([ANYONE],0) THEN RESPONSE #100 Enemy() END Your function inserts it as Spoiler IF AttackedBy([ANYONE],DEFAULT) Allegiance(Myself,NEUTRAL) OR(2) SpellCastOnMe([ANYONE],0) // Where is the RES version? SpellCastOnMe([ANYONE],0) THEN RESPONSE #100 Enemy() END Quote Link to comment
Jarno Mikkola Posted June 2, 2019 Share Posted June 2, 2019 I would expect that the RES not being named, is the reason why that version is not used. Quote Link to comment
Luke Posted June 2, 2019 Share Posted June 2, 2019 1 hour ago, Jarno Mikkola said: I would expect that the RES not being named, is the reason why that version is not used. But that is the empty string, thus a valid string.... @DavidW Could you fix this issue? Quote Link to comment
DavidW Posted June 2, 2019 Author Share Posted June 2, 2019 I'm pretty sure SpellCastOnMeRES("",[ANYONE]) and SpellCastOnMe([ANYONE],0) compile to the same BCS code, so they're just notational variants of each other. Try compiling and decompiling your code and see what you get. Quote Link to comment
Luke Posted June 2, 2019 Share Posted June 2, 2019 8 minutes ago, DavidW said: I'm pretty sure SpellCastOnMeRES("",[ANYONE]) and SpellCastOnMe([ANYONE],0) compile to the same BCS code, so they're just notational variants of each other. Try compiling and decompiling your code and see what you get. Yeah, I obtain the same result with a simple COMPILE..... And it decompiles into two SpellCastOnMe([ANYONE],0)... Anyway, are those two triggers really equivalent? I mean, I expect the non-RES version to trigger only with spells listed in SPELL.ids... Am I right? Quote Link to comment
DavidW Posted June 2, 2019 Author Share Posted June 2, 2019 The engine doesn’t see the text, only what it compiles to. So if two bits of script compile to the same thing, then yes: they’re equivalent. Quote Link to comment
DavidW Posted June 2, 2019 Author Share Posted June 2, 2019 Jarno: I just accidentally deleted your post while trying to reply. Apologies, and feel free to repost - but in any case, BCS isn’t a hex code. Quote Link to comment
Jarno Mikkola Posted June 2, 2019 Share Posted June 2, 2019 Ahh, well, those things happen. Yeah, it's not exactly simple as being hex code... but it's still just numbers to the computer. That can be expressed as a hexadecimal, or binary product. 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.