As of 1.0.132

4. Story Scripts for the Main Campaign
Note: A full listing of the Story scripting functions is useful for this section. Such a listing can be located at http://www.larian.com/forums/ubbthreads.php?ubb=showflat&Number=531151#Post531151

Modifying an existing Main Campaign Script-

The Story for the Main module (or any module for that matter) is where many important functions are defined. Dialog and trading are two examples of important functions defined in the Main module's Story. As modders we can over-write the Story scripts that the Main campaign comes with and/or come up with our own Story scripts.

First, however, a note about the Story and savegames. The way that the Story works is that the game generates it completely at the beginning of the game (when you start a New Game) and saves that generated story with the save file itself. If the Story for a module gets changed, the changes will have no effect until a new game is started (no effect on old savegames with the module).

Because the Story is determined at New Game, the game does incorporate every activated mod's story changes (even if they are #2+ in the load order). This is in contrast to non-Story elements (like RootTemplates) that have no effect if the mod is #2+ in the load order.

Any type of Story Editing can be done via the Story Editor in the Divinity Engine. At the top middle there is an "S" icon that says Story Editor. Click that and a window opens up containing all of the scripts that the Main module uses. You can double click any of the entries and browse them at your leisure.

To over-write a pre-existing story script, right click it in the left box and click "Copy to my mod". You will then be able to make changes and have them be in effect instead of the original version.

For example, what if you want to give the player additional ability/talent/etc points upon arriving at Cyseal? First, take a look at this portion of the CYS_General story script-
Code
IF
CharacterCreationFinished(CHARACTER_NULL)
AND
CurrentLevel(_Lvl)
AND
DB_CharacterCreationLevels(_Lvl)
THEN
CharacterTeleportPartyToTriggerMovieLoadState(TRIGGER_CYS_Start_P1,"","MovieGameIntro");
ItemToCharacter(ITEM_CYS_SHGuide1, CHARACTER_Player1);
ItemToCharacter(ITEM_CYS_SHGuide2, CHARACTER_Player2);
CharacterAddGold(CHARACTER_Player1,200);
CharacterAddGold(CHARACTER_Player2,200);
DB_CYSDoTutorial(1);


The game by default gives the party gold and their items after finishing character creation, in addition to teleporting them to the beach. I need to go into an explanation of the syntax before any changes are made though, since the scripting language is different from other programming languages in several respects.

Beginning with the first lines-
Code
IF
CharacterCreationFinished(CHARACTER_NULL)

The "IF" line is what tells the game to do something if the event immediately afterwards is called. In this case, it's the CharacterCreationFinished((Character)) event. If the CharacterCreationFinished event is called, each and every script that checks IF that event was called will trigger (though whether they do anything depends on further checks detailed later on) in the order that they are present in the scripts. This will be important later on when it comes to making our own scripts.

Code
AND
CurrentLevel(_Lvl)

The "AND" keyword is what represents the additional steps that must be taken to check whether the "THEN" lines later on are called. Notice the "_Lvl" located within the parentheses. It is an Integer variable that is created locally when the event is called, and is what stores the value returned by the CurrentLevel function. Anything that starts with an underscore in Story scripts is a variable, whose type is determined by the function that creates it. This variable can be used by any further "AND" and "THEN" statements that appear within the same IF-THEN block.

Code
AND
DB_CharacterCreationLevels(_Lvl)

DB stands for DataBase, and is a way for the game to store variables for retrieval at a later time. They have additional uses too, such as for checking whether something has already been stored in a database. In this case, because the DB is located after an "AND" and the _Lvl variable has already been defined, it is checking whether the value in _Lvl is within the DataBase with the name CharacterCreationLevels. If _Lvl wasn't previously defined, it would be outputting the first value in that DataBase into the _Lvl variable instead (ie retrieving a stored variable). DataBases can be created and used pretty much anywhere in Story scripting, and the type of data stored is determined when the first entry is entered.

Code
THEN
CharacterTeleportPartyToTriggerMovieLoadState(TRIGGER_CYS_Start_P1,"","MovieGameIntro");
ItemToCharacter(ITEM_CYS_SHGuide1, CHARACTER_Player1);
ItemToCharacter(ITEM_CYS_SHGuide2, CHARACTER_Player2);
CharacterAddGold(CHARACTER_Player1,200);
CharacterAddGold(CHARACTER_Player2,200);
DB_CYSDoTutorial(1);

