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

Version 4, by jastey

Contents

  1. Transition into SoD: change Biff's OVERRIDE script to the SoD one for a continuous BG:EE-SoD game
  2. Make Biff leave party after Korlasz's Crypt is cleared
  3. Add Biff to Corwin's list about companions in BG city
  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. Make Biff return to camp if told so after being kicked out in wilderness area
  10. Make Biff follow if kicked out in Avernus
  11. Move Biff to Dragonspear Castle interior after PC returns from Avernus
  12. Biff's SoD greetings dialogue with the needed local variables
  13. Biff's SoD kickout dialogue with the needed local variables
  14. Make the Mod EET compatible
  15. Credits, Used Tools, and Helpful Links
  16. Version History

This tutorial deals with the transition of an NPC from BG:EE to SoD 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 script name (death variable)
xxBiffs.dlg Biff's greeting dialogue in SoD
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 xxbd1000.baf and xxbdparty.baf
[x2.y2] area coordinates for Biff's camp spot in bd7100.are, used in xxbd7100.baf and xxbdparty.baf
[x3.y3] area coordinates for Biff's camp spot in bd3000.are, used in xxbd3000.baf and xxbdparty.baf

1. Transition into SoD: change Biff's OVERRIDE script to the SoD one for a continuous BG:EE-SoD game

This section is only of interest if your NPC was also available in BG:EE. If your NPC is a pure SoD NPC, you can skip this section.

If your NPC was also available in BG:EE, it is good practise to change the OVERRIDE script to a SoD specific one upon the transition into SoD. The advantages are simple: you don't have to take care that events related to BG:EE story or areas do not fire any more. Of course you can also continue using the former OVERRIDE script and make sure by using the according triggers that only SoD related actions will be triggered.

Sidenote: if you want to distinguish between BG:EE and SoD, an easy way to detect SoD in a BG:EE+SoD game is by using the unique variable "GlobalGT("bd_plot","global",0)" which 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.") This might be more useful for dialogues, though (PID, for example), since it's good practice to just use a separate script for SoD. (For EET, you can use "Global("ENDOFBG1","GLOBAL",1)" for the SoD portion of the game, which will be set to "2" in BGII.) And while we are at it: 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)" - just in case you want to count the intro dungeon to "BG:EE", content wise.

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.

So, if your NPC should use SoD specific joined dialogue and OVERRIDE script, we need to patch the bdintro.bcs with the following (with Biff being our example NPC):

/* xxbdintro.baf
transition from BG:EE to SoD: change Biff's Joined dialogue and OVERRIDE script */
IF
	Global("SOD_fromimport","global",1)
	InMyArea("xxBiff")  
THEN
	RESPONSE #100
		CutSceneId(Player1)
		ActionOverride("xxBiff",SetDialog("xxBiffJ")) //Biff's SoD joined dialogue
		ActionOverride("xxBiff",ChangeAIScript("xxBiffs",OVERRIDE)) //Biff's SoD script
END
	  

With the line for patching in the tp2 (note the EXTEND_TOP):

/* transition from BG:EE to SoD: change Biff's Joined dialogue and OVERRIDE script */
EXTEND_TOP ~bdintro.bcs~ ~%MOD_FOLDER%/baf/xxbdintro.baf~
  EVALUATE_BUFFER

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.

First, we add an EXTEND_BOTTOM 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). Then, we add the new dialogue state where Corwin tells about Biff. To this dialogue state we will patch all transactions from state 39 so the original cycle of mentioned NPCs - plus any mod NPCs added before Biff - will go on.

/* This goes into a .d file: Make Corwin mention Biff's whereabouts in BG city */
EXTEND_BOTTOM bdschael 39
IF ~!Dead("xxBiff") !InPartyAllowDead("xxBiff") BeenInParty("xxBiff")~ THEN + mynpc
END

APPEND bdschael

IF ~~ THEN mynpc
SAY ~Also, Biff the Understudy was seen right in front of the Ducal Palace's gates not long ago.~
COPY_TRANS bdschael 39 //This will add all transactions from bdschael.dlg state 39 here, so nothing of the original gets lost (also none of the mods installed before Biff)
END

