Skip to content

-HOW TO DO STUFF IN WEIDU-

12345679»

Comments

  • afrothundaaaaafrothundaaaa Member Posts: 16
    This is the code I use to allow spell casting in the crimson chain:

    LPF DELETE_EFFECT	INT_VAR	check_globals = 8	match_opcode = 145	END
    

    You need to tell the code with global has the effect you want to delete.

    Not sure why i would need check_globals. this worked for me.
    LPF DELETE_EFFECT INT_VAR match_opcode = 145 END
    
  • GwendolyneGwendolyne Member Posts: 461
    edited August 2021
    DELETE_ITEM_EFFECT deletes all extended effects with specified opcode from an item, not its global (equipped) effect. In this case, you must use the following function :
    DELETE_EFFECT: This is a patch function for creature, item, or spell files that will match an existing effect and then delete it. Numerous variables are available for matching the specified effect.

    The first batch of variables are meta-variables which will help determine the scope of the function:

    INT_VAR check_globals to whether the function should loop through global effects on items (also known as equipping effects) and spells (default is 1). Creature effects are all global effects, so this variable will always be considered to be one when this function is run on a creature.
    INT_VAR check_headers to whether the function should loop through effects on extended headers on items and spells (default is 1). Creatures have no extended headers, so this variable is ignored when used on creatures.
    INT_VAR header to whether the function should target effects on one specific header, counting the first header as zero. A negative value will match all headers (default is -1). Creatures have no extended headers, so this variable is ignored when used on creatures.
    INT_VAR header_type to whether the function should only delete effects on extended headers of the specified type (1 - melee, 2 - ranged, etc.). Negative values will look at effects on all headers (default is -1). Creatures have no extended headers, so this variable is ignored when used on creatures.
    INT_VAR multi_match to the number of effects to delete in the active stack. If you just want to match the first effect and have the function stop, use 1. Otherwise the function will continue matching until the number of deleted effects matches this value. The function will always make at least one change (e.g. 0 or negative values are treated as 1). (default is 999).

    INT_VAR match_opcode to the opcode of effects to be matched (default -1).
    INT_VAR match_target to the target of effects to be matched (default -1).
    INT_VAR match_power to the power of effects to be matched (default -1).
    INT_VAR match_parameter1 to parameter1 of effects to be matched (default -1).
    INT_VAR match_parameter2 to parameter2 of effects to be matched (default -1).
    INT_VAR match_timing to the timing of effects to be matched (default -1).
    INT_VAR match_resist_dispel to the resist/dispel setting of effects to be matched (default -1).
    INT_VAR match_duration to the duration of effects to be matched (default -1).
    INT_VAR match_probability1 to probability1 (the upper bound) of effects to be matched (default -1).
    INT_VAR match_probability2 to probability2 (the lower bound) of effects to be matched (default -1).
    INT_VAR match_dicenumber to the number of dice of effects to be matched (default -1). This field is instead used for maximum hit-dice (HD) by some opcodes.
    INT_VAR match_dicesize to the size of dice of effects to be matched (default -1). This field is used instead for minimum hit-dice (HD) by some opcodes.
    INT_VAR match_savingthrow to the saving throw type of effects to be matched (default -1).
    INT_VAR match_savebonus to the saving throw bonus effects to be matched (default -11). Since saving throws can range into negative values, this variable can match all the way down to -10.
    INT_VAR match_special to the special field of effects to be matched (default -1). The special field is used by some EE effects.
    STR_VAR match_resource to the resource of effects to be matched (default "SAME").

    So
    LPF DELETE_EFFECT INT_VAR check_headers = 0 match_opcode = 145 END
    
    afrothundaaaa
  • afrothundaaaaafrothundaaaa Member Posts: 16
    @Gwendolyne, thanks for the details. I'm still new to this all so i'm sure it will make sense eventually; however, I still am not sure what the extended headers are and why it matters. This is, however, good to know and I'm now closer to understanding weidu item patching. Thank you all!
  • AndreaColomboAndreaColombo Member Posts: 5,524
    Indeed, I am now wondering why check_headers is necessary and check_globals is not?
  • GwendolyneGwendolyne Member Posts: 461
    edited August 2021
    Let's say you have a sword that lowers the target resistance to poison when hit, and gives the wielder full immunity to poison when equipped. Both use op#173. If you want to remove the wielder immunity to poison, you must use this code:
    LPF DELETE_EFFECT INT_VAR check_headers = 0 match_opcode = 173 END
    

    if you want to remove the target resistance loss
    LPF DELETE_EFFECT INT_VAR check_globals = 0 match_opcode = 173 END
    

    In other words, when the same opcode is used both in global effects and in extended headers, you will delete both of them if you don't specify where to delete them.

    @afrothundaaaa Global effects are effects affecting characters when they equip an item. Extended effects are those used by items or spells headers. For example, used by a weapon's melee (or range) attack or by an item's special abilities.
    Indeed, I am now wondering why check_headers is necessary and check_globals is not?
    By default, both variables are set to 1. In @afrothundaaaa case, the only effect to remove is the equipped one. Hence, check_headers must be set to 0 in case one item extended header uses op#145.

    It is nothing but a safety check to be sure not to mess things. ;)
    Post edited by Gwendolyne on
    afrothundaaaaAndreaColombo
  • afrothundaaaaafrothundaaaa Member Posts: 16
    @Gwendolyne Thanks for info. That makes sense.
  • AndreaColomboAndreaColombo Member Posts: 5,524
    And the only values acceptable for check_globals and check_headers are 1 and 0?

    I was using it to specify which # the effect I wanted to target was in NI.
  • GwendolyneGwendolyne Member Posts: 461
    edited August 2021
    You confused check_globals with header (with first header = 0, 2d header = 1...). ;)

    You can't specify the effect number, only narrow its choice with all the match_variables settings (match_parameter1, match_target, match_duration...) if there are more than one occurrence (for example, op#215 or 141).
    afrothundaaaaAndreaColombo
  • kjeronkjeron Member Posts: 2,367
    And the only values acceptable for check_globals and check_headers are 1 and 0?
    Yes.
    Higher values function no differently than "1", negative values will break the function (not with errors, it just won't do anything).
    AndreaColombo
  • afrothundaaaaafrothundaaaa Member Posts: 16
    Hello all. Hoping you can help me with this one, been fighting this for days now. I'm not sure how to do this, been trying to find the documentation on thebigg WEIDU article.

    Basically, I want to iterate through a list of text, and assign the value to the STR_VAR icon for the (custom) patch function ADD_ITEM_HEADER.

    I am able to get an INT_VAR to enumerate successfully when specifying it the way i am with spell_icon, but it seems to not properly enumerate in any instance, if i use "%spell_icon%" ~%spell_icon%~ or just spell_icon.

    What am I doing wrong here? I did read somewhere (lost it now) that weidu doesn't have a way to enumerate the str vars? How do you get by this? Do you do another Patch function for each STR var? seems crazy...
    PATCH_FOR_EACH ~spell_icon~ IN ~spwi221b~ ~spwi114b~ ~spwi415b~ BEGIN
        LPF ADD_ITEM_HEADER
          INT_VAR
          type        = 3
          required_id = 1
          location    = 3
          range       = 45
          charges     = 3
          depletion   = 3
          flags       = 2048
          overhand    = 34
          backhand    = 33
          thrust      = 33
          STR_VAR
          icon        = spell_icon
        END
      END
    
  • kjeronkjeron Member Posts: 2,367
    @afrothundaaaa
    icon = EVAL ~%spell_icon%~
    
    afrothundaaaaStummvonBordwehr
  • afrothundaaaaafrothundaaaa Member Posts: 16
    kjeron wrote: »
    @afrothundaaaa
    icon = EVAL ~%spell_icon%~
    

    Thank you! so simple... lol
  • HudzyHudzy Member Posts: 300
    When using ADD_CRE_ITEM is it possible to target empty inventory spaces instead of a specific space?

    For example ADD_CRE_ITEM ~POTN08~ #0 #0 #0 ~NONE~ ~INV1~

    I'm assuming whatever may or may not currently exist in INV1 would be replaced. I'm hoping to avoid checking multiple creatures for specific free inventory spaces, or overwriting other items added by mods.
  • GwendolyneGwendolyne Member Posts: 461
    Use INV instead of INVn: WeiDU will place your item in the first empty INV slot.
  • HudzyHudzy Member Posts: 300
    Thank you.
  • LeiluLeilu Member Posts: 64
    I'm looking for a guide to make a mod that simply put PVRZ files in the override folder.
    Weidu documentation website is too complex for me.
  • jmerryjmerry Member Posts: 3,822
    A "drop in override" mod? Just put your files in a folder, include something like a readme to tell people what to do with them, and put it out there. You don't really need a guide for that.

    WeiDU comes in when you want to start doing things like editing files in a way that's compatible with them being already altered, or especially adding new text to the game. I could throw out a quick CLAB file and some spells to give the Undead Hunter better Turn Undead ability, but I needed WeiDU before I could update the kit's description to reflect that change.

    (I have a browser tab permanently open to the WeiDU documentation, for reference purposes.)
  • LeiluLeilu Member Posts: 64
    Yep I see what you mean but with Weidu you could uninstall easily without risking breaking other mods manually removing all PVRZ files in the override folder. I'm talking about 3000+ files. ;)
  • jmerryjmerry Member Posts: 3,822
    Yeah, that many calls for automation.

    It sounds like the COPY command does what you want here; give it a source that's a folder full of the files you want to copy over, and ~override~ as destination. That'll copy everything in that folder over.

    Fill around it with the basic skeleton of a mod, and you should be good. To build that basic skeleton - don't be afraid to copy from other mods.
  • GraionDilachGraionDilach Member Posts: 581
    For PVRZ handling and ensuring that the indices end up fine, there are also these functions https://www.gibberlings3.net/forums/topic/28835-toss-your-semi-useful-weidu-macros-here/page/8/#comment-279049
  • DracoqwertyDracoqwerty Member Posts: 36
    Is there an easy way to save certain integers (such as might be created by RESOLVE_STR_REF) and later create a string with the integer cast as part of the string? I'm trying to add to the Ranger's racial enemy 2da table, but I don't know how to save the dialog.tlk references to the short and long names.
  • jmerryjmerry Member Posts: 3,822
    You can do quite a few string manipulations, but it's fairly tricky. You're never quite sure if you have enough layers of EVAL going on.

    The string concatenation operator is ^, and integers cast to strings give their decimal representations. For example, if I wrote
    SET GaelanPrice = 20000
    SPRINT GaelanReply ~It may seem to be costly, but think of the danger in crossin' the Cowled Wizards. A fair price, if ye think about it. It be ~^~%GaelanPrice%~^~ gold pieces for their help.~

    then the string GaelanReply would be ~It may seem to be costly, but think of the danger in crossin' the Cowled Wizards. A fair price, if ye think about it. It be 20000 gold pieces for their help.~

    (Using a bit of canon dialogue for this; that's string 20944 in English-language BG2EE)

    Anyway, that's a fairly simple example. Two literal strings, one integer variable cast to a string. You can easily go more complicated, with more variables involved. As I do in my example below.

    A snippet from my kit mod demonstrating some of this, in which I cobble together several strings from component parts:
    In a spoiler to reduce clutter
    AUTO_EVAL_STRINGS
    
    /* lots of other stuff - just note that automatic EVAL layer in the preamble */
    
    OUTER_SET Include1 = 0
    OUTER_SPRINT IncCode1 ~~
    OUTER_FOR (lvl= 1; lvl <= 6; ++lvl) BEGIN
    	OUTER_SET BlingLevelOld = %lvl%-1
    	OUTER_SET BlingLevelNew = lvl
    	OUTER_SPRINT OldCode ~J8BBL~^~%BlingLevelOld%~^~%IncCode1%~
    	OUTER_SPRINT NewCode ~J8BBL~^~%BlingLevelNew%~^~%IncCode1%~
    	OUTER_SPRINT UpgradeSpell ~J8BBUP~^~%BlingLevelNew%~
    	ACTION_IF (lvl < 6) BEGIN
    		OUTER_SET UpgradeLevel = 3*lvl
    		OUTER_SET UpgradeCost = 5000*lvl
    	END ELSE BEGIN // lvl = 6
    		OUTER_SET UpgradeLevel = 25
    		OUTER_SET UpgradeCost = 50000
    	END
    	OUTER_SPRINT reply0 @5500
    	OUTER_SPRINT reply1 @5501
    	OUTER_SPRINT reply2 @5502
    	OUTER_SPRINT reply3 @5503
    	OUTER_SPRINT reply4 @5504
    	OUTER_SPRINT reply5 @5505
    	OUTER_SPRINT ReplyPay ~%reply0%~^~%UpgradeCost%~^~%reply3%~^~%BlingLevelNew%~^~.~
    	OUTER_SPRINT ReplyLevel ~%reply1%~^~%UpgradeLevel%~^~%reply4%~^~%BlingLevelNew%~^~%reply5%~
    	OUTER_SPRINT ReplyCost ~%reply2%~^~%UpgradeCost%~^~%reply3%~^~%BlingLevelNew%~^~%reply5%~
    	COMPILE ~bling-fist/resource/BlingGenPatch.D~ EVAL
    END // Loop for general upgrades, no jewelry incorporated
    

    So, what's this doing? First, I construct item codes by concatenating a fixed string and two variable strings; one a single-character number, the other an empty string (in this loop; I reuse much of this code in a later loop that puts a letter there). Crucially, ^ is the concatenation operator for strings.

    Then, define a couple of new integer variables and build dialogue strings in a similar fashion, concatenating strings out of my tra file and those integers. While @ references to the tra are really just string variables, I found it worked better not to use them directly in my concatenation lines and instead use more local variables. I think another layer of EVAL would have done it, but I wasn't about to risk breaking something once I got it working.

    Finally, run the D file, which contains a bunch of references to the variables defined in this loop. That needs an explicit EVAL to work properly.

    For your example, you'd presumably be creating new rows? Something like the following:
    SPRINT newrow ~%NewRaceName%~^~ ~^~%NewRaceStrref%~^~ ~^~%NewRaceIDS%~^~ ~^~%NewRaceHelp%~
    Maybe with fewer pieces than that, because you probably know what that new race is and can use literal strings for the internal name and IDS number.
    (Don't worry too much about using the right amount of space. Any number of spaces and tabs in one place act as a delimiter for a row's entries in the 2DA format. Making it line up properly has nothing to do with function; only human readability is affected)
  • AndreaColomboAndreaColombo Member Posts: 5,524
    Figured this might be as good a place as any for my code-related questions.

    In the Nostalgia Pack, I have code (written with substantial help from subtledoctor) that checks whether CRE files have anything equipped in the shield slot that is not a shield so it can skip them when patching CREs with BG1 sprites:
    COPY_EXISTING_REGEXP GLOB ~.*\.cre~ ~override~
      SET validslot = 0
      READ_LONG 0x2bc itemsoffset
      READ_LONG 0x2b8 itemslot
      READ_LONG 0x2c0 itemnumber
      READ_SSHORT (itemslot + 0x04) shieldslot
      PATCH_IF (shieldslot > "0") BEGIN
        READ_ASCII ((LONG_AT 0x2bc) + %shieldslot% * 0x14) itemname
        INNER_ACTION BEGIN
          COPY_EXISTING ~%itemname%.itm~ ~override~
            READ_SHORT 0x1c itemtype
            PATCH_IF (itemtype = "12") BEGIN
              SET validslot = 1
            END
          BUT_ONLY IF_EXISTS
        END
      END
      ELSE BEGIN
        SET validslot = 1
      END
      PATCH_IF (validslot = 1) BEGIN
        READ_LONG 0x28 current
        PATCH_PHP_EACH ac_animation_map AS old => new BEGIN
          PATCH_IF (old = current) BEGIN
            WRITE_LONG 0x28 new
           END
        END
      END
     BUT_ONLY
    

    I need to add a second check to confirm that the CRE does not have BDERINYE.ITM equipped (which goes to the helmet slot.)

    Should I add to the existing variables and put an extra condition within the existing INNER_ACTION, or should I create another INNER_ACTION after "BUT_ONLY IF_EXISTS"?

    Additionally, one thing I've never really understood about this code—how can I work out this part?
    READ_SSHORT (itemslot + [b]0x04[/b]) shieldslot
    

    As in, why is it "+ 0x04" and not just "itemslot" or "itemslot + any other number"?

    Same with
    READ_ASCII ((LONG_AT 0x2bc) + %shieldslot% * 0x14)
    

    Why is 0x2bc not enough and why 0x14 and not anything else?



    Last question: the following code does nothing, and I don't understand why.
    APPEND ~ANIMATE.IDS~
    ~0x5600 BG1_FIGHTER_HUMAN_MALE~
    
  • jmerryjmerry Member Posts: 3,822
    The various 0x**** magic numbers are locations for where things are, or offsets that help figure that out. The "0x" indicates the number that follows is in hexadecimal notation. For example, the item slots in a creature file start at a point marked by the "Item slots offset" field at 0x2b8. In the semi-random file I've chosen to look at (AKAE.CRE), the number there is 0x790.
    Then, each item slot takes up two bytes, so you need to add some multiple of 2 to get a particular slot. In order, helmet (0), armor (2), shield (4), gloves (6), left ring (8), right ring (0xa), amulet (0xc), belt (0xe), boots (0x10), weapon 1 through 4 (0x12 through 0x18), quiver 1 through 4 (0x1a through 0x20), cloak (0x22), quick slot 1 through 3 (0x24 through 0x28), and inventory 1 through 16 (0x2a through 0x48).
    Since you care about what's in the shield slot, you take the value at 0x2b8 (defined as %itemslot% in your code), and add 4 to that because it's the third slot. So for my example, 0x790 + 4 is 0x794. We read what's there, and it's a 2. OK, next stage needed - we need to figure out what that "2" actually means. (By the way, I've spotted a subtle bug in the code - the "if" condition should be (shieldslot >= "0"), not (shieldslot > "0"). 0 is one of the values that refers to an actual item, so excluding it could cause you to miss things.)
    Also - READ_SSHORT interprets the result as a signed integer, unlike the usual READ_SHORT which would interpret it as an integer.

    The numbers in the item slot fields refer to the list of items stored in the creature file. Item 0, item 1, item 2, and so on. The %itemsoffset% variable we read from location 0x2bc is where this list starts. Or, in the code you've copied, we read that field again with LONG_AT. Now, what's with that "%shieldslot% * 0x14"? The substructure that is an item takes up twenty bytes. Eight bytes for the item code, two for duration (when it expires), two each for three "number of charges" fields, and four for item flags such as "undroppable" or "stolen". That's twenty total, or 0x14. So item 0 is at %itemsoffset%, item 1 is at %itemsoffset% + 0x14, item 2 is at %itemsoffset% + 2*0x14, and so on. We could add another offset if we wanted one of the subfields like flags, but here we just want the item's code - the first field in the substructure. READ_ASCII reads eight bytes there and interprets it as a string, getting you the item's code - or, at least, the part before the .ITM.
    So, for my example, we're looking for item #2 in AKAE.CRE. %itemsoffset% is 0x72c, which we need to add 0x28 to in order to get item #2. 0x72c + 0x28 = 0x754. Read what's there, interpret as a string ... SW1H46. That's saved to %itemname%.

    On to the INNER_ACTION, now. We need to look up things about the item we've just found. Which naturally means copying that item to load it up ... but we don't want to lose track of the creature we're on now. So, INNER_ACTION. Once in that, the old file we had loaded is off to the side and we're in "nothing loaded" mode so we can call up a new file to work with. We take that %itemname%, append the .itm needed to make it a file name, and load it.
    Now, checking that item. It's in the shield slot, so we're checking if it's actually a shield. Which is nice and simple - the "category" field at 0x1c has that information. Read it, get a number, and interpret it. Shields are item type 12. If the item that was in the shield slot was item type 12, it was a shield. Good. We set %validslot% to 1 to remember that. If it was some other type, then it's not a shield and we leave %validslot% at zero.
    In my example, SW1H46 (a wakizashi) is not a shield. It belongs to item type 19 (small swords) instead. So %validslot% is zero.

    The BUT_ONLY IF_EXISTS is actually two logical checks. BUT_ONLY means that we don't save the item file to the override if we didn't change anything (we didn't), and IF_EXISTS means we skip the whole "load and check" process if the item file doesn't exist. For example, Akae's item zero is RNDTRE03, which is a random treasure token instead of an actual item. If that were in the shield slot instead of the quick item slot it's actually in, this code would note that it's not an actual item and skip the check. Which leaves %validslot% at zero.

    Then we END the INNER_ACTION. We're back on the creature file now. END the "if there's something in the shield slot" block, and check the ELSE - if there's nothing there, we'll consider that valid as well and set %validslot% to 1.

    Now, if the slot either had a shield or was empty, we do our animation stuff. Read the 4-byte animation field at 0x28 and call it %current%. Take our pre-built array of old/new animation pairs and iterate through it with PHP_EACH. If the "old" element of the pair matches %current%, we write the %new% element of the pair into the animation field at 0x28. There. Change made. Close things out with a BUT_ONLY (don't write the creature file if we didn't change anything) and move on to the next creature - because we're looping through all of them with that regexp.

    ....

    All right. That's it for explaining the current code. What about your other animation issue, regarding Ashatiel's wings? First off, that's not the only wing item out there - WINGS01, used by the Thralls of Azothet in Dorn's BGEE quest, also adds wings to an elf sprite. That's also done as a helmet, but I wouldn't recommend trusting that. Or that these two are the only such items.
    For this, I would recommend going through the list of all items. Check the "equipped appearance" field at 0x22 for whether it's "ZW". If so, add it to a list. Here's some code for that:
    ACTION_CLEAR_ARRAY WingItems
    OUTER_SET wingnum = 0
    COPY_EXISTING_REGEXP GLOB ~.*\.itm~ ~override~
      READ_ASCII 0x28 appearance (2)
      PATCH_IF (~%appearance%~ STR_EQ ~ZW~) BEGIN
        SPRINT $WingItems(~%wingnum%~) ~%SOURCE_RES%~
        SET wingnum = wingnum + 1
      END
    BUT_ONLY
    

    Run this before your "look at all creatures" loop, once, to generate the array. Now we can use that array inside the loop.

    Now, we want to check whether a creature has one of these items equipped? OK. We'll just run through all the items the creature has and check them against our list.
    SET haswings = 0
      FOR (n = 0; n < itemnumber; ++n) BEGIN
        READ_ASCII (%itemsoffset% + %n% * 0x14) itemname
        PATCH_PHP_EACH WingItems AS index => wingsid BEGIN
          PATCH_IF (~%itemname%~ STR_EQ ~%wingsid%~) BEGIN
            SET haswings = 1
          END
        END
      END
    

    Add this block to what you have. Outside the "if there's a shield/else" block, before the "change the animation" block. And then, we change the IF condition on that block to (validslot = 1 AND haswings = 0). Because, of course, those old BG1 animations don't support wings.

    This approach of looping through all items to build a list could also be done with the shields, of course. There's always more than one way to build code. I'd stick with what you have on the shields, though, for efficiency reasons - and so you don't need eniterly new code when you already have something that works.

    For that last question ... I don't know. I've never done anything with the APPEND action, and it strikes me as something that could easily cause unexpected problems. Also, it probably needs a newline added to the append text, though whether that's before or after I couldn't say.
  • AndreaColomboAndreaColombo Member Posts: 5,524
    edited May 2022
    @jmerry you are an encyclopedia of knowledge, a gentleman, and a scholar :)

    Thank you so much for explaining the code, spotting the bug, and helping me out with the code for the wing checks. I will, of course, credit you for it.

    This is the amended code:

    ACTION_CLEAR_ARRAY WingItems
      OUTER_SET wingnum = 0
      COPY_EXISTING_REGEXP GLOB ~.*\.itm~ ~override~
        READ_ASCII 0x28 appearance (2)
        PATCH_IF (~%appearance%~ STR_EQ ~ZW~) BEGIN
          SPRINT $WingItems(~%wingnum%~) ~%SOURCE_RES%~
          SET wingnum = wingnum + 1
        END
      BUT_ONLY
     COPY_EXISTING_REGEXP GLOB ~.*\.cre~ ~override~
      SET validslot = 0
      READ_LONG 0x2bc itemsoffset
      READ_LONG 0x2b8 itemslot
      READ_LONG 0x2c0 itemnumber
      READ_SSHORT (itemslot + 0x04) shieldslot
      PATCH_IF (shieldslot >= "0") BEGIN
        READ_ASCII ((LONG_AT 0x2bc) + %shieldslot% * 0x14) itemname
        INNER_ACTION BEGIN
          COPY_EXISTING ~%itemname%.itm~ ~override~
            READ_SHORT 0x1c itemtype
            PATCH_IF (itemtype = "12") BEGIN
              SET validslot = 1
            END
          BUT_ONLY IF_EXISTS
        END
      END
      ELSE BEGIN
        SET validslot = 1
      END
      SET haswings = 0
      FOR (n = 0; n < itemnumber; ++n) BEGIN
        READ_ASCII (%itemsoffset% + %n% * 0x14) itemname
        PATCH_PHP_EACH WingItems AS index => wingsid BEGIN
          PATCH_IF (~%itemname%~ STR_EQ ~%wingsid%~) BEGIN
            SET haswings = 1
          END
        END
      END
      PATCH_IF (validslot = 1 AND haswings = 0) BEGIN
        READ_LONG 0x28 current
        PATCH_PHP_EACH ac_animation_map AS old => new BEGIN
          PATCH_IF (old = current) BEGIN
            WRITE_LONG 0x28 new
           END
        END
      END
     BUT_ONLY
    

    Did I get it right?
  • jmerryjmerry Member Posts: 3,822
    Looks like it. Or, at least, you incorporated what I said. I wouldn't trust it until I actually tried it, of course; practically every bit of code I've done has needed to go through a few iterations before all the syntax errors got worked out.
  • AndreaColomboAndreaColombo Member Posts: 5,524
    I've just run the code on BG2:EE and it installs. It also turns out the APPEND action was, in fact, doing something; I'm going to keep it.

    Thanks again!
  • afrothundaaaaafrothundaaaa Member Posts: 16
    hello folks. Having some trouble getting a script to detect if a creature has the cleric spell defensive harmony applied. I was seeing how to detect it and the only thing I saw i could read from a trigger in my .baf was from CheckStat() CheckStatGT() or CheckStatLT() because it is setting the 'weapon proficiency' CLERIC_DEFENSIVE_HARMONY for the creature that it applies to. In my .baf script I have the following to attempt to detect if it is already applied, and to cast if it is not applied
    // Defensive Harmony
    IF
    	!CheckStat(Myself, 1, CLERIC_DEFENSIVE_HARMONY) 	// True if Def Harmony not set
    	HaveSpell(CLERIC_DEFENSIVE_HARMONY)			// True if Spell Exists
    THEN
    	RESPONSE	#100
    		Spell(Myself, CLERIC_DEFENSIVE_HARMONY)		// Cast Spell
    END
    

    But this consistently fires until all memorizations of the spell are exhausted. I have tried doing with the GT/LT variants with no success either. I'm not sure why this won't work.

    What can I do to ensure this detects the spell correctly? There is no state in splstate.ids that applies. Any help is appreciated.
Sign In or Register to comment.