The "THEN" keyword is what tells the game that if all of the previous IF-ANDs were true, then everything afterwards occurs. These are the actions that are performed in sequential order. Note that all of these end in semi-colons, while the IF-ANDs did not have semi-colons. The other thing to note here is the DataBase line "DB_CYSDoTutorial(1);". Because it is in a THEN action, this line is *creating* a database entry with the value 1.

A side note on Databases before continuing on, the other two uses for Databases can be in the IF section as well as an additional use in the THEN section. Here's a part of the script that uses a Database in the IF line-
Code
IF
DB_Companion(_Companion)
AND
CharacterIsFemale(_Companion,1)
THEN
DialogSetCharacterEvent("EVENT_player_is_female",_Companion,0);

In this case, this script portion triggers whenever a database entry is created for the Companion database with a single variable type.

The other use for databases is the NOT DB_Name(_Variable) in the THEN section, which deletes the specific database entry if it exists.

Now say that I want to give the players 9001 gold instead of the measly 200 gold when they arrive at the beach. I can modify the script to look like so-
Code
IF
CharacterCreationFinished(CHARACTER_NULL)
AND
CurrentLevel(_Lvl)
AND
DB_CharacterCreationLevels(_Lvl)
THEN
CharacterTeleportPartyToTriggerMovieLoadState(TRIGGER_CYS_Start_P1,"","MovieGameIntro");
ItemToCharacter(ITEM_CYS_SHGuide1, CHARACTER_Player1);
ItemToCharacter(ITEM_CYS_SHGuide2, CHARACTER_Player2);
CharacterAddGold(CHARACTER_Player1,9001);
CharacterAddGold(CHARACTER_Player2,9001);
DB_CYSDoTutorial(1);

This is a simple change; other changes can be easily made or added to this portion to change what happens upon arriving at Cyseal beach. You can even change where the party arrives at after character creation by pointing to a different Trigger with the first THEN action.

The final step before the changes are actually within the game is to "Generate Definitions and Build Story" which is under the File menu. The Definitions only need to be generated once, and the Story needs to be built anytime you want changes to stick. You'll notice that the various functions become color coded after the Definitions are built.
Each of the colors has a specific place they can be used:
Purple- IF statement only, these are events that can be responded to
Red- AND statements only. These are interim calculations/variable getters that can be used for various things.
Green- THEN statements only. These are all actions.
Black- Their use locations can vary.


Making your own Scripts-

Instead of modifying an existing script, you can also create your own to use in the main campaign. All told it's not too much different, since the syntax still must be followed and only certain commands can be used.

To create your own Script, just click the "New Script" button at the top left of the Story Editor. Enter a name for your script (with no spaces) and you'll have yourself an empty script.

I didn't note this above, but there are three main sections to a Script file. The INIT section (top), KB section (middle), and EXIT section (bottom). The INIT section is used primarily to create Database entries at the beginning of the game (upon New Game). The KB section houses all of the events that occur mid-game. I have never seen the EXIT section used, so I'm not sure what it is used for.

For this part of the tutorial I will be basing the examples off of my "Journey of the Lone Wolf" mod, which demonstrate several concepts.

First the example code in its entirety-
INIT Section-
Code
DB_LoneWolfAbility(CHARACTER_Player1,2,2);
DB_LoneWolfAbility(CHARACTER_Player1,3,2);
DB_LoneWolfAbility(CHARACTER_Player1,4,2);
DB_LoneWolfAbility(CHARACTER_Player1,5,2);
DB_LoneWolfAbility(CHARACTER_Player1,6,3);
DB_LoneWolfAbility(CHARACTER_Player1,7,3);
DB_LoneWolfAbility(CHARACTER_Player1,8,3);
DB_LoneWolfAbility(CHARACTER_Player1,9,3);
DB_LoneWolfAbility(CHARACTER_Player1,10,4);
DB_LoneWolfAbility(CHARACTER_Player1,11,4);
DB_LoneWolfAbility(CHARACTER_Player1,12,4);
DB_LoneWolfAbility(CHARACTER_Player1,13,4);
DB_LoneWolfAbility(CHARACTER_Player1,14,4);
DB_LoneWolfAbility(CHARACTER_Player1,15,4);
DB_LoneWolfAbility(CHARACTER_Player1,16,4);
DB_LoneWolfAbility(CHARACTER_Player1,17,4);
DB_LoneWolfAbility(CHARACTER_Player1,18,4);
DB_LoneWolfAbility(CHARACTER_Player1,19,4);
DB_LoneWolfAbility(CHARACTER_Player1,20,4);