END //APPEND

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

SoD starts with the party from BG1 being in Korlasz's Crypt, the "last follower" of Sarevok. After Korlsz's tomb 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's crypt. If your NPC joins in BG city or later, you can skip this and continue with top 4.

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.

The xxbd0103.baf looks like this:

/* xxbd0103.baf:
Biff will leave the group after clearing Korlasz's tomb */
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/xxbd0103.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 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's Tomb 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 xxbd0010.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's crypt):

/* xxbd0010.baf

Biff will be waiting in front of the Ducal Palace */
/* new SoD game */
IF
  Global("xxBiffSpawn","GLOBAL",0) //any variable that is true for a new game
  Global("xxBiff_MoveCamp","bd0010",0) //so it happens only once
THEN
  RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","bd0010",1)
    SetGlobal("xxBiffSpawn","GLOBAL",1)
    CreateCreature("xxBiff",[x0.y0],N)  //open area in NI and chose coordinates and face direction
    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("xxBiffs")  //greeting dialogue in SoD
END

/* 2nd script block if Biff was in party before */
IF
  !Dead("xxBiff")
  !InPartyAllowDead("xxBiff")
  BeenInParty("xxBiff") //this variable is set automatically if Biff was in Party in BG1 or SoD
  Global("xxBiff_MoveCamp","bd0010",0)  //so it happens only once
THEN
  RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","bd0010",1)
    SetGlobal("xxBiffSpawn","GLOBAL",1) //we set this so he will be moved to bd0101.are, not spawned anew
    MoveGlobal("bd0010","xxBiff",[x0.y0])
    ActionOverride("xxBiff",Face(N))    //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("xxBiffs")  //greeting dialogue in SoD
END

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

/* let Biff reappear in front of the Duchal Palace */
EXTEND_BOTTOM ~bd0010.bcs~ ~%MOD_FOLDER%/baf/xxbd0010.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 three possible cases here:

  1. It's a new SoD game and the player didn't meet Biff yet because he didn't go into the city first but left directly for the crusade.
  2. Biff was in party before but PC didn't meet him yet because he didn't go into the city first but left directly for the crusade.
  3. Biff and the PC already met but Biff was told to wait.

Points 2 and 3 need only one spawn script block in our example.

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

/* xxbd0101.baf
1. It's a new soD game, or Biff wasn't spawned yet in SoD because the player went straight for the crusade */
IF
  !Dead("xxBiff")
  !InPartyAllowDead("xxBiff")
  Global("xxBiffSpawn","GLOBAL",0) //true for a new SoD game or if Biff wasn't spawned in bd0010.are
  Global("xxBiff_MoveCamp","bd0101",0) //so it happens only once
THEN
  RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","bd0101",1)
    SetGlobal("xxBiffSpawn","GLOBAL",1)
    CreateCreature("xxBiff",[601.599],S)  //for Biff, these are same coordinates as in xxbd0010.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("xxBiffs")  //greeting dialogue in SoD
END

/* 2+3: Biff was 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(2) //One of the 2 following conditions needs to be true
	BeenInParty("xxBiff") //this is true if Biff was in party before
	Global("xxBiffSpawn","GLOBAL",1) //this is true if he was already spawned in bd0010.are
  Global("xxBiff_MoveCamp","bd0101",0)  //so it happens only once
THEN
  RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","bd0101",1)
    MoveGlobal("bd0101","xxBiff",[601.599])
    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("xxBiffs")  //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/xxbd0101.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.

From now on, we only consider the moving of the existent cre. Spawning of a new cre for a new SoD game is no longer necessary, because he was already spawned in bd0010.are or bd0101.are, so now we can just move him with MoveGlobal().

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 xxbd1000.baf with the spawn script block which needs to be patched to bd1000.bcs:

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

IF
  Global("xxBiff_MoveCamp","bd1000",0)
  !Dead("xxBiff")
  !InPartyAllowDead("xxBiff")
THEN
  RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","bd1000",1)
    MoveGlobal("bd1000","xxBiff",[x1.y1]) //look up area coordinates for bd1000.are
    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"))
    Continue()
END

And for the tp2:

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

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

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

/* xxbd7100.baf: */
IF
  Global("xxBiff_MoveCamp","bd7100",0)
  !Dead("xxBiff")
  !InPartyAllowDead("xxBiff")
