Skip to content

General mod Questions thread

1202123252670

Comments

  • kjeronkjeron Member Posts: 2,367
    edited April 2018
    The PC animations also use some cycles of prefix+G17.bam and prefix+G18.bam when idle.

    Monsters that only have G1/G1E, have all the animations of G11-G19 (at least those it uses) stored in that single bam file.
    ArunsunBubb
  • BubbBubb Member Posts: 998
    edited April 2018
    Wow, that's a lot more complicated than I expected it to be! Thanks for all the detailed answers everyone, it really helps a lot; I know for a fact I wouldn't have figured much of that out on my own.
    Post edited by Bubb on
  • _Luke__Luke_ Member, Mobile Tester Posts: 1,535
    edited April 2018
    Suppose I want my kit to be able to deal extra damage with flails.

    1) I need to patch every flail with an equipping 177 effect (Self, Instant/While equipped, resource: EFF1.EFF).
    2) EFF1.EFF must be a 248 (Melee hit effect, target: Self, Instant/while equipped, resource: EFF2.EFF).
    3) EFF2.EFF must be a 146 (Cast spell, target: Preset target, Instant/permanent until death, resource: SPL1.SPL).
    4) And finally SPL1.SPL must contain a spell ability with opcode 12 (Damage, Preset target, Instant/permanent until death).

    Is that correct? In particular, I cannot skip step #2, right?
  • kjeronkjeron Member Posts: 2,367
    @Luke93
    That will allow you to deal extra damage with any other weapon you wield along with a flail (when dual-wielding), or deal double extra damage with each flail if dual-wielding two flails.

    1) Patch flails adding an effect to each melee weapon ability:
    - Opcode 326, target=self, parameter1=(KITID), parameter2=109 (kit), resource=(SPL1.SPL)
    2) SPL1.SPL, with spell ability containing:
    - Opcode 326, target=preset, resource=(SPL2.SPL)
    3) SPL2.SPL, with spell ability containing:
    - Opcode 12, target=preset, timing=(1 or 9), duration=0

    Step 2 here is only so SPL2.SPL is cast at the wielder's level, otherwise it will be cast at level 10 (default for items). If the damage isn't going to scale with level, then you can skip step 2, and rename step 3 to SPL1.SPL.
    _Luke_
  • semiticgoddesssemiticgoddess Member Posts: 14,903
    How do you check for names in WeiDu and are there fields that WeiDu only edits using hex? I'm working with IWD2 and I'd like to set all .cre files with the name "Keg" (string reference 5124 or 5125) to have a custom script called "USSTKEG1.bcs," which is intended to make them die after one hit:

    IF
    HPLT(Myself,100)
    !Dead(Myself)
    THEN
    RESPONSE #100
    Kill(Myself)
    END
  • The user and all related content has been deleted.
  • semiticgoddesssemiticgoddess Member Posts: 14,903
    WRITE_ASCII lets me set the script, but READ_ASCII appears to result in a syntax error. I've tried reading for the string references (5124 and 5125) instead of the text itself, "Keg," but I'm getting nothing. The code doesn't crash WeiDu but the IF condition never appears to return a "true."

    COPY_EXISTING_REGEXP ~.*\.cre~ ~override~

    READ_BYTE "0x8" "name"

    WRITE_SHORT 0x274 USSTKEG1

    IF_EVAL ("%name%" = "0x5124")

    I'm using the same code I used to set challenge rating equal to total level, replacing both with the name. Even though the string reference is a number, I can't seem to use it the same way. I'm guessing there's some minor error in my code.

    Another question: does WeiDu have any mathematical operators for variables? I'd like to create a formula that would change a critter's saving throws based on its levels, like making its Will save equal to its cleric level plus half its fighter level.
  • BubbBubb Member Posts: 998
    edited May 2018
    @semiticgod
    READ_BYTE is not the correct operation to use for reading the name of a creature. You can either use READ_STRREF (to get the actual string) or READ_LONG (to get the strref). I don't have IWD2 so I can't confirm if this works, but something like this should do:

    COPY_EXISTING_REGEXP ~.+\.cre~ ~override~ READ_STRREF 0x0008 name PATCH_IF ~%name%~ STRING_EQUAL ~Keg~ BEGIN WRITE_ASCII 0x0274 ~USSTKEG1~ #8 END BUT_ONLY_IF_IT_CHANGES
    Note that checking against the string only works in the English language. It might actually be safer to compare against the strref.

    Edit: WRITE_ASCII needs the # 8 in order to completely replace whatever was already in the override script field.
    semiticgoddess
  • semiticgoddesssemiticgoddess Member Posts: 14,903
    @Bubb: Works perfectly! Thank you!
  • [Deleted User][Deleted User] Posts: 0
    edited May 2018
    The user and all related content has been deleted.
  • kjeronkjeron Member Posts: 2,367

    Is that sufficient? Or will that return false positives? Do I need to add a line to SPLPROT to check for true equality?

    No. It will return false positives. You will need to add a new row checking for exact equality (1).

    Why does the game have an entry checking for bit equality?

    No idea.
  • RaduzielRaduziel Member Posts: 4,714
    kjeron said:

    No idea.

    So... this is how the world ends...
    [Deleted User]semiticgoddess_Luke_Contemplative_Hamster
  • _Luke__Luke_ Member, Mobile Tester Posts: 1,535
    edited May 2018
    kjeron said:


    2) SPL1.SPL, with spell ability containing:
    - Opcode 326, target=preset, resource=(SPL2.SPL)

    You meant opcode 146 (Cast Spell ), didn't you?
  • kjeronkjeron Member Posts: 2,367
    Luke93 said:

    kjeron said:


    2) SPL1.SPL, with spell ability containing:
    - Opcode 326, target=preset, resource=(SPL2.SPL)

    You meant opcode 146 (Cast Spell ), didn't you?
    It could be either, but the effect added to the weapon abilities must be opcode 326.
    _Luke_
  • semiticgoddesssemiticgoddess Member Posts: 14,903
    I'd like to create a script that causes the user in question to die after a single hit, regardless of their HP. But I'm having trouble making this code work:

    IF
    HPPercentLT(Myself,100)
    !Dead(Myself)
    THEN
    RESPONSE #100
    Kill(Myself)
    END

    I'm using it on the kegs in IWD2, so maybe there's something about how those kegs work that prevents them from using scripts. They're coded as "neutral" in terms of their allegiance but have yellow circles instead of blue, and even when I disable the "Helpless" status effect on their .cre files, the player still gets automatic hits on them, so maybe they have some hardcoded effects active.

    I've also tried tacking on some triggers I stole from the townspeople:

    IF
    Or(12)
    HitBy([GOODCUTOFF],SLASHING)
    HitBy([GOODCUTOFF],CRUSHING)
    HitBy([GOODCUTOFF],PIERCING)
    HitBy([GOODCUTOFF],MISSILE)
    HitBy([GOODCUTOFF],FIRE)
    HitBy([GOODCUTOFF],ELECTRICITY)
    HitBy([GOODCUTOFF],POISON)
    HitBy([GOODCUTOFF],MAGIC)
    HitBy([GOODCUTOFF],COLD)
    HitBy([GOODCUTOFF],ACID)
    HitBy([GOODCUTOFF],MAGICFIRE)
    HitBy([GOODCUTOFF],MAGICCOLD)
    THEN
    RESPONSE #100
    SetInterrupt(FALSE)
    Kill(Myself)
    END

    And instead of setting the script to be the override script, I made it the "Special Script 1," which is the same field used by Kegs of Blasting. Kegs of Blasting have a fire-specific instant death effect on self that triggers after 3 seconds:

    IF
    Global("Fuse","LOCALS",0)
    IsHeartOfFuryModeOn()
    HitBy([ANYONE],FIRE)
    THEN
    RESPONSE #100
    SetInterrupt(FALSE)
    ChangeSpecifics(Myself,NORMAL)
    SetCreatureAreaFlag(Myself,NON_THREATING_ENEMY,FALSE)
    CreateItem("00CIKEG3",1,0,0) // Exploding Keg
    Wait(1)
    SetGlobal("Fuse","LOCALS",1)
    Shout(3)
    UseItem("00CIKEG3",Myself) // Exploding Keg
    Kill(Myself)
    END

    IF
    Global("Fuse","LOCALS",0)
    Or(2)
    CheckAreaDiffLevel(3)
    CheckAreaDiffLevel(2)
    HitBy([ANYONE],FIRE)
    THEN
    RESPONSE #100
    ChangeSpecifics(Myself,NORMAL)
    CreateItem("00CIKEG2",1,0,0) // Exploding Keg
    SetCreatureAreaFlag(Myself,NON_THREATING_ENEMY,FALSE)
    SetGlobal("Fuse","LOCALS",1)
    SpellHitEffectSprite(Myself,Myself,FIRE,0)
    FloatMessage(Myself,24086) // 3
    Wait(1)
    END

    IF
    Global("Fuse","LOCALS",1)
    THEN
    RESPONSE #100
    SetGlobal("Fuse","LOCALS",2)
    SpellHitEffectSprite(Myself,Myself,FIRE,0)
    FloatMessage(Myself,24087) // 2
    Wait(1)
    END

    IF
    Global("Fuse","LOCALS",2)
    THEN
    RESPONSE #100
    SetGlobal("Fuse","LOCALS",3)
    SpellHitEffectSprite(Myself,Myself,FIRE,0)
    FloatMessage(Myself,24088) // 1
    Wait(1)
    END

    IF
    Global("Fuse","LOCALS",3)
    THEN
    RESPONSE #100
    Shout(3)
    UseItem("00CIKEG2",Myself) // Exploding Keg
    Kill(Myself)
    END

    IF
    Global("Fuse","LOCALS",0)
    HitBy([ANYONE],FIRE)
    THEN
    RESPONSE #100
    ChangeSpecifics(Myself,NORMAL)
    CreateItem("00CIKEG1",1,0,0) // Exploding Keg
    SetCreatureAreaFlag(Myself,NON_THREATING_ENEMY,FALSE)
    SetGlobal("Fuse","LOCALS",4)
    SpellHitEffectSprite(Myself,Myself,FIRE,0)
    FloatMessage(Myself,24086) // 3
    Wait(1)
    END

    IF
    Global("Fuse","LOCALS",4)
    THEN
    RESPONSE #100
    SetGlobal("Fuse","LOCALS",5)
    SpellHitEffectSprite(Myself,Myself,FIRE,0)
    FloatMessage(Myself,24087) // 2
    Wait(1)
    END

    IF
    Global("Fuse","LOCALS",5)
    THEN
    RESPONSE #100
    SetGlobal("Fuse","LOCALS",6)
    SpellHitEffectSprite(Myself,Myself,FIRE,0)
    FloatMessage(Myself,24088) // 1
    Wait(1)
    END

    IF
    Global("Fuse","LOCALS",6)
    THEN
    RESPONSE #100
    Shout(3)
    UseItem("00CIKEG1",Myself) // Exploding Keg
    Kill(Myself)
    END

    IF
    !Specifics(Myself,200)
    THEN
    RESPONSE #100
    ChangeSpecifics(Myself,200)
    END

    IF
    !IsCreatureAreaFlag(Myself,NON_THREATING_ENEMY)
    THEN
    RESPONSE #100
    SetCreatureAreaFlag(Myself,NON_THREATING_ENEMY,TRUE)
    END

    If all else fails, I'd like to give the kegs an item that casts a spell on self when hit that instantly kills them. If scripts aren't an option, how would I use WeiDu to put an item on a critter? I've already got the conditional part of the code from my previous attempts:

    COPY_EXISTING_REGEXP ~.+\.cre~ ~override~
    READ_LONG 0x0008 name
    PATCH_IF ~%name%~ EQUALS ~5124~ BEGIN

    ????

    END
    BUT_ONLY_IF_IT_CHANGES
  • The user and all related content has been deleted.
    semiticgoddess
  • semiticgoddesssemiticgoddess Member Posts: 14,903
    I have an idea for a skip for IWD2. I think I have the right scripts in place--I just need a door to open when a creature dies--but now I need to add a creature to the right area. How would I add a creature called USSTIW01.cre to an area called AR4100.are, at coordinates 230, 750?
  • semiticgoddesssemiticgoddess Member Posts: 14,903
    I found a workaround. I just appended a few lines to the bottom of the area script. Since it took some work, I'll explain how I did it in case other people need to solve the same problem.

    What I wanted to do is to allow the player to open a door by providing a creature they could destroy (normally, that door only opens after you do a lot of different quest triggers). When the critter dies, the door is opened. So I put this in my TP2:

    EXTEND_BOTTOM ~AR4100.bcs~ ~testWeiDu/baf/USSTIW06.baf~

    That refers to a .baf file in my "testWeiDu" folder. That .baf file reads:

    IF
    !Global("US_IW1","GLOBAL",1)
    THEN
    RESPONSE #100
    CreateCreature("USSTIW01","",[230.750],0) // Ice Wall Fault
    SetGlobal("US_IW1","GLOBAL",1)
    END

    Those variables are mod-specific variables I created myself; that way I don't have to use any other variables that might conflict with other scripts and stuff. Anyway, this adds a few lines to the bottom of the area script.

    The area script creates exactly one critter called USSTIW01 and places it at coordinates 230,750. Once the critter is there, it switches the variable to 1 so that that trigger never activates again (without it, the area would spawn a new critter every second or so, infinitely). My initial, erroneous code created infinite critters:

    IF
    True()
    THEN
    RESPONSE #100
    CreateCreature("USSTIW01","",[230.750],0) // Ice Wall Fault
    END

    Now, I wasn't able to set a script to trigger properly after the critter's death, presumably because its script stops functioning the moment the critter dies (only a few of the post-death actions I wanted happened). Instead, I gave the critter a MINHP1 item and rigged its script to fire when it reached 10% HP. This (much longer) script gives the party some XP, plays a sound and makes the screen shake, and then borrows some (but not all!) of the lines of a vanilla script that it seeks to replace.

    (Normally, that door opens with a cutscene that features both Nathaniel and some Luskan soldiers. Since it's possible for the player to destroy that critter without doing any of the related quests, that means that the subsequent dialogue doesn't make any sense (they say you killed a bunch of priestesses, but my mod allows you to skip that). So, rather than tweak the dialogue, I decided to remove Nathaniel and the Luskan soldiers from the door-opening script.)

    The script then opens the door, sets a few variables to the same values they'd be if the player opened the door the normal way (I assume this will decrease the chance of any weird bugs), and then kills the critter via script so the player can't try to do it again for more XP. The non-cosmetic actions are in bold:

    IF
    HPPercentLT(Myself,10)
    THEN
    RESPONSE #100
    AddExperiencePartyCR(30)
    PlaySoundPoint("AM2101g",[230.750])
    ScreenShake(30,25,25)
    CutSceneId("Nathaniel") // Nathaniel
    HideGUI()
    Explore()
    FadeToColor([0.0],0)

    Wait(1)
    MoveViewPoint([1055.705],INSTANT)
    ClearActions(Player1)
    ClearActions(Player2)
    ClearActions(Player3)
    ClearActions(Player4)
    ClearActions(Player5)
    ClearActions(Player6)
    ActionOverride(Player1,JumpToPoint([1060.760]))
    ActionOverride(Player2,JumpToPoint([1060.760]))
    ActionOverride(Player3,JumpToPoint([1060.760]))
    ActionOverride(Player4,JumpToPoint([1060.760]))
    ActionOverride(Player5,JumpToPoint([1060.760]))
    ActionOverride(Player6,JumpToPoint([1060.760]))
    FadeFromColor([0.0],0)
    ActionOverride(Player1,FaceObject("Nathaniel")) // Nathaniel
    ActionOverride(Player2,FaceObject("Nathaniel")) // Nathaniel
    ActionOverride(Player3,FaceObject("Nathaniel")) // Nathaniel
    ActionOverride(Player4,FaceObject("Nathaniel")) // Nathaniel
    ActionOverride(Player5,FaceObject("Nathaniel")) // Nathaniel
    ActionOverride(Player6,FaceObject("Nathaniel")) // Nathaniel
    SetGlobal("41NateDestroySourceQuest","GLOBAL",3)
    SetGlobal("41WesternPassDone","GLOBAL",1)
    Wait(1)
    PlaySequence(Myself,CAST)
    SpellCastEffect(Myself,"","","",GLOW_INVOCATION,1,ATTACK)
    ActionOverride(Player1,Face(N))
    ActionOverride(Player2,Face(N))
    ActionOverride(Player3,Face(N))
    ActionOverride(Player4,Face(N))
    ActionOverride(Player5,Face(N))
    ActionOverride(Player6,Face(N))
    SpellHitEffectPoint(Myself,[960.600],DISINTEGRATE,0)
    SpellHitEffectPoint(Myself,[991.607],DISINTEGRATE,0)
    SpellHitEffectPoint(Myself,[1183.351],DISINTEGRATE,0)
    SpellHitEffectPoint(Myself,[735.671],DISINTEGRATE,0)
    SpellHitEffectPoint(Myself,[671.287],DISINTEGRATE,0)
    SpellHitEffectPoint(Myself,[1247.351],DISINTEGRATE,0)
    SpellHitEffectPoint(Myself,[980.415],DISINTEGRATE,0)
    SpellHitEffectPoint(Myself,[1180.480],DISINTEGRATE,0)
    OpenDoor("AR4100_ICEWALL")
    SetGlobal("50Dragon","GLOBAL",1)
    UnhideGUI()
    EndCutSceneMode()
    Kill(Myself)

    END

    IF
    Global("41WesternPassDone","GLOBAL",1)
    !Dead(Myself)
    THEN
    RESPONSE #100
    Kill(Myself)
    END

    That last IF section is designed to prevent the player from opening the door both the normal way and the new way (which would give them extra XP and might cause some horrible bugs). Basically, if the player opens the door normally, that critter will destroy itself automatically. This way, you can't open the door twice.
  • semiticgoddesssemiticgoddess Member Posts: 14,903
    How would I patch individual doors within an area? I want to change a door's lock difficulty from 500% to 99% so the player can force it open without needing a key.




    I can find the offset, but apparently it has 200 bytes and I can't even see the whole "value" attached to it.


  • The user and all related content has been deleted.
  •  TheArtisan TheArtisan Member Posts: 3,277
    edited May 2018
    @subtledoctor

    Append to the itemspec.2da file. Make an ITEMSPEC.txt with a line like this:
    ITEMCODE 1
    And a line in your .tp2 like this:
    COPY_EXISTING ~ITEMSPEC.2da~ ~override~ APPEND_FILE ~%MOD_FOLDER%/items/ITEMSPEC.txt~
  • The user and all related content has been deleted.
    Raduziel
  • GwendolyneGwendolyne Member Posts: 461
    edited May 2018
    It works fine, but the only drawback is that the identifying screen doesn't display the name of the new item, only the "scroll" option (the spell one is grayed).

  • semiticgoddesssemiticgoddess Member Posts: 14,903
    I figured out how to reach individual bytes with WeiDu!

    Basically, what I wanted to do was to change the lock difficulty of a door from 500 to 99. But when I looked at the offsets for Door 0, all I could see was the range of bytes, starting with 9508. That "structure" had 200 bytes, but I only wanted to deal with one piece of data. How do I find which byte is the lock difficulty?

    Near Infinity places the lock difficulty parameter just below the door's script, which in this case is 30PMAING. I used the Find function to look for "30PMAING" in the offsets. I find "30PMAING," which is followed by several zeroes... and then F4, followed by 01. I don't know what that means, so I take the current lock difficulty (500), use a hex converter to find out its hex value, and it says 1F4... instead of F401. I don't know it looks like it's reversed.

    But now I know how to edit it. Now, the "structure" starts at 9508, but it continues all the way to 95C something, 200 bytes ahead. So I count the bytes from the start of 9508 (highlighted in green in Near Infinity) up to that "F4" that indicates the field I want. I copy all the numbers into Microsoft Word, highlight the numbers I want to count, and then use Review to get the word count, which is 140. That's how many bytes the "F4" is from the start of the structure. I already know the start of the structure is 9508 in hex, so I convert it to a decimal, add 140, and then convert it back into hex.

    Now I have 9594! That's the byte that I want to edit. I also want the one after it, which will be 9595. Ultimately, I want to end up with a 99 in that field, so I use this code in WeiDu to patch the area:

    COPY_EXISTING ~AR3000.are~ ~override~
    WRITE_BYTE 0x9594 99
    WRITE_BYTE 0x9595 00
    BUT_ONLY

    Now the lock difficulty is set to 99! I repeat the steps for another area, allowing me to set a completely different area's door's lock difficulty to 99.

    COPY_EXISTING ~AR6103.are~ ~override~
    WRITE_BYTE 0xE428 99
    BUT_ONLY

    Next, I want to change an area transition in a separate area. That's a completely different field in a completely different file. I had trouble with this one. See, the field I'm editing is no longer just a number; it has letters in it. The current value is "AR6103," but I want to change it to "AR6104." When I try to patch letters onto that byte, WeiDu fails due to a parsing error. So I can't do that.

    Instead, I just ignore the letters. I don't need to change the AR; I just need to change 6104 to 6200. But when I try to put in new values...

    COPY_EXISTING ~AR6104.are~ ~override~
    WRITE_BYTE 0x91E6 6
    WRITE_BYTE 0x91E7 2
    WRITE_BYTE 0x91E8 0
    WRITE_BYTE 0x91E9 0
    BUT_ONLY

    ...or when I try to write multiple bytes at once...

    COPY_EXISTING ~AR6104.are~ ~override~
    WRITE_BYTE 0x91E6 6200
    BUT_ONLY

    ...I get null values in that field. That means the area transition goes nowhere, which either breaks the transition or just crashes the game. After several false starts, I figure out the solution. See, "AR6104" is in ASCII. That's the only way the field could have letters in it. I don't know how to write those values as a string, but I can take those letters, convert them into ASCII one by one, and then program WeiDu to write them instead. The 6 becomes a 36, the 32 becomes a 2, and the zeroes become 30's. Notice that, in order to write ASCII using WeiDu's WRITE_BYTE command, I have to add "0x" before each value.

    COPY_EXISTING ~AR6104.are~ ~override~
    WRITE_BYTE 0x91E6 0x36
    WRITE_BYTE 0x91E7 0x32
    WRITE_BYTE 0x91E8 0x30
    WRITE_BYTE 0x91E9 0x30
    BUT_ONLY

    That code pinpoints the exact field I'm looking for, which goes from at 91E4 to 91E9, though we can only see the greater structure in Near Infinity, which is just 91AC. By counting the bytes from the start of 91AC to the numbers I'm looking for, I can pinpoint the exact bytes I want to change and then replace them using ASCII characters, without touching all the other bytes in that giant 200-byte structure.
  • [Deleted User][Deleted User] Posts: 0
    edited May 2018
    The user and all related content has been deleted.
    semiticgoddessArunsun
  • semiticgoddesssemiticgoddess Member Posts: 14,903
    I do wish I'd waited for a response before solving my own problem... That would have saved me over an hour of fussing with hex stuff. At least now I have a better tool!
  • The user and all related content has been deleted.
    semiticgoddess
  • argent77argent77 Member Posts: 3,431
    The game resources are all using little endian byte order, i.e. numeric values that take up more than one byte are laid out starting with the lowest order byte. For example, hex value 0x12345678 is written as 0x78, 0x56, 0x34 and 0x12 to the file.

    Byte order is really only relevant if you want to make changes to a byte buffer directly, which is rarely the case (e.g. if you want to directly flip a bit in a 16-bit or 32-bit data field). Using the READ/WRITE functions for the right right datatype (e.g. READ_LONG/WRITE_LONG for 32-bit values or READ_SHORT/WRITE_SHORT for 16-bit value) takes care of it automatically.
  • semiticgoddesssemiticgoddess Member Posts: 14,903
    @argent77: What's a byte buffer?
  • argent77argent77 Member Posts: 3,431
    I was just referring to the raw sequence or stream of bytes WeiDU would read data from with the READ_* functions. Sorry for not being more clear. Afaik, WeiDU's COPY routine reads the whole file content into a buffer to speed up individual READ/WRITE operations.
    semiticgoddess
Sign In or Register to comment.