DB_LoneWolfAttribute(CHARACTER_Player1,3,1);
DB_LoneWolfAttribute(CHARACTER_Player1,5,1);
DB_LoneWolfAttribute(CHARACTER_Player1,7,1);
DB_LoneWolfAttribute(CHARACTER_Player1,9,1);
DB_LoneWolfAttribute(CHARACTER_Player1,11,1);
DB_LoneWolfAttribute(CHARACTER_Player1,13,1);
DB_LoneWolfAttribute(CHARACTER_Player1,15,1);
DB_LoneWolfAttribute(CHARACTER_Player1,17,1);
DB_LoneWolfAttribute(CHARACTER_Player1,19,1);
DB_LoneWolfAttribute(CHARACTER_Player1,21,1);

DB_LoneWolfTalent(CHARACTER_Player1,5,1);
DB_LoneWolfTalent(CHARACTER_Player1,9,1);
DB_LoneWolfTalent(CHARACTER_Player1,13,1);
DB_LoneWolfTalent(CHARACTER_Player1,17,1);
DB_LoneWolfTalent(CHARACTER_Player1,21,1);


KB Section-
Code
PROC
ProcLoneWolfAbilities((CHARACTER)_Player)
AND
CharacterGetLevel(_Player, _charLevel)
AND
DB_LoneWolfAbility(_Player, _charLevel, _abilPoints)
THEN
CharacterAddAbilityPoint(_Player, _abilPoints);

PROC
ProcLoneWolfAttributes((CHARACTER)_Player)
AND
CharacterGetLevel(_Player, _charLevel)
AND
DB_LoneWolfAttribute(_Player,_charLevel,_attPoints)
THEN
CharacterAddAttributePoint(_Player, _attPoints);

PROC
ProcLoneWolfTalents((CHARACTER)_Player)
AND
CharacterGetLevel(_Player, _charLevel)
AND
DB_LoneWolfTalent(_Player, _charLevel, _talPoints)
THEN
CharacterAddTalentPoint(_Player, _talPoints);


IF
CharacterLeveledUp(_Player)
AND
_Player.isPlayer()
THEN
ProcLoneWolfAbilities(_Player);
ProcLoneWolfAttributes(_Player);
ProcLoneWolfTalents(_Player);

PROC
ProcCheckLoneWolfStart((CHARACTER)_Player)
AND
CharacterHasTalent(_Player,"LoneWolf",1)
THEN
CharacterAddTalentPoint(_Player,1);

PROC
ProcCheckLoneWolfStart((CHARACTER)_Player)
AND
NOT CharacterHasTalent(_Player,"LoneWolf",1)
THEN
CharacterAddTalent(_Player,"LoneWolf");

IF
CharacterCreationFinished(CHARACTER_NULL)
AND
CurrentLevel(_Lvl)
AND
DB_CharacterCreationLevels(_Lvl)
THEN
CharacterAddAttributePoint(CHARACTER_Player1,6);
CharacterAddAbilityPoint(CHARACTER_Player1,5);
CharacterAddTalentPoint(CHARACTER_Player1,2);
ProcCheckLoneWolfStart(CHARACTER_Player1);


As you can see, I incorporate various aspects into this piece of scripting. I will be walking through what this code does step by step, starting with what it does first-
Code
IF
CharacterCreationFinished(CHARACTER_NULL)
AND
CurrentLevel(_Lvl)
AND
DB_CharacterCreationLevels(_Lvl)
THEN
CharacterAddAttributePoint(CHARACTER_Player1,6);
CharacterAddAbilityPoint(CHARACTER_Player1,5);
CharacterAddTalentPoint(CHARACTER_Player1,2);
ProcCheckLoneWolfStart(CHARACTER_Player1);