THEN
    RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","bd7100",1)
    MoveGlobal("bd7100","xxBiff",[x2.y2]) //look up area coordinates for bd7100.are
    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"))
    Continue()
END

And for the tp2:

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

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 NI and note down the coordinates you want your PC to wait at, we will call them [x3.y3] for this tutorial.

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

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

IF
  Global("xxBiff_MoveCamp","bd3000",0)
  !Dead("xxBiff")
  !InPartyAllowDead("xxBiff")
THEN
  RESPONSE #100
    SetGlobal("xxBiff_MoveCamp","bd3000",1)
    MoveGlobal("bd3000","xxBiff",[x3.y3]) //look up area coordinates for bd3000.are
    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"))
    Continue()
END

And for the tp2:

/* 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_BOTTOM ~bd3000.bcs~ ~%MOD_FOLDER%/baf/xxbd3000.baf~
  EVALUATE_BUFFER

9. 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 below.

/* xxbdparty.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, see below
  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 xxbd1000.baf
  RESPONSE #3
    EscapeAreaMove("bd7100",x2,y2,N) //coordinates as used in xxbd7100.baf
  RESPONSE #4
    EscapeAreaMove("bd3000",x3,y3,N) //coordinates as used in xxbd3000.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 xxbd1000.baf
  RESPONSE #3
    SetGlobal("bd_npc_camp","locals",2)
    SaveLocation("LOCALS","bd_default_loc",[x2.y2]) //coordinates as used in xxbd7100.baf
  RESPONSE #4
    SetGlobal("bd_npc_camp","locals",2)
    SaveLocation("LOCALS","bd_default_loc",[x3.y3]) //coordinates as used in xxbd3000.baf
    ChangeAIScript("bdasc3",CLASS)
END

And for the tp2 (note the EXTEND_TOP):

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

10. Make Biff follow if kicked out in Avernus

We are not done with script additions yet. If kicked out in the hell dimension (in area bd4700.are to make room for Caelar), 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:

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

IF
  Global("xxBiff_kicked_bd4700","global",1) //set in xxBiffsP.dlg, see below
  !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/xxbdcut58.baf~
  EVALUATE_BUFFER

11. 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 below, 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:

/* xxbdcut59b.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, see below
  !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/xxbdcut59b.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 xxBiffs.dlg and kickout dialogue xxBiffsP.dlg.

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

What we still need is the greetings dialogue xxBiffs.dlg and the kickout dialogue xBiffP.dlg with all the used variables from above.

For the SoD greeting's dialogue, the following would do. Note that it sets “Global("xxBiffWait","GLOBAL",1)” if Biff should wait for the PC which is used in the script block in xxbd0101.baf above as well as the GLOBAL variable "xxBiffSpawn" which is set to “1” if he is spawned for a new game.

/* Biff's SoD greetings dialogue */

BEGIN xxBiffs

/* 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 in front of the Ducal Palace when the PC is leaving for the crusade */
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

13. 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 (so Caelar can join) in bd4700.are
This sets Global("xxBiff_kicked_bd4700","global",1) which is used in xxbdcut59b.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's tomb */
IF ~OR(2)
      AreaCheck("bd0120")
      AreaCheck("bd0130")
    GlobalGT("bd_joined","locals",0)~ THEN BEGIN kickout_2
  SAY ~You want me to go?~
  ++ ~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's tomb) */
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)
     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

And that would be all – for now. What is still missing is the moving around and commenting on the events of Biff in the cutscenes at the end of SoD. I am planning on writing a tutorial for that, too.

14. Make the Mod EET compatible

The depth of what is required for your mod to be EET compatible depends strongly on what your mod will cover. If your NPC is also available in BG:EE, then the naming differences of most BG1 resources (those files that needed renaming because BGII has different files of the same name) require the use of OUTER_SPRINT variables as defined in the widely used cpmvars.tpas for e.g. area names, Imoen's script name, or some cre file names. The principle of this multi-platform coding is described in cmorgan's tutorial Crossing the Great Divide. k4thos made a list about different naming conventions here.

