It occurs to me that you can reverse all of the EE edits that prevented stuff from stacking by removing opcode 321, Remove Effects by Resource (and maybe opcode 318, which I believe is an EE-exclusive Protection from Spell opcode) from all spells and items.
How would you erase all traces of opcode 321 in WeiDu?
How could I add lines to a .bcs file with weidu? I want to add an extra block of script to baldur.bcs to fix a bug. I have the text I want to add, but am not sure how to command weidu to put it in there.
DECOMPILE_BCS_TO_BAF has been superseded by DECOMPILE_AND_PATCH. It has the advantage that a compile error would leave no decompiled script file behind.
The same sample code would look like this: COPY_EXISTING ~baldur.bcs~ ~override~
DECOMPILE_AND_PATCH BEGIN
REPLACE_TEXTUALLY ~old code~ ~new code~
END
BUT_ONLY
@subtledoctor Thanks! That was easier than I anticipated.
@Gwendolyne Are you sure? According to the weidu documentation that I read you are supposed to use DECOMPILE_AND_PATCH instead of DECOMPILE_BCS_TO_BAF and COMPILE_BAF_TO_BCS, but what do I know?
@Tresset and @Argent77 You are right. It is just a bad habit. I have been using it for so long that I always write it this way before remembering to replace it with DECOMPILE_AND_PATCH.
Is there some mysterious magical trick to REPLACE_TEXTUALLY when it comes to editing .bcs files? I am copying the block I want to remove directly from NI but nothing ever gets overwritten...
Edit: I am thinking that I am not using REGEXP syntax properly...
REPLACE_TEXTUALLY is not the best choice if you want to remove or replace more than a single line from a script. For example, it doesn't match newlines easily (you'd have to specifically match %LNL% or %WNL%).
If you want to remove or replace whole script blocks I would recommend to use INDEX_BUFFER() instead. It returns the file position of search strings. With subsequent DELETE_BYTES you can remove the old block and with INSERT_BYTES/WRITE_ASCIIE insert new blocks.
Your example code using INDEX_BUFFER: [spoiler]// predefine sequence of new script blocks
OUTER_TEXT_SPRINT newScript
~IF
!Allegiance(NearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC)
See(NearestEnemyOfType([GOODCUTOFF.UNDEAD]))
!HPLT(Myself,20)
!GlobalTimerNotExpired("UndeadTurn","LOCALS")
Range(LastSeenBy(Myself),20)
!HPGT(LastSeenBy(Myself),75)
THEN
RESPONSE #100
SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND)
DisplayStringHead(Myself,73243) // Undead thing begone!
CreateVisualEffectObject("SPDISPMA",NearestEnemyOfType([GOODCUTOFF.UNDEAD]))
ActionOverride(NearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself))
END
IF
!Allegiance(SecondNearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC)
See(SecondNearestEnemyOfType([GOODCUTOFF.UNDEAD]))
!HPLT(Myself,20)
!GlobalTimerNotExpired("UndeadTurn","LOCALS")
Range(LastSeenBy(Myself),20)
!HPGT(LastSeenBy(Myself),75)
THEN
RESPONSE #100
SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND)
DisplayStringHead(Myself,73243) // Undead thing begone!
CreateVisualEffectObject("SPDISPMA",SecondNearestEnemyOfType([GOODCUTOFF.UNDEAD]))
ActionOverride(SecondNearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself))
END
IF
!Allegiance(ThirdNearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC)
See(ThirdNearestEnemyOfType([GOODCUTOFF.UNDEAD]))
!HPLT(Myself,20)
!GlobalTimerNotExpired("UndeadTurn","LOCALS")
Range(LastSeenBy(Myself),20)
!HPGT(LastSeenBy(Myself),75)
THEN
RESPONSE #100
SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND)
DisplayStringHead(Myself,73243) // Undead thing begone!
CreateVisualEffectObject("SPDISPMA",ThirdNearestEnemyOfType([GOODCUTOFF.UNDEAD]))
ActionOverride(ThirdNearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself))
END
IF
!Allegiance(FourthNearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC)
See(FourthNearestEnemyOfType([GOODCUTOFF.UNDEAD]))
!HPLT(Myself,20)
!GlobalTimerNotExpired("UndeadTurn","LOCALS")
Range(LastSeenBy(Myself),20)
!HPGT(LastSeenBy(Myself),75)
THEN
RESPONSE #100
SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND)
DisplayStringHead(Myself,73243) // Undead thing begone!
CreateVisualEffectObject("SPDISPMA",FourthNearestEnemyOfType([GOODCUTOFF.UNDEAD]))
ActionOverride(FourthNearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself))
END
IF
!Allegiance(FifthNearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC)
See(FifthNearestEnemyOfType([GOODCUTOFF.UNDEAD]))
!HPLT(Myself,20)
!GlobalTimerNotExpired("UndeadTurn","LOCALS")
Range(LastSeenBy(Myself),20)
!HPGT(LastSeenBy(Myself),75)
THEN
RESPONSE #100
SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND)
DisplayStringHead(Myself,73243) // Undead thing begone!
CreateVisualEffectObject("SPDISPMA",FifthNearestEnemyOfType([GOODCUTOFF.UNDEAD]))
ActionOverride(FifthNearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself))
END
IF
!Allegiance(SixthNearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC)
See(SixthNearestEnemyOfType([GOODCUTOFF.UNDEAD]))
!HPLT(Myself,20)
!GlobalTimerNotExpired("UndeadTurn","LOCALS")
Range(LastSeenBy(Myself),20)
!HPGT(LastSeenBy(Myself),75)
THEN
RESPONSE #100
SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND)
DisplayStringHead(Myself,73243) // Undead thing begone!
CreateVisualEffectObject("SPDISPMA",SixthNearestEnemyOfType([GOODCUTOFF.UNDEAD]))
ActionOverride(SixthNearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself))
END
IF
!Allegiance(SeventhNearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC)
See(SeventhNearestEnemyOfType([GOODCUTOFF.UNDEAD]))
!HPLT(Myself,20)
!GlobalTimerNotExpired("UndeadTurn","LOCALS")
Range(LastSeenBy(Myself),20)
!HPGT(LastSeenBy(Myself),75)
THEN
RESPONSE #100
SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND)
DisplayStringHead(Myself,73243) // Undead thing begone!
CreateVisualEffectObject("SPDISPMA",SeventhNearestEnemyOfType([GOODCUTOFF.UNDEAD]))
ActionOverride(SeventhNearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself))
END~
COPY_EXISTING ~Meliss01.bcs~ ~override~
~Meliss02.bcs~ ~override~
~Meliss03.bcs~ ~override~
DECOMPILE_AND_PATCH BEGIN
// use a search string that can only be found in the script block to replace
SET ofs = INDEX_BUFFER(~GlobalTimerNotExpired("UndeadTurn","LOCALS")~)
PATCH_IF (ofs >= 0) BEGIN
// find start of target script block
SET ofsStart = RINDEX_BUFFER( ~^IF$~ ofs )
PATCH_IF (ofsStart >= 0) BEGIN
// find end of target script block
SET ofsEnd = INDEX_BUFFER( ~^END$~ ofsStart ) + 3
PATCH_IF (ofsEnd > ofsStart) BEGIN
// remove old block
DELETE_BYTES ofsStart (ofsEnd - ofsStart)
// insert sequence of new blocks (that are defined above)
SET strlen = STRING_LENGTH ~%newScript%~
INSERT_BYTES ofsStart strlen
WRITE_ASCIIE ofsStart ~%newScript%~ (strlen)
END
END
END
END
BUT_ONLY
[/spoiler]
@argent77 THANK YOU VERY MUCH!!! I doubt I would have ever figured out to do it that way. I will have to study that code carefully for future reference!
Would it be possible to use a custom spell to assign a custom kit to the target? I can use Change AI Type to turn a critter into a ranger, but I don't know how to give it a kit, much less a non-vanilla kit.
Would it be possible to use a custom spell to assign a custom kit to the target? I can use Change AI Type to turn a critter into a ranger, but I don't know how to give it a kit, much less a non-vanilla kit.
KIT.IDS is not available for opcode 72 (Change AI type). Only script actions work:
AddKit(KIT) // Remove existing kit CLAB features and apply new kits CLAB level by level.
AddSuperKit(KIT) // Retain existing kit CLAB features and apply new kits CLAB level by level.
ChangeStat(Myself,KIT,#,ADD/SET) // Retain existing kit CLAB features, does not apply new kits CLAB.
This is more of a general coding question than a Weidu question. But does anyone know how to move an entire chain of journal entries into the Quest Done section from the active quest section? I don't want to leave the original journal entries dangling once my custom quest is finished.
Journal entries can be a bit complicated. But if you add something like the below to the tp2, you can group several journal entry chains together (you need a translation file for this though): // JOURNAL
ADD_JOURNAL TITLE (@10000) @10001 @10002 @10003 @10004 @10005 @10006 @10007 @10008 USING ~%MOD_FOLDER%/translations/english/setup.tra~
ADD_JOURNAL TITLE (@20000) @20001 @20002 USING ~%MOD_FOLDER%/translations/english/setup.tra~ Check out my Drake and Aura NPC mods which make use of the journal. Also, there is an EraseJournalEntry() command.
Journal entries can be a bit complicated. But if you add something like the below to the tp2, you can group several journal entry chains together (you need a translation file for this though): // JOURNAL
ADD_JOURNAL TITLE (@10000) @10001 @10002 @10003 @10004 @10005 @10006 @10007 @10008 USING ~%MOD_FOLDER%/translations/english/setup.tra~
ADD_JOURNAL TITLE (@20000) @20001 @20002 USING ~%MOD_FOLDER%/translations/english/setup.tra~ Check out my Drake and Aura NPC mods which make use of the journal. Also, there is an EraseJournalEntry() command.
Thanks. I was hoping for a nice simple 'move journal entry' command but I suppose that would be too easy :-)
Sounds like I'll have to go the deleting entry route after all, though your code might help me add/delete a bunch at once. So thanks.
For a completely different question, does anyone know a command to change a character's thief skills after a certain piece of dialogue? I know the game changes the PC's stats from dialogue sometimes, but I can't recall a place where a companion gets changed instead to go look at the code.
I want to have a conversation between Jan and a modded thief that causes a +5 increase to the mod thief's pickpocket skill. Can I just edit their character file somehow?
Make a nameless spell that provides +5% Pick Pockets with the duration type set to 9 (permanent after death). Use ApplySpellRES(SpellRES,Target) in the dialog to cast the spell on the target.
Thanks for the advice. I don't plan to make too many spells at the moment but it's always good to know how to do things properly. Once I get to that point, I'll have to see how many spells I actually need and then go from there.
I did manage to code my first 2 interjections ever yesterday so there's progress being made.
@subtledoctor: I just had an idea for a small mod and I think you know how to do it. I'd like to create a mod that makes plot-immune critters go to 1 HP whenever one of their ability scores goes below 1 or 2. The idea is to allow stat drain to trigger death dialogues like Abazigal's or Irenicus' without breaking a script somewhere--very convenient for Archers using STR drain, Haer'dalis using DEX drain, or mages using INT drain.
I know that WeiDu can apply the relevant effect to any item with the minimum HP opcode, but I don't know how to rig an opcode to apply a nonmagical Harm effect on self when an ability score hits a certain value.
Comments
How would you erase all traces of opcode 321 in WeiDu?
The same sample code would look like this:
COPY_EXISTING ~baldur.bcs~ ~override~ DECOMPILE_AND_PATCH BEGIN REPLACE_TEXTUALLY ~old code~ ~new code~ END BUT_ONLY
@Gwendolyne Are you sure? According to the weidu documentation that I read you are supposed to use DECOMPILE_AND_PATCH instead of DECOMPILE_BCS_TO_BAF and COMPILE_BAF_TO_BCS, but what do I know?
Edit: Ninja'd by argent77.
Edit: I am thinking that I am not using REGEXP syntax properly...
I figure I need a whole ton of backslashes and things in this code, but I have no idea where to put them...
If you want to remove or replace whole script blocks I would recommend to use INDEX_BUFFER() instead. It returns the file position of search strings. With subsequent DELETE_BYTES you can remove the old block and with INSERT_BYTES/WRITE_ASCIIE insert new blocks.
Your example code using INDEX_BUFFER:
[spoiler]
// predefine sequence of new script blocks OUTER_TEXT_SPRINT newScript ~IF !Allegiance(NearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC) See(NearestEnemyOfType([GOODCUTOFF.UNDEAD])) !HPLT(Myself,20) !GlobalTimerNotExpired("UndeadTurn","LOCALS") Range(LastSeenBy(Myself),20) !HPGT(LastSeenBy(Myself),75) THEN RESPONSE #100 SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND) DisplayStringHead(Myself,73243) // Undead thing begone! CreateVisualEffectObject("SPDISPMA",NearestEnemyOfType([GOODCUTOFF.UNDEAD])) ActionOverride(NearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself)) END IF !Allegiance(SecondNearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC) See(SecondNearestEnemyOfType([GOODCUTOFF.UNDEAD])) !HPLT(Myself,20) !GlobalTimerNotExpired("UndeadTurn","LOCALS") Range(LastSeenBy(Myself),20) !HPGT(LastSeenBy(Myself),75) THEN RESPONSE #100 SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND) DisplayStringHead(Myself,73243) // Undead thing begone! CreateVisualEffectObject("SPDISPMA",SecondNearestEnemyOfType([GOODCUTOFF.UNDEAD])) ActionOverride(SecondNearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself)) END IF !Allegiance(ThirdNearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC) See(ThirdNearestEnemyOfType([GOODCUTOFF.UNDEAD])) !HPLT(Myself,20) !GlobalTimerNotExpired("UndeadTurn","LOCALS") Range(LastSeenBy(Myself),20) !HPGT(LastSeenBy(Myself),75) THEN RESPONSE #100 SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND) DisplayStringHead(Myself,73243) // Undead thing begone! CreateVisualEffectObject("SPDISPMA",ThirdNearestEnemyOfType([GOODCUTOFF.UNDEAD])) ActionOverride(ThirdNearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself)) END IF !Allegiance(FourthNearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC) See(FourthNearestEnemyOfType([GOODCUTOFF.UNDEAD])) !HPLT(Myself,20) !GlobalTimerNotExpired("UndeadTurn","LOCALS") Range(LastSeenBy(Myself),20) !HPGT(LastSeenBy(Myself),75) THEN RESPONSE #100 SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND) DisplayStringHead(Myself,73243) // Undead thing begone! CreateVisualEffectObject("SPDISPMA",FourthNearestEnemyOfType([GOODCUTOFF.UNDEAD])) ActionOverride(FourthNearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself)) END IF !Allegiance(FifthNearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC) See(FifthNearestEnemyOfType([GOODCUTOFF.UNDEAD])) !HPLT(Myself,20) !GlobalTimerNotExpired("UndeadTurn","LOCALS") Range(LastSeenBy(Myself),20) !HPGT(LastSeenBy(Myself),75) THEN RESPONSE #100 SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND) DisplayStringHead(Myself,73243) // Undead thing begone! CreateVisualEffectObject("SPDISPMA",FifthNearestEnemyOfType([GOODCUTOFF.UNDEAD])) ActionOverride(FifthNearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself)) END IF !Allegiance(SixthNearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC) See(SixthNearestEnemyOfType([GOODCUTOFF.UNDEAD])) !HPLT(Myself,20) !GlobalTimerNotExpired("UndeadTurn","LOCALS") Range(LastSeenBy(Myself),20) !HPGT(LastSeenBy(Myself),75) THEN RESPONSE #100 SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND) DisplayStringHead(Myself,73243) // Undead thing begone! CreateVisualEffectObject("SPDISPMA",SixthNearestEnemyOfType([GOODCUTOFF.UNDEAD])) ActionOverride(SixthNearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself)) END IF !Allegiance(SeventhNearestEnemyOfType([GOODCUTOFF.UNDEAD]),PC) See(SeventhNearestEnemyOfType([GOODCUTOFF.UNDEAD])) !HPLT(Myself,20) !GlobalTimerNotExpired("UndeadTurn","LOCALS") Range(LastSeenBy(Myself),20) !HPGT(LastSeenBy(Myself),75) THEN RESPONSE #100 SetGlobalTimer("UndeadTurn","LOCALS",ONE_ROUND) DisplayStringHead(Myself,73243) // Undead thing begone! CreateVisualEffectObject("SPDISPMA",SeventhNearestEnemyOfType([GOODCUTOFF.UNDEAD])) ActionOverride(SeventhNearestEnemyOfType([GOODCUTOFF.UNDEAD]),Kill(Myself)) END~ COPY_EXISTING ~Meliss01.bcs~ ~override~ ~Meliss02.bcs~ ~override~ ~Meliss03.bcs~ ~override~ DECOMPILE_AND_PATCH BEGIN // use a search string that can only be found in the script block to replace SET ofs = INDEX_BUFFER(~GlobalTimerNotExpired("UndeadTurn","LOCALS")~) PATCH_IF (ofs >= 0) BEGIN // find start of target script block SET ofsStart = RINDEX_BUFFER( ~^IF$~ ofs ) PATCH_IF (ofsStart >= 0) BEGIN // find end of target script block SET ofsEnd = INDEX_BUFFER( ~^END$~ ofsStart ) + 3 PATCH_IF (ofsEnd > ofsStart) BEGIN // remove old block DELETE_BYTES ofsStart (ofsEnd - ofsStart) // insert sequence of new blocks (that are defined above) SET strlen = STRING_LENGTH ~%newScript%~ INSERT_BYTES ofsStart strlen WRITE_ASCIIE ofsStart ~%newScript%~ (strlen) END END END END BUT_ONLY
[/spoiler]
Only script actions work:
Journal entries can be a bit complicated. But if you add something like the below to the tp2, you can group several journal entry chains together (you need a translation file for this though):
// JOURNAL ADD_JOURNAL TITLE (@10000) @10001 @10002 @10003 @10004 @10005 @10006 @10007 @10008 USING ~%MOD_FOLDER%/translations/english/setup.tra~ ADD_JOURNAL TITLE (@20000) @20001 @20002 USING ~%MOD_FOLDER%/translations/english/setup.tra~
Check out my Drake and Aura NPC mods which make use of the journal. Also, there is an EraseJournalEntry() command.
Sounds like I'll have to go the deleting entry route after all, though your code might help me add/delete a bunch at once. So thanks.
I want to have a conversation between Jan and a modded thief that causes a +5 increase to the mod thief's pickpocket skill. Can I just edit their character file somehow?
Thanks in advance.
Make a nameless spell that provides +5% Pick Pockets with the duration type set to 9 (permanent after death). Use ApplySpellRES(SpellRES,Target) in the dialog to cast the spell on the target.
Thankfully that's a ways down the coding process.
I did manage to code my first 2 interjections ever yesterday so there's progress being made.
I know that WeiDu can apply the relevant effect to any item with the minimum HP opcode, but I don't know how to rig an opcode to apply a nonmagical Harm effect on self when an ability score hits a certain value.
As a stupid example: Make it capable of turn Undead and Myconids.