The IF-ANDs are identical to the IF-ANDs located within the CYS_General file in the example from modifying an existing story script. This is taking advantage of the fact that each and every CharacterCreationFinished event is called sequentially, and is guaranteed to work at the same time that the CYS_General script does (because the ANDs aren't changed by the THENs). The CharacterAdd functions are predefined actions that I use to give additional Attribute/Ability/Talent points to Player 1 at the beginning of the game.

But what about the "ProcCheckLoneWolfStart(CHARACTER_Player1);" line? This looks like a function, but is not one of the predefined functions in any Story Editor function list. This is a function that I create elsewhere in the Script-

Code
PROC
ProcCheckLoneWolfStart((CHARACTER)_Player)
AND
CharacterHasTalent(_Player,"LoneWolf",1)
THEN
CharacterAddTalentPoint(_Player,1);

PROC
ProcCheckLoneWolfStart((CHARACTER)_Player)
AND
NOT CharacterHasTalent(_Player,"LoneWolf",1)
THEN
CharacterAddTalent(_Player,"LoneWolf");

PROCs are essentially user-defined functions. They are actions that can be called anywhere you want in other scripts, so long as they are defined within the same Story. Notice that I have two definitions with slightly different AND statements; this is also taking advantage of the fact that every event (like the PROC) is checked *sequentially*. In this case, the second block that adds Lone Wolf to the character will never occur before the first code block that is checking whether the character has Lone Wolf, so the first one will only ever trigger if the conditions are fulfilled prior to the PROC being called.

It's important to note the differences in the syntax for the PROC line itself-
Code
PROC
ProcCheckLoneWolfStart((CHARACTER)_Player)

PROC is like IF, except it's defining a function that is called. The name of the function can be anything really; the important part is what's inside the parentheses. The (CHARACTER)_Player is stating that the function is accepting any variable with the CHARACTER variable type, and storing that value in a variable called _Player to be used by the PROC. If I wanted the PROC to accept an integer, I could say ProcNumber((INTEGER)_Num). The Variable type is within parentheses and in all upper case letters, while the variable name is immediately following it (starting with an underscore).

With this information in mind, the rest of the code is fairly straightforward-
Code
IF
CharacterLeveledUp(_Player)
AND
_Player.isPlayer()
THEN
ProcLoneWolfAbilities(_Player);
ProcLoneWolfAttributes(_Player);
ProcLoneWolfTalents(_Player);

This block of code is triggered whenever a player levels up. I call three Proc functions in the THEN section as a result. These Procs are essentially the same, so I will only go into detail about one of them-

Code
PROC
ProcLoneWolfTalents((CHARACTER)_Player)
AND
CharacterGetLevel(_Player, _charLevel)
AND
DB_LoneWolfTalent(_Player, _charLevel, _talPoints)
THEN
CharacterAddTalentPoint(_Player, _talPoints);

There are a few important things to note in this PROC. First, a CHARACTER type variable called _Player is being passed to this PROC when called. Because _Player is defined, the CharacterGetLevel uses that _Player and stores their level in the _charLevel variable (integer) which is defined by this function.

Then comes the DataBase line. By the time that this is reached, _Player and _charLevel are already defined, while _talPoints is not. As such, the game checks the Database (defined in the INIT section) for any entry that uses both the _Player and _charLevel numbers, and outputs the third entry in the Database into the new variable. If Player 1 levels up to level 5, for example, the game would match it with this line: "DB_LoneWolfTalent(CHARACTER_Player1,5,1);" and output 1 to _talPoints. If Player 1 levels up to 6 instead, because there is no appropriate Database entry it will not do anything and the THEN actions will not occur.

As with any Story scripts, make sure to Generate Definitions at least once and Build Story after making any changes to get them to stick in-game. Much more can be done with scripts; these examples have just been to demonstrate the core concepts of IF/PROC/AND/THEN and Databases, which are used for everything in Story scripting. A list of pre-defined functions is handy to reference when Story scripting.