Modding Tutorial Part 1: Automatic Transition of NPCs into SoD and between Camps in SoD

Version 9, by jastey

Contents

  1. General throughts to transition into SoD's "Korlasz' Crypt"
  2. Add Biff to Corwin's list about companions in BG city
  3. Make Biff leave party after Korlasz' Crypt is cleared
  4. Moving Biff to his new meeting point in BG city
  5. Make Biff wait outside the Ducal Palace when PC marches against crusade
  6. Move Biff to first camp in Coast Way Crossing (bd1000.are)
  7. Move Biff to 2nd camp in Troll Claw Woods (bd7100.are)
  8. Move Biff to the Coalition Camp in bd3000.are
  9. Move NPC to entrance of the Allied Siege Camp (Crusade battle)
  10. Make Biff return to camp if told so after being kicked out in wilderness area
  11. Make Biff follow if kicked out in Avernus
  12. Move Biff to Dragonspear Castle interior after PC returns from Avernus
  13. Biff's SoD greetings dialogue with the needed local variables
  14. Biff's SoD kickout dialogue with the needed local variables
  15. Credits, Used Tools, and Helpful Links
  16. Version History

This tutorial deals with the transition of an NPC from BG:EE to SoD, how to spwn them for the first meeting after the main SoD campaign started, and how to move your NPC from one SoD area to another depending on the campaign's progress if they are not in the group. As an example NPC, Biff the Understudy will be the NPC in this guide!

Some nomenclature:

xx used as a modding prefix in this tutorial. If you don't have a prefix, check the IE Community Filename Prefix Reservations List and register one at Black Wyrm Lair Forums.
xxBiff.cre Biff's cre-file name
xxBiff Biff's scriptname (death variable)
xxBiffsG.dlg Biff's greeting dialogue in SoD
xxBiffJ.dlg Biff's joined dialogue in SoD (note: and also BG1 if he would be available)
xxBiffs.bcs Biff's SoD override script
xxBiffsP.dlg Biff's kickout (post) dialogue
[x0.y0] area coordinates for Biff's spot in front the of the Ducal Palace
[x1.y1] area coordinates for Biff's camp spot in bd1000.are, used in bd1000_patch.baf and bdparty_patch.baf
[x2.y2] area coordinates for Biff's camp spot in bd7100.are, used in bd7100_patch.baf and bdparty_patch.baf
[x3.y3] area coordinates for Biff's camp spot in bd3000.are, used in bd3000_patch.baf and bdparty_patch.baf

1. General throughts to transition into SoD's "Korlasz' Crypt"

This section is only of interest if your NPC was also available in BG:EE. If your NPC is a pure SoD NPC that will wait after the transition into the palace, you can skip this section.

Please be aware that my tutorials assume that your mod NPC uses the same joined dialogue in SoD like they use in the BG1 part of the game. If you are using a different joined dialogue for the SoD part, not only compatibility with EndlessBG1's and Transitions' "Korlasz' Crypt is in BG1" content will require special handling, but also examples in my tutorial mod about patching bddialog.2da etc. will need to be adjusted.

After the end of BG:EE, the transition to SoD is done first by the cutscene "bdsodtrn.bcs" (triggered by Sarevok's death in AR0125.bcs) which sets the Global("SOD_fromimport","GLOBAL",1) with which the continuous game will be detected later, resurrects and completely heals all NPCs, and starts the SoD campaign ("MoveToCampaign("SoD")"). After that, the bd0120.bcs does party rearrangements depending on who is present (change Viconia's portrait, remove Imoen from the party, substituting her with Safana in case the party lacks a thief with either open locks or detect traps ability etc.) and calls the cutscene bdintro.bcs, which makes newly spawned replacement NPCs join the party and sets the NPCs override scripts and dialogues to the SoD ones.

For compatibility with my EndlessBG1 mod I do not recommend to switch to the SoD script upon transitioning to Korlasz' Dungeon any more. Instead, my NPC mods treat Korlasz' Dungeon as BG1 content, i.e. they use BG1 script and dialogues inside Korlasz' Crypt. In addition, my mod NPCs use the same joined dialogues for BG1 and SoD.

Reason is that the IE engine resets the NPCs' joined dialogues to the entry in the active (p/bd)dialog.2da, and banter files are the active ones in the interdia/bdbanter.2da. With Korlasz' Crypt moved into the BG1 "world" by the mods EndlessBG1 (component 14) or Transitions, this means that all NPCs' banter files will still be the BG1 ones inside the crypt - and all joined dialogue files get reset to the BG1 ones every time the player loads a save. By using the same joined dialogue for your mod NPC in BG1 and SoD, you do not have to worry about the joined dialogue of your NPC being switched. This also means that if you are using a different SoD joined dialogue, you need to make sure it is always active while the group is inside Korlasz' Dungeon, regardless whether it is still BG1 or original SoD. Also, one would have to distinguish between Korlasz' Crypt in SoD and Korlasz' Crypt in BG1 with regard to the originals NPCs' banter dialogues in case your NPC tries to start a banter while being in the crypt. See also here for more info: [How-To] Original NPC Scripts and Dialogue in Korlasz' Crypt and compatibility with EndlessBG1 and Transitions (Community Effort)

This means that I removed the former recommendation to patch the bdintro.bcs with a switch of your mod NPC's OVERRIDE script (and dialogue) to the SoD one but am recommending to treat Korlasz' Dungeon like BG1 content with regard to your NPC's dialogue and script instead. Also, I recommend using one joined dialogue name for SoD and BG1.

Note: Switching to your NPC's SoD script (and SoD greetings dialogue) will be done later when they are moved to the first meeting area in SoD as described in later sections of this tutorial. If your NPC is supposed to just stay with the PC like for example my Grey the Dog, switching to SoD OVERRIDE script needs to be done accordingly, either inside the sodtrn.bcs, bd0103.bcs, or even the NPC's own BG1 script upon arriving in bd0103.are. (Just for info: Grey the Dog mod uses the same script file for BG1 and SoD).

Still, there needs to be some specification whether Korlasz' Crypt needs to be treated like SoD or like BG1 content with respect to interaction with original NPCs as described above.

An easy way to detect SoD in a BG:EE+SoD game is by using the unique variable "bd_plot". It is set to "2" at the beginning of the SoD intro cutscene (where the two followers of Korlasz are talking beside the chasm: "Sarevok's dead. Everything's a mess. We should have gotten out of the city days ago.") and will be increased to "40" when Korlasz' Dungeon is done. After finishing Korlasz' Crypt and the PC being woken up by Imoen in the Ducal Palace, the check variable for SoD would be "GlobalGT("BD_PLOT","GLOBAL",50)". Example code how to use this to our advantage is given below.

To ensure compatibility with both SoD's and "BG1's" Korlasz' Crypt you need to consider the following for any content you add to it:

1. NPC Banter indide Korlasz' Crypt

To make sure your NPC does not start banter that will break, Korlasz' Dungeon needs to be included or excluded from BG1 content dependent on whether EndlesssBG1 or Transitions mod is installed or not, because:

  1. Original NPCs will use their SoD banter file (if assigned) if Korlasz' Crypt is in SoD.
  2. Original NPCs will use their BG1 banter file inside Korlasz' Crypt if it is in BG1. (This is due to engine structure.)