If looking "only" at SoD, there is the huge advantage that most resources didn't need to be renamed as SoD already uses unique file names and almost all resources are the same for SoD(BG:EE) and SoD(EET). One exception is Imoen's death variable: it is "IMOEN" in SoD and "IMOEN2" in EET.

Then the only question is whether the NPC will be available in BGII, too (and whether you realize this by a continuous NPC that has the same script variable (death name) in BGII, also), or whether the NPC should not be available if the player continuous into BGII after finishing SoD in EET.

EET offers a very comfortable function to provide your NPC mod with all needed script additions etc. for the transitions BG1 -> SoD, SoD -> SoA, and SoA -> ToB. The function is called EET_NPC_TRANSITION and there is a very detailed description how to use it and what it does in the according Modder Notes (It is well possible the link will not work because of the apostroph in it. Here is the address for copy&paste: https://cdn.rawgit.com/K4thos/EET/master/EET/docs/Modder's Notes.html.)

Biff is supposed to be a SoD NPC, so the mod would be category "1" (BG:EE/SoD NPC). The needed parameters for the function would be the following:

/* EET compatibility: Biff is an BG1 (SoD) NPC only. His name will be mentioned on EET's fate spirit in ToB, but he will not be summonable (like all NPCs that didn't join the group in BGII). */

ACTION_IF GAME_IS ~eet~ BEGIN
  INCLUDE ~EET/other/EET_functions.tph~ //this function will be in the player's own EET folder
  LAF ~EET_NPC_TRANSITION~ 
    INT_VAR
      type = 1 //Biff is only available in BG:EE/SoD
    STR_VAR
      dv = "xxBiff" //Biff's script name (death variable)
      override_BG1 = "" /* OVERRIDE script in BG:EE. Put your NPC's BG:EE script here UNLESS you use the same script for BG:EE and SoD 
(adds a "destruct in Chapter greater than 7" scriptblock, for example if your NPC was spawned in BG:EE but was never in party (although "BeenInParty" is not being checked by this scriptblock)). 
If your NPC is SoD only like Biff, leave this open. */
      override_SoD = "xxBiffs" //OVERRIDE script in SoD. This will add a "DestroySelf()" in Chapter greater than 13 to the SoD script.
      traFile = "%MOD_FOLDER%/tra/%LANGUAGE%/dialog.tra" //path to the text line for the ToB Fate Spirit summoning
      string = "@0" /* ~Bring me Biff the Understudy.~ */
      stringPosDV = "Baeloth" //Name after which Biff's should be put into the order of reply options at the fate spirit summoning dialogue. 
/* From the "Modder's Notes for Baldur's Gate: Enhanced Edition Trilogy (EET)":
      //Aerie, Ajantis, Alora, Anomen, Baeloth, Branwen, Cernd, Coran, Corwin, Dorn, Dynaheir, Edwin, Eldoth, Faldorn, Garrick,
      //Glint, HaerDalis, Hexxat, Imoen2, Jaheira, Jan, Kagain, Keldorn, Khalid, Kivan, Korgan, MKhiin, Mazzy, Minsc, Montaron,
      //Nalia, Neera, Quayle, Rasaad, Safana, SharTeel, Skie, Tiax, Valygar, Viconia, Voghiln, Wilson, Xan, Xzar, Yeslick, Yoshimo
      //variable not set (default) = NPC name appended at the end of summoning list */
  END
END	  
	  

For Biff being an SoD mod only, this is basically it. The transition from SoD to SoA in EET is done by K#TELBGT.bcs which lets all NPCs that are not multiplayer characters leave the party. The EET_NPC_TRANSITION will add a DestroySelf() to Biff's OVERRIDE script if the chapter (of the continuous EET chapter system) hits chapter 14. So, we do not need to add anything to make sure he will not be present in BGII.

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 4:

Version 3:

Version 2:

Version 1: