Zed Nocear's BG1 Trigger-Simulations
This tutorial presents the simulations of BGII triggers for original BG (with or without the Add-on Tales of the Sword Coast) as defined by Zed Nocear. They are already successfully used in two BG1 mods: Zed Nocear's TWM ("Mysteries of the Sword Coast", currently only in Polish), and jastey's Ajantis BG1 Expansion Mod. The tutorial is open for discussion. If you have suggestions, corrections or other ideas please share them!
Content
1. Detection of AreaType and simulating AreaCheck()
2. Detection of "Party rested"
3. Simulating CombatCounter()
1. Detection of AreaType and simulating AreaCheck()
To simulate the AreaType() check known from BGII, all area scripts are patched to set the appropriate variables. There are two sets of scripts for patching: One for the master areas, and one for all areas that are not listed as master areas. Note: The code transfers all content of the actual area.bcs to a script that has the area name. Hence, if the area script did not have the same number/name as the area before, the name will be changed by this script.
The patching code in the tp2 is the following:
<<<<<<<< .../BG1TriggerEmulation-inlined/Z!EmulAreaCheck.BAF
IF
Delay(2)
ActionListEmpty()
THEN
RESPONSE #100
SetGlobal("Z!EmulAreaCheck","GLOBAL",%Area_Number%)
SetGlobal("Z!EmulAreaType","GLOBAL",%Area_Flags%)
SetGlobal("Z!EmulAreaOutdoor","GLOBAL",%Variable_Outdoor%)
SetGlobal("Z!EmulAreaCity","GLOBAL",%Variable_City%)
SetGlobal("Z!EmulAreaForest","GLOBAL",%Variable_Forest%)
SetGlobal("Z!EmulAreaDungeon","GLOBAL",%Variable_Dungeon%)
SetGlobalTimer("Z!EmulAreaNotMaster","GLOBAL",4)
END
>>>>>>>>
<<<<<<<< .../BG1TriggerEmulation-inlined/Z!EmulAreaCheck1.BAF
IF
!Global("Z!EmulAreaCheck","GLOBAL",%Area_Number%)
!GlobalTimerNotExpired("Z!EmulAreaNotMaster","GLOBAL")
ActionListEmpty()
THEN
RESPONSE #100
SetGlobal("Z!EmulAreaCheck","GLOBAL",%Area_Number%)
SetGlobal("Z!LastMasterArea","GLOBAL",%Area_Number%)
SetGlobal("Z!EmulAreaType","GLOBAL",%Area_Flags%)
SetGlobal("Z!EmulAreaOutdoor","GLOBAL",%Variable_Outdoor%)
SetGlobal("Z!EmulAreaCity","GLOBAL",%Variable_City%)
SetGlobal("Z!EmulAreaForest","GLOBAL",%Variable_Forest%)
SetGlobal("Z!EmulAreaDungeon","GLOBAL",%Variable_Dungeon%)
END
>>>>>>>>
COPY_EXISTING ~AR2612.ARE~ ~override~ // two areas in BG1 have wrong flag "Outdoor"
~AR3317.ARE~ ~override~
WRITE_BYTE 0x48 0
BUT_ONLY_IF_IT_CHANGES
COPY_EXISTING_REGEXP GLOB ~.*\.ARE~ ~override~ // for all areas in game proper script name and file
SPRINT area_name ~%SOURCE_RES%~
READ_ASCII 0x94 ~old_script_name~
WRITE_EVALUATED_ASCII 0x94 ~%SOURCE_RES%~
INNER_ACTION BEGIN
ACTION_IF !(~%old_script_name%~ STRING_EQUAL_CASE ~%area_name%~)
AND (FILE_EXISTS_IN_GAME ~%old_script_name%.bcs~)
THEN BEGIN COPY_EXISTING ~%old_script_name%.bcs~ ~override/%area_name%.bcs~ END
END
BUT_ONLY_IF_IT_CHANGES
COPY_EXISTING_REGEXP GLOB ~.*\.ARE~ ~override~ // adds AreaCheck emulation block to the area script
READ_ASCII 0x94 ~Script_Name~
PATCH_IF (~%SOURCE_RES%~ STRING_MATCHES_REGEXP ~AR[0-9][0-9][0-9][0-9]~) = 0
THEN BEGIN READ_ASCII 0x96 ~Area_Number~ (4) END
ELSE BEGIN SPRINT ~Area_Number~ ~0~ END
READ_BYTE 0x48 ~Area_Flags~
INNER_ACTION BEGIN
ACTION_IF (NOT FILE_CONTAINS_EVALUATED(~%Script_Name%.BCS~ ~Z!EmulAreaCheck~)) // area script not already patched
THEN BEGIN
OUTER_SET Variable_Outdoor = ((~%Area_Flags%~ BAND 0b1) = 0b1)
OUTER_SET Variable_City = ((~%Area_Flags%~ BAND 0b1000) = 0b1000)
OUTER_SET Variable_Forest = ((~%Area_Flags%~ BAND 0b10000) = 0b10000)
OUTER_SET Variable_Dungeon = ((~%Area_Flags%~ BAND 0b100000) = 0b100000)
ACTION_IF (FILE_CONTAINS_EVALUATED(~MASTAREA.2DA~ ~%Script_Name%~))
THEN BEGIN EXTEND_BOTTOM ~%Script_Name%.BCS~ ~.../BG1TriggerEmulation-inlined/Z!EmulAreaCheck1.BAF~ EVALUATE_BUFFER END //area is "master area" as in mastarea.2DA
ELSE BEGIN EXTEND_BOTTOM ~%Script_Name%.BCS~ ~.../BG1TriggerEmulation-inlined/Z!EmulAreaCheck.BAF~ EVALUATE_BUFFER END //area is not "master area"
END
END
BUT_ONLY_IF_IT_CHANGES
The code can be downloaded as tpa here: BG1areacheck_emulation.tpa (3KB)
Now, including the tpa into your mod, installation only needs the following line in the tp2:
INCLUDE ~%PatchInMyMod%/BG1AreaCheck_emulation.tpa~
"Z!EmulAreaType" gives the sum of all flags (value of the offset 0x48) of the according ARE-Datei.
The triggers AreaType() and AreaCheck() can be simulated:
AreaType():
OUTDOOR: "Global("Z!EmulAreaOutdoor","GLOBAL",1)"
CITY: "Global("Z!EmulAreaCity","GLOBAL",1)"
FOREST: "Global("Z!EmulAreaForest","GLOBAL",1)"
DUNGEON: "Global("Z!EmulAreaDungeon","GLOBAL",1)"
AreaCheck()
For simulating AreaCheck(), the variable "Z!EmulAreaCheck" can be used: For the original BG areas, the value of the variable is equal to the "ARxxxx" number. For (mod) areas that are not named like the standard "ARxxxx", the variable is equal to "0".
For example, now the Area AR2600 can be detected via the trigger "Global("Z!EmulAreaCheck","GLOBAL",2600)".
2. Detection of "Party rested"
To simulate the "PartyRested()" trigger known from BGII, the FATIGUE state of the Player1 will be used. Directly after rest, this stat has the value "0". During the game, it increases until the character reached a state of fatigue. For detection of the rest, the FATIGUE value will be checked. If it is "0", the custom spell "Z!FATIG1" sets the FATIGUE value to "1", along with the setting of the needed timers for the after-rest dialogues.
(Note: Theoretically, this means that the character will fatigue faster in the game (4 hours earlier). So far, this could not be detected in the game while playing with this party rested detection installed, though.)
For this, dplayer3.bcs is patched with the following script block. The example is for the Ajantis romance in the BG1 Ajantis expansion modification. Own variables and timers have to be inserted in your mod, accordingly:
//dplayer3_add.baf
IF
CheckStat(Player1,0,FATIGUE)
THEN
RESPONSE #100
ApplySpellRES("Z!FATIG1",Player1)
IncrementGlobal("X#AjantisRomanceRestCounter","GLOBAL",1)
SetGlobalTimer("X#AjantisRomanceRestAfterTimer","GLOBAL",30)
END
Thus, the needed triggers for the dialogues will be given after the rest, i.e. if the FATIFUE value was at "0". For a short period after wake-up, the timer "X#AjantisRomanceRestAfterTimer" runs and can be used to trigger morning dialogues.
For the Ajantis morning dialogue, the triggering script block looks like this:
/* dialogue activation */
IF
InParty(Myself)
!StateCheck(Myself,CD_STATE_NOTVALID) //custom state from CamDawg
!StateCheck(Player1,CD_STATE_NOTVALID)
Detect(Player1)
!See([ENEMY])
!GlobalTimerNotExpired("Z!EnemySeenPeriod","GLOBAL") //simulation of CombatCounter(0), see below
!Global("X#AjantisRomanceActive","GLOBAL",3)
Global("X#AjantisRomanceMatch","GLOBAL",1)
GlobalGT("X#AjantisRomanceRestCounter","GLOBAL",1) //greater as 1: the first time the variable is incremented is directly after the installation!
!GlobalTimerExpired("X#AjantisRomanceRestAfterTimer","GLOBAL") //timer runs: dialogue triggers shortly after wake-up
Global("X#AjantisRomanceStars","GLOBAL",1)
THEN
RESPONSE #100
SetGlobal("X#AjantisRomanceStars","GLOBAL",3)
END
/* dialogue triggering */
IF
InParty(Myself)
!StateCheck(Myself,CD_STATE_NOTVALID)
!StateCheck(Player1,CD_STATE_NOTVALID)
Global("X#AjantisRomanceStars","GLOBAL",3)
THEN
RESPONSE #100
SetGlobalTimer("X#AjantisRomance","GLOBAL",TWO_DAYS)
Dialogue(Player1)
END
Now the crucial part: Making the PartyRested() simulation compatible with other mods. For this, a distinction of cases has to be put into the tp2-files. With this disctinction, the above posted patching of the dplayer3.bcs will only happen, if no other mod already patched the application of the spell. If another mod already did so, patching of the appropriate script block is not only sufficient for your mod, but the only way to keep the mods compatible.
So, the tp2 section looks like this:
APPEND ~action.ids~ ~160 ApplySpellRES(S:ResRef*,O:Target*)~ UNLESS ~ApplySpellRES~
ACTION_IF (FILE_CONTAINS_EVALUATED (~dplayer3.BCS~ ~"Z!FATIG1"~))
THEN BEGIN
COPY_EXISTING ~dplayer3.BCS~ ~override~
DECOMPILE_BCS_TO_BAF
REPLACE_TEXTUALLY ~ApplySpellRES("Z!FATIG1",Player1)~
~ApplySpellRES("Z!FATIG1",Player1)
IncrementGlobal("X#AjantisRomanceRestCounter","GLOBAL",1)
SetGlobalTimer("X#AjantisRomanceRestAfterTimer","GLOBAL",30)~
COMPILE_BAF_TO_BCS
END
ELSE BEGIN
EXTEND_TOP ~dplayer3.BCS~ ~C#AjantisRomance_BG1/TriggerSimulations/dplayer3_add.BAF~
COPY ~TWM_Pack/00RESTcheck/Z!FATIG1.SPL~ ~override~
END
The spell can be downloaded here: Download Z!FATIG1.SPL.RAR (120 kB)
The .zip contains the spell, which has to be copied to the override folder.
3. Simulating CombatCounter()
To simulate the trigger "CombatCounter(0)" known from BGII, the override scripts of the NPCs, the dplayer3.bcs (for the PC), and dplayer2.bcs (for mod-NPCs that do not use the enemy detection in their scripts) are patched with a script block that runs a timer as soon as one of the NPCs see an enemy. Note: dplayer2.bcs only gets executed, if the AI is turned on in the game. The override script of an NPC is the only one that gets executed with tuned off AI.
The script for patching is the following (in the following referred to as "CombatTimer_add.BAF"):
IF
Global("Z!EnemyAlreadySeen","LOCALS",0)
InParty(Myself)
See([ENEMY])
ActionListEmpty()
THEN
RESPONSE #100
SetGlobal("Z!EnemyAlreadySeen","LOCALS",1)
END
IF
Global("Z!EnemyAlreadySeen","LOCALS",0)
InParty(Myself)
Died([ENEMY])
THEN
RESPONSE #100
SetGlobal("Z!EnemyAlreadySeen","LOCALS",1)
END
IF
InParty(Myself)
!See([ENEMY])
Global("Z!EnemyAlreadySeen","LOCALS",1)
THEN
RESPONSE #100
SetGlobal("Z!EnemyAlreadySeen","LOCALS",0)
SetGlobalTimer("Z!EnemySeenPeriod30","GLOBAL",30) // halbe Minute, halbe Tour im Spiel
SetGlobalTimer("Z!EnemySeenPeriod60","GLOBAL",60) //eineMinute, volle Tour im Spiel
SetGlobalTimer("Z!EnemySeenPeriod150","GLOBAL",150) //reale 2,5 Minuten, halbe Stunde im Spiel
SetGlobalTimer("Z!EnemySeenPeriod900","GLOBAL",900) // drei Stunden im Spiel
SetGlobalTimer("Z!EnemySeenPeriod7200","GLOBAL",7200) // Tag und Nacht im Spiel
END
Now the BGII trigger "CombatCounter(0)" can be simulated using the trigger
"!See([ENEMY])
!GlobalTimerNotExpired("Z!EnemySeenPeriodX","GLOBAL")"
for the relevant NPC, with "X" being the wanted time interval: 60, 150, 900, 7200.
For patching, it is useful to check whether the script blocks were already added. For Ajantis override script, this would look like this:
ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~ajantis.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~ajantis.BCS~ ~AjantisBG1/BG1_TriggerSimulations/combatcounter.baf~ ENDin the .tp2.
Note: Not every BioWare BG1 NPCs have override scripts.
NPCs who do not have override scripts are: Alora, Branwen, Faldorn, Garrick, Imoen, Skie, and Xan.
Imoen.bcs is used for the imoen.cre (the "Imoen" the PC meets in Candlekeep). If patching the joinable "Imoen" creature files, this one should be left out.
Alora*.cres use "alora.bcs" as class script, which additionally has faulty script blocks that would block any mod script blocks that get added via EXTEND_BOTTOM. Class scripts can get deactivated by turning off the AI in the game. To have a standard concerning "override scripts of the NPC have the name of the NPC", it is suggested 1. to patch the alora.bcs as override script and 2. to correct the faulty script blocks (deleting them).
The code for the tp2-patching to achieve all this would look like this:
//Give BioWare NPCs override scripts COPY_EXISTING_REGEXP GLOB ~^alora[0-9]*\.cre~ ~override~ WRITE_ASCII SCRIPT_OVERRIDE ~alora~ READ_ASCII SCRIPT_CLASS ~Class_Script_Name~ PATCH_IF ( ~%Class_Script_Name%~ STRING_EQUAL_CASE ~alora~ ) //only class script ~Alora~ will be deleted THEN BEGIN WRITE_LONG 0x250 0x00000000 WRITE_LONG 0x254 0x00000000 END BUT_ONLY_IF_IT_CHANGES COPY_EXISTING ~alora.BCS~ ~override~ REPLACE_BCS_BLOCK ~Modfolder/alora_old.baf~ ~Modfolder/alora_new.baf~ //delete bad blocks BUT_ONLY_IF_IT_CHANGES COPY_EXISTING_REGEXP GLOB ~^branwe[0-9]*\.cre~ ~override~ WRITE_ASCII SCRIPT_OVERRIDE ~branwen~ BUT_ONLY_IF_IT_CHANGES COPY_EXISTING_REGEXP GLOB ~^faldor[0-9]*\.cre~ ~override~ WRITE_ASCII SCRIPT_OVERRIDE ~faldorn~ BUT_ONLY_IF_IT_CHANGES COPY_EXISTING_REGEXP GLOB ~^garric[0-9]*\.cre~ ~override~ WRITE_ASCII SCRIPT_OVERRIDE ~garrick~ BUT_ONLY_IF_IT_CHANGES COPY_EXISTING_REGEXP GLOB ~^imoen[0-9]+\.cre~ ~override~ // imoen1,2,4,6.CRE but not imoen.CRE WRITE_ASCII SCRIPT_OVERRIDE ~imoen~ BUT_ONLY_IF_IT_CHANGES COPY_EXISTING_REGEXP GLOB ~^skie[0-9]*\.cre~ ~override~ WRITE_ASCII SCRIPT_OVERRIDE ~skie~ BUT_ONLY_IF_IT_CHANGES COPY_EXISTING_REGEXP GLOB ~^xan[0-9]*\.cre~ ~override~ WRITE_LONG 0x248 0x00000000 WRITE_LONG 0x250 0x00000000 WRITE_ASCII SCRIPT_OVERRIDE ~xan~ BUT_ONLY_IF_IT_CHANGES //patching der Skripte für die CombatCounter() simulation ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~dplayer3.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~dplayer3.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~dplayer2.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~dplayer2.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~ajantis.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~ajantis.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~alora.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~alora.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~branwen.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~branwen.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~coran.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~coran.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~dynaheir.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~dynaheir.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~edwin.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~edwin.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~eldoth.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~eldoth.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~faldorn.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~faldorn.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~garrick.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~garrick.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~imoen.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~imoen.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~jaheira.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~jaheira.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~khalid.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~khalid.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~kagain.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~kagain.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~kivan.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~kivan.BCS~ ~Modfolder/Z!CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~minsc.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~minsc.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~montaron.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~montaron.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~quayle.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~quayle.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~safana.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~safana.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~sharteel.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~sharteel.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~skie.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~skie.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~tiax.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~tiax.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~viconia.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~viconia.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~xan.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~xan.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~xzar.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~xzar.BCS~ ~Modfolder/CombatTimer_add.BAF~ END ACTION_IF (NOT FILE_CONTAINS_EVALUATED (~yeslick.BCS~ ~Z!EnemyAlreadySeen~)) THEN BEGIN EXTEND_BOTTOM ~yeslick.BCS~ ~Modfolder/CombatTimer_add.BAF~ END
With alora_old.baf:
IF TimeOfDay(DAY) THEN RESPONSE #100 Activate(Myself) END IF TimeOfDay(DAY) THEN RESPONSE #100 Deactivate(Myself) END
And the alora_new.baf being an empty file.
Edited by jastey, 14 January 2012 - 03:10 PM.