For this, I defined the variables "IT_IS_SOD" and "BG1_BEFORE_TRANSITION" with the following definitions. Put this into the ALWAYS block of your mod:

/* (T1) Definition of variables to distinguish between SoD and BG1 (for banter or script blocks) */
ACTION_IF (GAME_IS ~bgee~) THEN BEGIN
  ACTION_IF (MOD_IS_INSTALLED ~c#endlessbg1.tp2~ ~14~) BEGIN
    OUTER_SPRINT ~BG1_BEFORE_TRANSITION~ ~GlobalLT("BD_PLOT","GLOBAL",41)~
    OUTER_SPRINT ~IT_IS_SOD~ ~GlobalGT("bd_plot","global",40)~
  END ELSE ACTION_IF (MOD_IS_INSTALLED ~transitions.tp2~ ~0~) BEGIN
    OUTER_SPRINT ~BG1_BEFORE_TRANSITION~ ~GlobalLT("BD_PLOT","GLOBAL",41)~
    OUTER_SPRINT ~IT_IS_SOD~ ~GlobalGT("bd_plot","global",40)~
  END ELSE BEGIN
    OUTER_SPRINT ~BG1_BEFORE_TRANSITION~ ~Global("BD_PLOT","GLOBAL",0)~
    OUTER_SPRINT ~IT_IS_SOD~ ~GlobalGT("bd_plot","global",0)~
  END
END
ACTION_IF GAME_IS ~eet~ THEN BEGIN
  ACTION_IF (MOD_IS_INSTALLED ~c#endlessbg1.tp2~ ~14~) BEGIN
    OUTER_SPRINT ~BG1_BEFORE_TRANSITION~ ~OR(2) Global("ENDOFBG1","GLOBAL",0) GlobalLT("BD_PLOT","GLOBAL",41)~
    OUTER_SPRINT ~IT_IS_SOD~ ~Global("ENDOFBG1","GLOBAL",1) GlobalGT("bd_plot","global",40)~
  END ELSE ACTION_IF (MOD_IS_INSTALLED ~transitions.tp2~ ~0~) BEGIN
    OUTER_SPRINT ~BG1_BEFORE_TRANSITION~ ~OR(2) Global("ENDOFBG1","GLOBAL",0) GlobalLT("BD_PLOT","GLOBAL",41)~
    OUTER_SPRINT ~IT_IS_SOD~ ~Global("ENDOFBG1","GLOBAL",1) GlobalGT("bd_plot","global",40)~
  END ELSE BEGIN
    OUTER_SPRINT ~BG1_BEFORE_TRANSITION~ ~Global("ENDOFBG1","GLOBAL",0)~
    OUTER_SPRINT ~IT_IS_SOD~ ~Global("ENDOFBG1","GLOBAL",1)~
  END
END
	  

Using the variables is easy: tagg the BG1 banter with %BG1_BEFORE_TRANSITION% and any SoD ones with %IT_IS_SOD% and they will trigger correctly inside Korlasz' Dungeon, as well. You can also use one OVERRIDE script for BG1 and SoD and tagg the script blocks accordingly, ensuring that no script action meant for BG1 will trigger in SoD and vice versa. This tutorial assumes that your NPC uses a separate SoD script, so there will be no taggs in the examples.

Note that original SoD handles banters differently than just adding banters into the banter.dlgs. See Modding Tutorial Part 4: "SoD Banter System for Your NPC" for more details.

2. Adding interjections to original NPCs

Original NPCs will use their SoD joined dialogue inside Korlasz' Crypt, no matter whether it is in SoD or BG1 "world". This is ensured by EndlessBG1 to keep compatibility with the original content of NPC interjections into dialogues inside the crypt.

3. Adding other content for original NPCs

Original NPCs will also have their SoD OVERRIDE scripts assigned in Korlasz' Dungeon. If they do not have one for SoD, the OVERRIDE slot will be empty.

2. Add Biff to Corwin's list about companions in BG city

When leaving for Baldur's Gate city, Corwin mentions that the Flaming Fist tracked down some of CHARNAME's former companions. The PC can ask about them and Corwin lists them in case they are in BG city starting in state 39 of bdschael.dlg. This is also the dlg state we will patch our NPC to, assuming they and the PC already met, either in BG:EE or they were in Korlasz' Crypt together.

This is before the PC gets out into the city the first time. If the PC doesn't talk to Corwin here, the addition will be lost: Corwin also mentions the NPCs if talked to outside the Palace. The problem here is how to fit in another mod NPC while keeping compatibility - inserting one here would be no problem, but as soon as two mods want to do it, one of them will be skipped in the further conversation, because the whole thing is a mixture of parallel and looping dialogue states where all NPCs would need to be considered by reply options - for EET even more since it also considers whether some NPCs are already dead. That is why I only show the patching of the one dialogue state here.

We add an I_C_T where Corwin tells about Biff with the global check variable "xxBiff_bdschael_39" which will give true in case the PC knows our NPC. For Biff, I will use "BeenInParty" (which is an example only, because Biff wasn't available yet, but your NPC might). We also check whether the NPC is dead or in party.

/* This goes into a .d file: Make Corwin mention Biff's whereabouts in BG city */
I_C_T bdschael 39 xxBiff_bdschael_39
== bdschael IF ~!Dead("xxBiff") !InPartyAllowDead("xxBiff") BeenInParty("xxBiff")~ THEN ~Also, Biff the Understudy was seen right in front of the Ducal Palace's gates not long ago.~
END

3. Make Biff leave party after Korlasz' Crypt is cleared

SoD starts with the party from BG1 being in Korlasz' Crypt, the "last follower" of Sarevok. After Korlsz' Crypt is cleared, all joinable NPCs leave the group and the PC is transferred to the Ducal Palace (bd0103.are).

This you only need if your NPC was already available in BG:EE, or if you let him join in Korlasz' Crypt. If your NPC joins in BG city or later, you can skip this and continue with Chapter 4: Moving Biff to his new meeting point in BG city.

This transition is done by bd0103.bcs, i.e. the area script of the Ducal Palace, 3rd floor where the PC finds him/herself. (Origially, the transition is started in bdimoen.dlg which calls bdcut00z.bcs which just transfers the whole party to bd0103.are. Then all the sorting and moving and leaving is done by bd0103.bcs.)

The script bd0103.bcs does the following for BeamDog and BioWare NPCs:

  1. The inventory (BACKPACK) gets transferred to the player's chest in the Ducal Palace ("PlayerChest00").
  2. dead NPC get revived,
  3. NPC leave the group and vanish (2 and 3 in one script block for each NPC).

If we would do nothing here, Biff would be treated like a multiplayer character and will just remain in the group and be inside the Ducal Palace with the PC.

But Biff should not just remain in the group after the transition but behave like a "real" NPC, so we need to add script blocks for him to bd0103.bcs.

The patching to bd0103.bcs to remove Biff has to be added via EXTEND_TOP, since bd0103.bcs also triggers the cutscene with Imoen approaching the PC – patching at the bottom would be too late in the game. This means that the removal of the NPCs' BACKPACK in bd0103.bcs has to be added for Biff individually, as well.

For patching to scripts with EXTEND_TOP, make sure you only add script blocks that contain a Continue(), otherwise OnCreation() triggers in the script might get blocked. This is especially important for area scripts. Even if there is none in the original game, it could have been added by another mod.

The bd0103_patch.baf looks like this:

/* bd0103_patch.baf:
Biff will leave the group after clearing Korlasz' Crypt */
IF
  OR(2)
  InMyArea("xxBiff")
  InPartyAllowDead("xxBiff")
  GlobalLT("BD_PLOT","GLOBAL",51)
THEN
  RESPONSE #100
    ApplySpellRES("bdresurr","xxBiff") //resurrection
    SmallWait(1)
    ActionOverride("PlayerChest00",TakeCreatureItems("xxBiff",BACKPACK))
    SmallWait(1)
    ActionOverride("xxBiff",LeaveParty())
    SmallWait(1)
    ActionOverride("xxBiff",DestroySelf())
    Continue()
END

In the tp2, this line specifies the patching to bd0103.bcs (note the EXTEND_TOP):

/* after Korlasz is defeated: PC is moved to Ducal Palace. Joined NPCs leave */
EXTEND_TOP ~bd0103.bcs~ ~%MOD_FOLDER%/baf/bd0103_patch.baf~
  EVALUATE_BUFFER

4. Moving Biff to his new meeting point in BG city

Now, your NPC should be recruitable somewhere inside BG city. As an example, Biff will be waiting in front of the Ducal Palace (bd0010.are) where the PC can meet him after leaving the palace for the first time.

This would also be the first meeting point for players who started a new SoD game. Unless, of course, you want your NPC to start in Korlasz' Crypt for a new game also, then you'll have to add them to bd0130.bcs or bd0120.bcs, instead.

Open Near Infinity and look at the bd0010.are (open the graphical view). Chose a good spot and note down the coordinates that NI shows at the right bottom side. Let's call them [x0.y0] for now.

Spawning Biff will be done with bd0010_patch.baf which needs to be patched to bd0010.bcs. Note that we need two script blocks: one in case it's a new SoD game, and one in case Biff was already in the party (because we want the player to have him back the way he left in BG1 or Korlasz' Crypt):

/* bd0010_patch.baf

Biff will be waiting in front of the Ducal Palace */
/* new SoD game */
IF
/* We consider two possibilities for detection whether Biff already existed. Chose what suits you best here. You could also chose e.g. Global("SOD_fromimport","global",0) for a new SoD game */
  !BeenInParty("xxBiff") //this variable is set automatically if Biff was in Party before. 
  Global("xxBiff_SpawnBG1","GLOBAL",0) //Biff was not present in BG1 - from your BG1 potion of your NPC mod
  Global("xxBiff_Spawn","GLOBAL",0) //spawn variable for SoD
  Global("xxBiff_MoveCamp","MYAREA",0) //so spawning here happens only once
THEN
  RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","MYAREA",1)
    SetGlobal("xxBiff_Spawn","GLOBAL",1)
    CreateCreature("xxBiff",[x0.y0],S)  //open area in NI and chose coordinates and face direction. I put Biff right behind Ryphus.
    ActionOverride("xxBiff",MakeGlobalOverride())
    ChangeSpecifics("xxBiff",ALLIES)
    ActionOverride("xxBiff",ChangeAIScript("xxBiffs",OVERRIDE)) //Biff's SoD override script
    ActionOverride("xxBiff",ChangeAIScript("",CLASS))
    ActionOverride("xxBiff",ChangeAIScript("",RACE))
    ActionOverride("xxBiff",ChangeAIScript("",GENERAL))
    ActionOverride("xxBiff",ChangeAIScript("",DEFAULT))
    ActionOverride("xxBiff",SetDialog("xxBiffsG")  //greeting dialogue in SoD
END

/* 2nd script block if Biff was in party before */
IF
  !Dead("xxBiff")
  !InPartyAllowDead("xxBiff")
  Global("xxBiff_Spawn","GLOBAL",0) //SoD spawn variable
  OR(2) //we consider two possibilities for detection whether Biff already existed. Chose what suits you best here.
	BeenInParty("xxBiff") //this variable is set automatically if Biff was in Party in BG1 or SoD. 
	GlobalGT("xxBiff_SpawnBG1","GLOBAL",0) //Biff was present in BG1 - from your BG1 potion of your NPC mod
  Global("xxBiff_MoveCamp","MYAREA",0)  //so spawning here happens only once
THEN
  RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","MYAREA",1)
    SetGlobal("xxBiff_Spawn","GLOBAL",1) //we set this so we can check for it in bd0101.bcs
    MoveGlobal("bd0010","xxBiff",[x0.y0])
    ActionOverride("xxBiff",Face(S))    //or any other direction depending on the coordinates
    ApplySpellRES("bdrejuve","xxBiff")  //completely heals and removes all spell effects
    ChangeEnemyAlly("xxBiff",NEUTRAL)
    ChangeSpecifics("xxBiff",ALLIES)
    ActionOverride("xxBiff",SetGlobal("bd_joined","locals",0))  //this is needed for kickout and moving to camps -> see below
    ActionOverride("xxBiff",SetGlobal("bd_retreat","locals",0)) //this variable is used to toggle behavior of crusaders in the class-dependent GENERAL scripts also used for NPCs. It needs to be "0" for NPCs.
    ActionOverride("xxBiff",SaveObjectLocation("LOCALS","bd_default_loc",Myself)) // used e.g. in bdshout.bcs which is set to RACE script by bdparty.bcs which uses MoveToSavedLocationn("bd_default_loc","LOCALS")
    ActionOverride("xxBiff",ChangeAIScript("xxBiffs",OVERRIDE)) //Biff's SoD override script
    ActionOverride("xxBiff",ChangeAIScript("DEFAULT",CLASS))
    ActionOverride("xxBiff",ChangeAIScript("",RACE))
    ActionOverride("xxBiff",ChangeAIScript("",GENERAL))
    ActionOverride("xxBiff",ChangeAIScript("",DEFAULT))
    ActionOverride("xxBiff",SetDialog("xxBiffsG")  //greeting dialogue in SoD
END

And to patch bd0010.bcs with our bd0010_patch.baf, we need this line in the tp2:

/* let Biff reappear in front of the Duchal Palace */
EXTEND_BOTTOM ~bd0010.bcs~ ~%MOD_FOLDER%/baf/bd0010_patch.baf~ EVALUATE_BUFFER

OK, so now he is waiting in front of the Palace. But what if the PC doesn't take him along now but tells him to wait? Then Biff needs to move while the campaign progresses.

5. Make Biff wait outside the Ducal Palace when PC marches against crusade

First move: Biff should be still outside the Palace when the PC moves out against the crusaders. If your NPC was elsewhere in the city beforehand, he could wait here, now.

Once the PC decides to move out against the Crusade, the area outside in front of the Ducal Palace is no longer bd0010.are, but bd0101.are. We want to meet up Biff here so he should be here, too. Note: The PC can also leave for the Crusaders without going into the city to recruit NPCs, they will be available later nontheless. So will be Biff!

So, we have two possible cases here from a technical point of view for spawning him in bd0101.are:

  1. Global("xxBiff_Spawn","GLOBAL",0) is still at 0 because the player did not leave the palace to look for companions but went straight to leaving BG city with the Flaming Fist forces, but Biff was not in the game before - needs to be spawned anew.
  2. Global("xxBiff_Spawn","GLOBAL",0) is still at 0 because the player did not leave the palace to look for compagnions but went straight to leaving BG city with the Flaming Fist forces, and Biff was already in party or existed in BG1 - needs to be moved to bd0101.are, not spawned anew.
  3. Global("xxBiff_Spawn","GLOBAL",1) is at 1 meaning no matter whether Biff was present in BG1 or not, he was already spawned in front of the palace in his original SoD meeting point (we chose bd0010.are) - needs to be moved to bd0101.are, not spawned anew.

Points 2 and 3 can be handled in one spawn script block in our example.

The according bd0101_patch.baf which needs to be patched to bd0101.bcs:

/* (T1) bd0101_patch.baf // Ducal Palace, Leaving BG */

/* Close the spawning/moving variable in case Biff is already in party */
IF
  Global("xxBiff_MoveCamp","MYAREA",0)  
  OR(2)
    InPartyAllowDead("xxBiff")  
    Dead("xxBiff")
THEN
  RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","MYAREA",1)  
    Continue()
END

/* 1. Biff needs to be spawned anew: Global("xxBiff_Spawn","GLOBAL",0) is still at 0 because the player did not leave the palace to look for companions but went straight to leaving BG city with the Flaming Fist forces, plus Biff was not in the game before */
IF
  !Dead("xxBiff")
  !InPartyAllowDead("xxBiff")
  !BeenInParty("xxBiff") //Biff was never in party
  Global("xxBiff_SpawnBG1","GLOBAL",0) //Biff wasn't spawned in BG1
  Global("xxBiff_Spawn","GLOBAL",0) //Biff wasn't spawned in SoD before
  Global("xxBiff_MoveCamp","MYAREA",0) //so it happens only once in this area
THEN
  RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","MYAREA",1)
    SetGlobal("xxBiff_Spawn","GLOBAL",1) //Biff was spawned in SoD
    CreateCreature("xxBiff",[x0.y0],S)  //for Biff, these are same coordinates as in bd0010_patch.baf since he was in front of the Palace before, too
    ActionOverride("xxBiff",MakeGlobalOverride())
    ChangeSpecifics("xxBiff",ALLIES)
    ActionOverride("xxBiff",ChangeAIScript("xxBiffs",OVERRIDE)) //Biff's SoD override script
    ActionOverride("xxBiff",ChangeAIScript("",CLASS))
    ActionOverride("xxBiff",ChangeAIScript("BDSHOUT",RACE))
    ActionOverride("xxBiff",ChangeAIScript("BDFIGH01",GENERAL))  //Biff's a fighter so he gets a fighter script. Have a look at bdparty.bcs to see what other scripts are available for the different classes.
    ActionOverride("xxBiff",ChangeAIScript("",DEFAULT))
    ActionOverride("xxBiff",SetDialog("xxBiffsG")  //greeting dialogue in SoD
END

/* 2+3: Biff needs to be moved here: he was either in party before (e.g. as a BG:EE NPC or in SoD beginning) OR he was spawned in SoD before */
IF
  !Dead("xxBiff")
  !InPartyAllowDead("xxBiff")
  OR(3) //One of the 3 following conditions needs to be true
	BeenInParty("xxBiff") //this is true if Biff was in party before
	GlobalGT("xxBiff_SpawnBG1","GLOBAL",0) //Biff was already spawned in BG1
	Global("xxBiff_Spawn","GLOBAL",1) //this is true if he was already spawned in bd0010.are
  Global("xxBiff_MoveCamp","MYAREA",0)  //so this happens only once in this area
THEN
  RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","MYAREA",1)
    SetGlobal("xxBiff_Spawn","GLOBAL",1) //Biff was spawned in SoD
    MoveGlobal("bd0101","xxBiff",[x0.y0])
    ActionOverride("xxBiff",Face(S))    //or any other direction depending on the coordinates
    ApplySpellRES("bdrejuve","xxBiff")  //completely heals and removes all spell effects
    ChangeEnemyAlly("xxBiff",NEUTRAL)
    ChangeSpecifics("xxBiff",ALLIES)
    ActionOverride("xxBiff",SetGlobal("bd_joined","locals",0))  //this is needed for kickout and moving to camps -> see below
    ActionOverride("xxBiff",SetGlobal("bd_retreat","locals",0)) //this variable is used to toggle behavior of crusaders in the class-dependent GENERAL scripts also used for NPCs. It needs to be "0" for NPCs.
    ActionOverride("xxBiff",SaveObjectLocation("LOCALS","bd_default_loc",Myself))
    ActionOverride("xxBiff",ChangeAIScript("xxBiffs",OVERRIDE)) //Biff's SoD override script
    ActionOverride("xxBiff",ChangeAIScript("",CLASS))
    ActionOverride("xxBiff",ChangeAIScript("BDSHOUT",RACE))
    ActionOverride("xxBiff",ChangeAIScript("BDFIGH01",GENERAL)) //Biff's a fighter so he gets a fighter script. Have a look at bdparty.bcs to see what other scripts are available for the different classes.
    ActionOverride("xxBiff",ChangeAIScript("",DEFAULT))
    ActionOverride("xxBiff",SetDialog("xxBiffsG")  //greeting dialogue in SoD
END

And for the tp2:

/* Biff is in front of Ducal Palace if told to wait - or Player marches onto the battle without recruiting NPCs in Baldur' Gate: Biff will be in front of the palace, too */
EXTEND_BOTTOM ~bd0101.bcs~ ~%MOD_FOLDER%/baf/bd0101_patch.baf~
  EVALUATE_BUFFER

6. Move Biff to first camp in Coast Way Crossing (bd1000.are)

So, PC is off to the first war camp, bd1000.are. Biff needs to come to the camp by himself in case he is not in party, i.e. he was left standing in front of the Ducal Palace but is supposed to follow.

The first war camp is in Coast Way Crossing, bd1000.are. Good-aligned NPCs usually gather near Rayphus Goodtree who is standing at [600.3725]. Evil NPCs seem to gather to the left in front of the tents, but of course you can put your NPC wherever you want. (You could even let him walk around, like Glint does.) Open the area with Near Infinity and note down the coordinates you want your NPC to wait at, we will call them [x1.y1] for now.

Then we create the bd1000_patch.baf with the spawn script block which needs to be patched to bd1000.bcs:

/* bd1000_patch.baf */
/* Biff moves to first war camp at Coast Way Crossing, bd1000.are */

/* Move Biff here. He was already spawned at some point. */
IF
  Global("xxBiff_Spawn","GLOBAL",1) //Biff was spawned in SoD
  Global("xxBiff_MoveCamp","MYAREA",0) //so it happens only once in this area
  !Dead("xxBiff")
  !InPartyAllowDead("xxBiff")
THEN
  RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","MYAREA",1)
    MoveGlobal("bd1000","xxBiff",[x1.y1]) 
    ActionOverride("xxBiff",Face(N))
    ReallyForceSpellDeadRES("bdrejuve","xxBiff")
    ChangeEnemyAlly("xxBiff",NEUTRAL)
    ChangeSpecifics("xxBiff",ALLIES)
    ActionOverride("xxBiff",SetGlobal("bd_joined","locals",0))
    ActionOverride("xxBiff",SetGlobal("bd_retreat","locals",0))
    ActionOverride("xxBiff",SaveObjectLocation("LOCALS","bd_default_loc",Myself))
    ActionOverride("xxBiff",ChangeAIScript("xxBiffs",OVERRIDE))
    ActionOverride("xxBiff",ChangeAIScript("bdasc3",CLASS))
    ActionOverride("xxBiff",ChangeAIScript("BDSHOUT",RACE))
    ActionOverride("xxBiff",ChangeAIScript("BDFIGH01",GENERAL))
    ActionOverride("xxBiff",ChangeAIScript("",DEFAULT))
    ActionOverride("xxBiff",SetDialogue("xxBiffsP")) //kickout dialogue: assuming the PC already talked to Biff
    Continue()
END

And for the tp2 via EXTEND_TOP:

/* Biff moves to war camp in bd1000.are */
EXTEND_TOP ~bd1000.bcs~ ~%MOD_FOLDER%/baf/bd1000_patch.baf~
  EVALUATE_BUFFER

The "Move NPC to Camp" variable gets also set in case the NPC is in party. For this, the original game uses a script block inside the bd1000.bcs which is triggered by Global("bd_move_npcs","bd1000",0) which does not check whether the NPCs are in party but which is later than the "spawn NPCs" script blocks. We patch our NPC's variable here, accordingly, to prevent the spawn/move script blocks to interfere later.

For this, the following is added to the tp2:

  
	
	/* Patch your NPC's "Move Camp" variable to the original game's script block (bd1000.bcs) */
COPY_EXISTING ~bd1000.BCS~ ~override~
	DECOMPILE_AND_PATCH BEGIN
		SPRINT textToReplace ~\(SetGlobal("bd_move_npcs","bd1000",1)\)~
		COUNT_REGEXP_INSTANCES ~%textToReplace%~ num_matches
		PATCH_IF (num_matches > 0) BEGIN
			REPLACE_TEXTUALLY ~%textToReplace%~ ~\1 SetGlobal("xxBiff_MoveCamp","MYAREA",1)~ 
			PATCH_PRINT ~Patching: %num_matches% matches found in %SOURCE_FILESPEC% for REPLACE_TEXTUALLY: %textToReplace%~
		END ELSE BEGIN
			PATCH_WARN ~WARNING: could not find %textToReplace% in %SOURCE_FILESPEC%~
		END
	END
BUT_ONLY
	

7. Move Biff to 2nd camp in Troll Claw Woods (bd7100.are)

The next camp is set in the Troll Claw Woods, bd7100.are. Search for fitting coordinates with Near Infinity, in this tutorial we will use the placeholder [x2.y2]. bd7100_patch.baf will contain Biff's spawn script block that needs to be patched to bd7100.bcs:

/* bd7100_patch.baf: */

/* Move him here. He was already spawned before or exists from BG1 */
IF
  Global("xxBiff_Spawn","GLOBAL",1) //Biff was spawned in SoD
  Global("xxBiff_MoveCamp","MYAREA",0)
  !Dead("xxBiff")
  !InPartyAllowDead("xxBiff")
THEN
    RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","MYAREA",1)
    MoveGlobal("bd7100","xxBiff",[x2.y2]) 
    ActionOverride("xxBiff",Face(N))
    ReallyForceSpellDeadRES("bdrejuve","xxBiff")
    ChangeEnemyAlly("xxBiff",NEUTRAL)
    ChangeSpecifics("xxBiff",ALLIES)
    ActionOverride("xxBiff",SetGlobal("bd_joined","locals",0))
    ActionOverride("xxBiff",SetGlobal("bd_retreat","locals",0))
    ActionOverride("xxBiff",SaveObjectLocation("LOCALS","bd_default_loc",Myself))
    ActionOverride("xxBiff",ChangeAIScript("xxBiffs",OVERRIDE))
    ActionOverride("xxBiff",ChangeAIScript("bdasc3",CLASS))
    ActionOverride("xxBiff",ChangeAIScript("BDSHOUT",RACE))
    ActionOverride("xxBiff",ChangeAIScript("BDFIGH01",GENERAL))
    ActionOverride("xxBiff",ChangeAIScript("",DEFAULT))
    ActionOverride("xxBiff",SetDialogue("xxBiffsP")) //kickout dialogue: assuming the PC already talked to Biff
    Continue()
END

And for the tp2 via EXTEND_TOP:

/* and the same for the next camp in bd7100.are: */
EXTEND_TOP ~bd7100.bcs~ ~%MOD_FOLDER%/baf/bd7100_patch.baf~
  EVALUATE_BUFFER

The "Move NPC to Camp" variable gets also set in case the NPC is in party. For this, the original game uses a script block inside the bd7100.bcs which is triggered by Global("bd_move_npcs","bd7100",0). We need to patch our NPC's variable here, accordingly.

For this, the following is added to the tp2:

  
	
	/* Patch your NPC's "Move Camp" variable to the original game's script block (bd7100.bcs) */
COPY_EXISTING ~bd7100.BCS~ ~override~
	DECOMPILE_AND_PATCH BEGIN
		SPRINT textToReplace ~\(SetGlobal("bd_move_npcs","bd7100",1)\)~
		COUNT_REGEXP_INSTANCES ~%textToReplace%~ num_matches
		PATCH_IF (num_matches > 0) BEGIN
			REPLACE_TEXTUALLY ~%textToReplace%~ ~\1 SetGlobal("xxBiff_MoveCamp","MYAREA",1)~ 
			PATCH_PRINT ~Patching: %num_matches% matches found in %SOURCE_FILESPEC% for REPLACE_TEXTUALLY: %textToReplace%~
		END ELSE BEGIN
			PATCH_WARN ~WARNING: could not find %textToReplace% in %SOURCE_FILESPEC%~
		END
	END
BUT_ONLY
	

8. Move Biff to the Coalition Camp in bd3000.are

The last camp the coalition moves to is the big Coalition Camp in bd3000.are. Again, Biff will follow in case he wasn't in the party upon transition. Look up bd3000.are in Near Infinity and note down the coordinates you want your PC to wait at, we will call them [x3.y3] for this tutorial.

bd3000_patch.baf (which will be patched to bd3000.bcs) with Biff's spawn script inside the Coalition Camp:

/* bd3000_patch.baf */
/* Moves Biff into the Coalition Camp if he was not in party */

/* Move him here, he was already spawned before or exists from BG1
IF
  Global("xxBiff_Spawn","GLOBAL",1) //Biff was spawned in SoD
  Global("xxBiff_MoveCamp","MYAREA",0)
  !Dead("xxBiff")
  !InPartyAllowDead("xxBiff")
THEN
  RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","MYAREA",1)
    MoveGlobal("bd3000","xxBiff",[x3.y3]) 
    ActionOverride("xxBiff",Face(N))
    ReallyForceSpellDeadRES("bdrejuve","xxBiff")
    ChangeEnemyAlly("xxBiff",NEUTRAL)
    ChangeSpecifics("xxBiff",ALLIES)
    ActionOverride("xxBiff",SetGlobal("bd_joined","locals",0))
    ActionOverride("xxBiff",SetGlobal("bd_retreat","locals",0))
    ActionOverride("xxBiff",SaveObjectLocation("LOCALS","bd_default_loc",Myself))
    ActionOverride("xxBiff",ChangeAIScript("xxBiffs",OVERRIDE))
    ActionOverride("xxBiff",ChangeAIScript("bdasc3",CLASS))
    ActionOverride("xxBiff",ChangeAIScript("BDSHOUT",RACE))
    ActionOverride("xxBiff",ChangeAIScript("BDFIGH01",GENERAL))
    ActionOverride("xxBiff",ChangeAIScript("",DEFAULT))
    ActionOverride("xxBiff",SetDialogue("xxBiffsP")) //kickout dialogue: assuming the PC already talked to Biff
    Continue()
END

And for the tp2 via EXTEND_TOP:

/* Last camp is in bd3000.are. In case Biff was never in party or kicked out to wait somewhere else, he will also be back in the (last) camp. */
EXTEND_TOP ~bd3000.bcs~ ~%MOD_FOLDER%/baf/bd3000_patch.baf~
  EVALUATE_BUFFER

The "Move NPC to Camp" variable gets also set in case the NPC is in party. For this, the original game uses a script block inside the bd3000.bcs which is triggered by Global("bd_move_npcs","bd3000",0). We need to patch our NPC's variable here, accordingly.

The use of this script block for bd3000.bcs is explained in the Tutorial Part 3, subsection "Move NPC to entrance of the Allied Siege Camp (Crusade battle)".

For this, the following is added to the tp2:

  
	
	/* Patch your NPC's "Move Camp" variable to the original game's script block (bd3000.bcs) */
COPY_EXISTING ~bd3000.BCS~ ~override~
	DECOMPILE_AND_PATCH BEGIN
		SPRINT textToReplace ~\(SetGlobal("bd_move_npcs","bd3000",1)\)~
		COUNT_REGEXP_INSTANCES ~%textToReplace%~ num_matches
		PATCH_IF (num_matches > 0) BEGIN
			REPLACE_TEXTUALLY ~%textToReplace%~ ~\1 SetGlobal("xxBiff_MoveCamp","MYAREA",1)~ 
			PATCH_PRINT ~Patching: %num_matches% matches found in %SOURCE_FILESPEC% for REPLACE_TEXTUALLY: %textToReplace%~
		END ELSE BEGIN
			PATCH_WARN ~WARNING: could not find %textToReplace% in %SOURCE_FILESPEC%~
		END
	END
BUT_ONLY
	

9. Move NPC to entrance of the Allied Siege Camp (Crusade battle)

At the start of the battle inside the Allied Siege Camp, the NPCs are moved to the entrance of the camp. This is done by patching bdasc3.bcs.

The variable used for his is the same as we used to move the NPC into the camp in case they were not in the group (refer to 8. Move Biff to the Coalition Camp in bd3000.are): Global("xxBiff_MoveCamp","bd3000",1).

This is what we will patch to bdasc3.bcs via EXTEND_BOTTOM:

  
/* bdasc3_patch.baf */
/* Move NPC to entrance of the Allied Siege Camp (Crusade battle) */
IF
	Global("bd_battle","global",1)
	Global("bd_move_npcs","bd3000",1)  // Allied Siege Camp
	Global("bd_move_to_entrance","locals",0)
	Name("xxBiff",Myself)  
	Global("xxBiff_MoveCamp","MYAREA",1)  // same variable as used for moving Biff here
THEN
	RESPONSE #100
		SetGlobal("bd_move_to_entrance","locals",1)
		JumpToPoint([xx.yy]) //Coordinates at the entrance of the Siege Camp. Minsc for example jumps to [1465.400]
		Face(N)
		SaveObjectLocation("LOCALS","bd_default_loc",Myself)
END
	

And the tp2 patching:

  
/* Move NPC to entrance of the Allied Siege Camp (Crusade battle) */
EXTEND_BOTTOM ~bdasc3.bcs~ ~%MOD_FOLDER%/baf/bdasc3_patch.baf~
  EVALUATE_BUFFER
	

This is not all we need to make our NPC work as an original one, though. In the Tutorial Part 1, we moved our NPC here in case they were not in party and set a variable for this:

Global("xxBiff_MoveCamp","MYAREA",1).

As soon as the battle starts ("SetGlobal("bd_battle","global",1) in bd3000.bcs"), the game does the following: the "Move to Camp" variables of all NPC are reset to "0". This results in *all* NPCs being called back to camp in case they were left standing elsewhere. Then, all NPCs are moved to the camp entrance regardless of whether they are in the party or not by the above script patching to bdasc3.bcs.

The "Move NPC to Camp" variable was also set "blindly" in case the NPC was in party, and we see now what this was for in bd3000.bcs.

In the same script block where the battle starts and all NPCs are called back to camp, the general variable of the script block which sets the "Move NPC to Camp" variables of *all* NPCs to one is reset to "0", as well. This leads to the "Move NPC to Camp" variable to be reset to "1" for the NPCs in party, too. Thus, now the "move NPC to camp entrance" script blocks from bdasc3.bcs will be executed for all NPCs in party as well, moving them to the camp entrance for the battle to start.

For this, the following is added to the tp2:

  
	
	/* Patch your NPC's "Move Camp" variable to the original game's script block (bd3000.bcs) to call back NPC when battle starts */
COPY_EXISTING ~bd3000.BCS~ ~override~
	DECOMPILE_AND_PATCH BEGIN
		SPRINT textToReplace ~\(SetGlobal("bd_move_voghiln","bd3000",0)\)~
		COUNT_REGEXP_INSTANCES ~%textToReplace%~ num_matches
		PATCH_IF (num_matches > 0) BEGIN
			REPLACE_TEXTUALLY ~%textToReplace%~ ~\1 SetGlobal("xxBiff_MoveCamp","MYAREA",0)~ 
			PATCH_PRINT ~Patching: %num_matches% matches found in %SOURCE_FILESPEC% for REPLACE_TEXTUALLY: %textToReplace%~
		END ELSE BEGIN
			PATCH_WARN ~WARNING: could not find %textToReplace% in %SOURCE_FILESPEC%~
		END
	END
BUT_ONLY
	

10. Make Biff return to camp if told so after being kicked out in wilderness area

So, Biff will move with the camps in case he was left standing, either in a camp itself or somewhere else. But there is more in the SoD scripting we can take advantage of. For example, what if we want Biff to return to the current camp when kicked out in some wilderness area? And which camp is the current one, anyway? - Fortunately, SoD has means to detect this.

Let's have a look at bdparty.bcs. This script is set by dplayer2.bcs if an NPC is kicked out of the party in SoD. It makes every NPC talk to the PC after being kicked out (or leave if unhappy) and also sets "Global("bd_joined","locals",1)" which we will use in Biff's kickout dialogue.

The script also sets the appropriate GENERAL script (depending on your NPC's class), RACE, and DEFAULT scripts and resets several local variables.

We need the following addition to bdparty.bcs to make Biff move back to the camp on his own if kicked out somewhere else. Everything else is done by the script. The used variable Global("bd_npc_camp","locals",1) which makes Biff go back to the (current) camp is set by the kickout dialogue which will be discussed in Chapter 14: "Biff's SoD kickout dialogue with the needed local variables".

Please note: Trigger overrides for objects that may not exist in the area might bug out so that is one of the reasons it is integrated into an OR trigger here. In addition, TriggerOverride needs to be the last in a condition block, i.e. switching the !Range() and !TriggerOverride() in the OR(2) block would lead to the OR-trigger to fail. This is also the case for dialogue triggers.

/* bdparty_patch.baf:
Addition to bdparty.bcs to toggle moving back to the current camp if kicked out of party. */

IF
  Global("bd_npc_camp","locals",1)  //this is set via the kickout dialogue xxBiffsP.dlg
  Name("xxBiff",Myself)
  Switch("bd_npc_camp_chapter","global")  //this makes sure the NPC goes back to the correct camp along SoD's campaign
  OR(2)
    !Range("ff_camp",999) //"Name" of a trigger which detects the camps
    !TriggerOverride("ff_camp",IsOverMe("xxBiff"))
THEN
  RESPONSE #2
    EscapeAreaMove("bd1000",x1,y1,N) //coordinates as used in bd1000_patch.baf
  RESPONSE #3
    EscapeAreaMove("bd7100",x2,y2,N) //coordinates as used in bd7100_patch.baf
  RESPONSE #4
    EscapeAreaMove("bd3000",x3,y3,N) //coordinates as used in bd3000_patch.baf
END

/* this sets the "bd_npc_camp" variable to "2" as soon as Biff is in one of the camps again: bg1000.are, bg7100.are, bd3000.are – and will make bdparty.bcs set the correct fighting script etc. */
IF
  GlobalLT("bd_npc_camp","locals",2)
  Global("bd_joined","locals",0)
  Name("xxBiff",Myself)
  TriggerOverride("ff_camp",IsOverMe("xxBiff"))
  Switch("bd_npc_camp_chapter","global")
THEN
  RESPONSE #2
    SetGlobal("bd_npc_camp","locals",2)
    SaveLocation("LOCALS","bd_default_loc",[x1.y1]) //coordinates as used in bd1000_patch.baf
	/* Note: here the "LOCALS" really need to be in capital letters. Ardanis: "Coordinate variables 
are about the only place I know where case matters." */
  RESPONSE #3
    SetGlobal("bd_npc_camp","locals",2)
    SaveLocation("LOCALS","bd_default_loc",[x2.y2]) //coordinates as used in bd7100_patch.baf
  RESPONSE #4
    SetGlobal("bd_npc_camp","locals",2)
    SaveLocation("LOCALS","bd_default_loc",[x3.y3]) //coordinates as used in bd3000_patch.baf
    ChangeAIScript("bdasc3",CLASS)
END

And for the tp2 (note the EXTEND_TOP, since the script blocks needs to be applied early, but we do not add "Continue()" here as it might leads to bugs for our mod - this is an exception):

/* return to camp if kicked out */
EXTEND_TOP ~bdparty.bcs~ ~%MOD_FOLDER%/baf/bdparty_patch.baf~
  EVALUATE_BUFFER

11. Make Biff follow if kicked out in Avernus

We are not done with script additions yet. If kicked out in the hell dimension bd4700, the NPC does not move directly back to camp but is moved with the party in the appropriate cutscenes bdcut58.bcs and bdcut59b.bcs.

This is the addition for Biff to bdcut58.bcs, where all characters are moved back to the portal in bd4400.are:

/* bdcut58_patch.baf
Biff was left standing in bd4700 - make sure he's coming to the last hell area, too */

IF
  Global("xxBiff_kicked_bd4700","global",1) //set in xxBiffsP.dlg
  !Dead("xxBiff")
  !InParty("xxBiff")
THEN
  RESPONSE #100
    CutSceneId(Player1)
    MoveGlobal("bd4400","xxBiff",[x4.y4]) //coordinates somewhere near the portal in bd4400.are. For example, Minsc's are: [1160.1250]
    ActionOverride("xxBiff",Face(NE))     //or whatever so he is facing the portal
END

And for the tp2 (note the EXTEND_TOP):

/* Biff is kicked in hell dimension - teleport to last hell area bd4400.are */
EXTEND_TOP ~bdcut58.bcs~ ~%MOD_FOLDER%/baf/bdcut58_patch.baf~
  EVALUATE_BUFFER

12. Move Biff to Dragonspear Castle interior after PC returns from Avernus

bdcut59b.bcs deals with moving PC and all NPCs – joined or left standing in Avernus - back into the Dragonspear Castle interior (bd4300.are) which is the last area before the “end game” starts where none of the other areas are accessible any more (for plain SoD). It also deals with NPCs not in party (left standing in the last camp or some wilderness area, not in Avernus) being moved here.

Thus, if Biff was kicked out of the party in Avernus (bd4700.are), the variable Global("xxBiff_kicked_bd4700","global",1) was set by the kickout dialogue xxBiffsP.dlg as shown in Chapter 14, and he should be near the portal (because he just came through it since he was in Avernus even without being in the party).

For Global("xxBiff_kicked_bd4700","global",0), he can be wherever he wants to be - maybe in the room to the northeast since that's where most of the other NPCs seem to be waiting.

This is what bdcut59b.bcs needs to be patched with for Biff:

/* bdcut59b_patch.baf */

/* Global("xxBiff_kicked_bd4700","global",1): Biff was left standing in hell dimension - make sure he's coming back to the castle, too */
IF
  Global("xxBiff_kicked_bd4700","global",1) //set in xxBiffsP.dlg
  !Dead("xxBiff")
  !InParty("xxBiff")
THEN
  RESPONSE #100
    CutSceneId(Player1)
    MoveGlobal("bd4300","xxBiff",[x5.y5]) //coordinates above the portal. For example: Minsc's are [685.310]
    ActionOverride("xxBiff",Face(N))
END

/* This moves Biff into the castle in case he was left standing somewhere else (not in hell dimension) because the other areas cannot be visited again (for plain SoD) */
IF
  Global("xxBiff_kicked_bd4700","global",0)
  !Dead("xxBiff")
  !InParty("xxBiff")
THEN
  RESPONSE #100
  CutSceneId(Player1)
  MoveGlobal("bd4300","xxBiff",[x6.y6]) //coordinates wherever Biff should be waiting if he wasn't with the PC to Avernus. For example near Rayphus who is at [1840.1260], in the room to the northeast.
  ActionOverride("xxBiff",Face(NW))
END

And for the tp2 (note the EXTEND_TOP):

/* Biff is kicked in hell dimension or was waiting elsewhere- teleport to Dragonspear Castle interior (bd4300.are) after PC returns from Avernus */
EXTEND_TOP ~bdcut59b.bcs~ ~%MOD_FOLDER%/baf/bdcut59b_patch.baf~
  EVALUATE_BUFFER

And that was all we needed for script patching for moving Biff until this point. Only thing missing are the according greeting dialogue xxBiffsG.dlg and kickout dialogue xxBiffsP.dlg.

13. Biff's SoD greetings dialogue with the needed local variables

For the SoD greeting's dialogue, the following would do.

/* Biff's SoD greetings dialogue */

BEGIN xxBiffsG

/* we need an always true "can I join you" block, because this dialogue might also trigger if Biff was left standing in BG city and is now waiting either in front of the Ducal Palace when the PC is leaving for the crusade, or even in one of the camps! */
IF ~True()~ THEN sod_meeting
  SAY ~<CHARNAME>! Can I join you?~
  ++ ~Sure!~ + sod_meeting_01
  ++ ~Not right now, but make sure to stay around in case I need you later!~ + sod_meeting_02
END

IF ~~ THEN sod_meeting_01
  SAY ~Thank you!~
  IF ~~ THEN DO ~JoinParty()~ EXIT
END

IF ~~ THEN sod_meeting_02
  SAY ~I will do that.~
  IF ~~ THEN EXIT
END

For the Tutorial mod package, I put this into a d-file called "greetingsdialogue.d". This is the compilation in the tp2:

/* SoD greeting dialogue */
COMPILE EVALUATE_BUFFER ~%MOD_FOLDER%/dialogue/greetingsdialogue.d~

14. Biff's SoD kickout dialogue with the needed local variables

SoD uses the kickout dialogue, joined dialogue and dream script defined in the BDDIALOG.2DA. For Biff, the SoD kickout dialogue will be xxBiffsP.dlg.

The kickout dialogue uses the following variables:

Global("bd_joined","locals",0): will be set to "1" by DPLAYER2.bcs upon kickout and can be used to detect a kickout to trigger the “Do you really want me to go?” dialogue. Needs to be set to “0” after the NPC is told to leave the group.

Global("bd_npc_camp","locals",1) needs to be set if NPC is told to return to the camp so bdparty.bcs can do the transition.

~GlobalGT("bd_npc_camp_chapter","global",1) GlobalLT("bd_npc_camp_chapter","global",5)~: time span after leaving for the crusade and before going to Avernus. This is the time where NPCs can return to a coalition camp.

AreaCheck("bd4700") GlobalLT("bd_plot","global",570): in Avernus, before endfight against Belhifet

!Range("ff_camp",999): “ff_camp” is the name of proximity triggers covering the camps' area (in all three camp areas)

This would do for a basic xxBiffsP.dlg.

/* xxBiffsP.dlg Biff's kickout dialogue for SoD */
BEGIN ~xxBiffsP~

/* If kicked out in Avernus in bd4700.are
This sets Global("xxBiff_kicked_bd4700","global",1) which is used in bdcut58_patch.baf and bdcut59b_patch.baf */
/* Biff can't go home from here - so he will stay and say something brave. */
IF ~AreaCheck("bd4700")
    GlobalLT("bd_plot","global",570)~ THEN BEGIN kickout_1
  SAY ~Sure, er, I'll stay around... and fight.~
  IF ~~ THEN DO ~SetGlobal("xxBiff_kicked_bd4700","global",1)
                 SetGlobal("bd_joined","locals",0)~ EXIT
END

/* If kicked out in Korlasz' Crypt */
IF ~OR(2)
      AreaCheck("bd0120")
      AreaCheck("bd0130")
    GlobalGT("bd_joined","locals",0)~ THEN BEGIN kickout_2
  SAY ~You want me to go?~
  /* commented out - this reply options doesn't *do* anything - maybe it gets fixed at some point
  + ~OR(2)
    !Range("ff_camp",999)
    NextTriggerObject("ff_camp")
    !IsOverMe("jaheira")~ THEN REPLY #%2%66314 /* ~I would. Go to the crypt entrance and wait for me there.~ */ DO ~SetGlobal("bd_npc_camp","locals",1) + kickout_3
	*/
  ++ ~Just remain here and await my return, all right?~ + kickout_3
  ++ ~No, stay with the group.~ + kickout_4
END

IF ~~ THEN BEGIN kickout_3
  SAY ~Fine, I'll be around.~
  IF ~~ THEN DO ~SetGlobal("bd_joined","locals",0)~ EXIT
END

IF ~~ THEN BEGIN kickout_4
  SAY ~My pleasure!~
  IF ~~ THEN DO ~JoinParty()~ EXIT
END

/* kicked out somewhere else (not bd4700.are in Avernus, not Korlasz' Crypt) */
IF ~GlobalGT("bd_joined","locals",0)~ THEN BEGIN kickout_5
  SAY ~You want me to go?~
  + ~GlobalGT("bd_npc_camp_chapter","global",1)
     GlobalLT("bd_npc_camp_chapter","global",5)
/* Note: The following OR() structure "needs to be in exactly that order - Range() first, NTO(ISOverMe()) next, - and not followed by any further script syntax, otherwise the game will fail to evaluate the trigger if ff_camp is not present in current area." - Ardanis */
     OR(2)
       !Range("ff_camp",999)
       NextTriggerObject("ff_camp")
     !IsOverMe("xxBiff")~ + ~Please go back to the camp.~ DO ~SetGlobal("bd_npc_camp","locals",1)~ + kickout_6
  ++ ~Just remain here and await my return, all right?~ + kickout_3
  ++ ~No, stay with the group.~ + kickout_4
END

IF ~~ THEN BEGIN kickout_6
  SAY ~I will.~
  IF ~~ THEN DO ~SetGlobal("bd_joined","locals",0)~ EXIT
END

/* join-up after leaving the group */
IF ~Global("bd_joined","locals",0)~ THEN join_again
  SAY ~Want me to join again?~
  ++ ~Yes, please come along once more.~ + kickout_4
  ++ ~Just remain here and await my return, all right?~ + kickout_3
END

For the Tutorial mod package, I put this into a d-file called "kickoutdialogue.d". This is the compilation in the tp2:

/* SoD kickout dialogue */
COMPILE EVALUATE_BUFFER ~%MOD_FOLDER%/dialogue/kickoutdialogue.d~

How to move etc. your NPC from this point on in the cutscenes at the end of SoD will be explained in Modding Tutorial Part 2: Make Your NPC Comment and Move Along at the End of SoD.

15. Credits, Used Tools, and Helpful Links

Thank you to Argent77 for turning this tutorial into a html file!

Used Tools:

Near Infinity

IESDP

grepWin

Notepad++

SoD Walkthrough on GameBanshee

Links:

BeamDog Forums

The Gibberlings 3

Kerzenburgforum

Spellhold Studios

Pocket PLane Group

Prefix Reservation at Black Wyrm Lair Forums

16. Version History

Version 9:

Version 8:

Version 7:

Version 6:

Version 5:

Version 4:

Version 3:

Version 2:

Version 1: