Contents
Back
Forward

9. Global Thread


9.1 Global Thread overview

In the last chapter we've seen how Wonders Thread works.
Here we'll analyze another thread in greater depth, the Global Thread.
The Global thread continuously scans ToT memory searching for Global information about the game, while simultaneously executing the GlbCheck function.
The CSPL designer protects his global-related events by just changing the GlbCheck function, leaving untouched the main structure of Global Thread.
There are a lot of differences between Global Thread and the other Threads in CSPL.
The exact source-code of Global Thread cannot be reported because it is very complex and uses some of the inner (hidden) CSPL features.
Anyway from an external view the Global Thread works exactly as other Threads, so don't be too concerned.


9.2 Global functions
In CSPL I've defined several functions to manage global variables:
  • TurnPassed : Returns the number of turns passed from the beginning.
  • GetDifficulty : Get game difficulty level.
  • SetDifficulty : Set game difficulty level.
  • GetBarbarianActivity : Get barbarian activity level currently selected.
  • SetBarbarianActivity : Set barbarian activity level.
  • GetCurrentCiv : Get Civilization which is currently moving.
  • SetCurrentCiv : Set Civilization which is currently moving.
  • GetCurrentUnit : Get Unit which is currently selected.
  • SetCurrentUnit : Set Unit which is currently selected.
  • GetCivInPlay : Returns a byte representing Civs currently active.
  • CreateCiv : Adds a particular civilization to the game.
  • SetHumanPlayer : Set the human player.
  • GetHumanPlayer : Get the human player currently selected.
  • ReadCurrentPollution : Get current pollution level.
  • WriteCurrentPollution : Set new pollution level.
  • PeaceTurns : Returns the number of peace turns.
WORD TurnPassed()
This reports the number of turns passed since the game started. It can be used as a CSPL replacement for the macro-language TURN turn=x command.

byte GetDifficulty()
This function returns the difficulty level chosen on game start. I've defined the following constants for address difficulty level:

DIFFICULTY LEVEL CONSTANT DEFINED
Deity Level DIF_DEITY
Emperor Level DIF_EMPEROR
King Level DIF_KING
Prince Level DIF_PRINCE
Warlord Level DIF_WARLORD
Chieftain Level DIF_CHIEFTAIN

byte SetDifficulty(int Level)
This function, as the name itself says, is the opposite of the previous function. It sets the difficulty level in game to Level passed as parameter.

byte GetBarbarianActivity()
This functions returns the level of Barbarian activity, which ranges from "Villages Only" to "Raging Hordes".
Again, I've defined some constants to help designers:

BARBARIAN ACTIVITY LEVEL CONSTANT DEFINED
Raging Hordes BA_RAGING
Restless Tribes BA_RESTLESS
Roving Bands BA_ROVING
Villages Only BA_VILLAGES

byte SetBarbarianActivity(byte Activity)
Again, this function is the opposite of the previous function, it sets the difficulty level in the game to Level passed as parameter.

BOOL TestCiv(int ID)
this functions takes as parameter a Civ ID (we saw them here) and returns TRUE if that Civ is still in the game, FALSE otherwise.

byte GetCurrentCiv()
This function returns the ID value of the civ currently playing it's turn.

WORD GetCurrentUnit()
This function returns the ID value of the Unit currently selected on screen.
Notice that if no unit is currently selected this function returns 0xFFFF.

byte GetCivsInPlay()
Returns a byte showing (when read as a binary number) all civs currently in game. This is exactly the byte 46 in section 2 of Allard's document:
enumeration of all civs still in play [binary]
eg. 1001 1101 means: barbarians alive, civ 3 alive, civ 4, 5, and 7 alive.
Nothing else to add.

void CreateCiv(int ID)
This function allows the designer to add a new civ to the game (WARNING: you should manually give some units or cities to the new civ in order to see it). Just call CreateCiv passing as parameter ID the ID of the civ you want to add.
In case selected civ is already present in the game this function don't do anything.
byte GetHumanPlayer()
Returns a byte showing (when read as a binary number) which civs are currently human controlled.
This is exactly the byte 47 in section 2 of Allard's doc:
human player played (can be more than one) [binary]
toggling this byte is great and allows Hotseat mode in FW!!!

void SetHumanPlayer(byte Civs)
This function is the opposite of the previous function, and can set human player while the game is running.
WARNING: It takes as parameter NOT the ID of civs to be human controlled, but the humancivs byte as described in the previous function. So, if you just want to add a human civ you should do the following:
  1. Read the current humancivs byte (byte HumanCiv=GetHumanPlayer();)
  2. Set ID bit in HumanCiv byte (HumanCiv=HumanCiv | ID;)
  3. Finally, set the new HumanCiv byte (SetHumanPlayer(HumanCiv);)
(If it's not clear what is the | operator or why I used it there, then you should read Appendix A )

If we want to set the human controlled civ to a particular one (with id ID) we should do the following:
  1. Calculate ID Civ position in humancivs byte (byte HumanCiv=2^ID;)
  2. Set the new HumanCiv byte (SetHumanPlayer(HumanCiv);)
(If it's not clear what is the 2 ^ ID equation or why I used it there, then you should read Appendix A )

byte ReadCurrentPollution()
This function returns the current world pollution level.
Let's quote Allard's documentation on this value:
7F is maximum, and will certainly cause global
temperature rising at the end of the turn. A value of 80 till FF is a negative
amount (still shows the icon for global rising), but it will be reset to 00 at the
end of the turn.
Here obviously Allard talks about hex values, this means 7F is 127 decimal while FF is 255 decimal.

void WriteCurrentPollution(byte Level)
Again, this function is the opposite of the previous function, it sets the pollution level in game to Level passed as parameter.

byte PeaceTurns()
Returns the number of (global) peace turns in current game



9.3 Example 8 : SwitchRuler
In this example we will try to create an event which switches the player civ after 10 turns.
This effect can be used to simulate civil wars in which the player now controls a new faction, or you can use this effect in multi-protagonist scenarios (play the first half with one civ, the second half with another), and a lot of other interesting effects (such as scenarios designed with companies instead of civs, where the player is hired first by one company and then by another, etc). In the following example let's have the player start with the white civ, and at turn 10 the player controlled civ will become the green one.

PHASE 0: CREATING A NEW PROJECT

As we've learned in the previous chapters the first step towards CSPL compilation is the project creation (usually done with CSPLCompanion).
Start by creating a new project called SwitchRuler.

PHASE 1: UNDERSTANDING WHAT WE NEED

The first thing a CSPL designer should think is : "which thread do I need?"
Our choice is simple. Since we want to play with human civs, only GlobalThread is required.

PHASE 2: DESIGNING THE EVENT

The next thing we have to do is to design the "skeleton" of our event.
From the first chapter we know that each event is made of HEAD (Trigger Statement) and BODY (Action Statement).
In this case HEAD is "Current turn is 10."
while BODY is "Change human controlled civ."
WARNING: We should find a way to make sure this change happens only once.

PHASE 3: CODING THE EVENT

We should check continuously for current turn value, and when it equals to 10, we simply call SetHumanPlayer to change the human controlled civ. All should be done inside the GlbCheck function. It's time to write a bit of code:

WORD Turn=TurnPassed();

if (Turn==10)
{
  SetHumanPlayer(pwr(CIV_GREEN));//If turn is equal to 10 then set human player to green
  Refresh();
}


Notice a couple of things:

pwr(CIV_GREEN): this is a function I implemented to calculate in a fast way (without including math lib) 2^CIV_GREEN.
Why does SetHumanPlayer need 2^CIV_GREEN as parameter? Well, because SetHumanPlayer is very simple, it writes the parameter in the ToT memory byte which controls HumanPlayer (check Allards documentation for more info).

Refresh(): This is a function which forces ToT to repaint the screen. It's useful in a lot of situations because when ToT repaints the screen it also checks a lot of global variables and redesigns the screen according to them. In our example, without the Refresh call, ruler switching will still happen but the player won't notice it until he tries to move a unit or something similar.

We should also (though it's not necessary) warn the player of what has happened, using the MessageCSPL function to obtain this result:

WORD Turn=TurnPassed();

if (Turn==10)
{
  MessageCSPL("The White ruler has been driven away by a group of armed rebels,\nGreen Civ has been very happy to receive Player as their leader for skill showed ruling whites,\n Player accepted gratefully and proclaimed that his vengeance against whites will be terrible");
  SetHumanPlayer(pwr(CIV_GREEN));//If turn is equal to 10 then set human player to green
Refresh();
}

And that's all. Quite simple, isn't it?
But as I said before, if you try to compile the source, on turn 10 CSPL program goes to loop, it shows you the messagebox and, when you click ok, it shows you the box again (this is because the turn is still 10 until the player presses the EndTurn key).
How to avoid this?
It's easy, we just need to add a global boolean variable. When the game starts this flag will be false and it will be set to true ONLY when the messagebox is displayed (obviously the messagebox will be displayed ONLY if the flag is false). So, let's write the code:

In Csplclient.h we should define the global Flag variable:

BOOL Flag=FALSE;

Now we should modify the code of GlbCheck:

WORD Turn=TurnPassed();

if (Turn==10 && !Flag)
   SetHumanPlayer(pwr(CIV_GREEN));//If turn is equal to 10 then set human player to green
   MessageCSPL("The White ruler has been driven away by a group of armed rebels,\nGreen Civ has been very happy to receive Player as their leader for skill showed ruling whites,\n Player accepted gratefully and proclaimed that his vengeance against whites will be terrible");
   Flag=TRUE;
   Refresh();

PHASE 4: MERGING THE RESULTING SOURCE CODE

Now it's time to merge all the source code we've written:

Editing CSPLClient.h:
In CSPLClient.h we need to activate the Global thread:

BYTE ACTIVITY_FLAG=ACTIVATE_GLOBAL;
Flag=FALSE;


And we're finished with CSPLClient.h .

Editing CSPLClient.csp:
The only thing we need to do here is edit the GlobalCheck function as described in the previous phase:

WORD Turn=TurnPassed();

if (Turn==10 && !Flag)
{
SetHumanPlayer(pwr(CIV_GREEN));//If turn is equal to 10 then set human player to green
MessageCSPL("The White ruler has been driven away by a group of armed rebels,\nGreen Civ has been very happy to receive Player as their leader for skill showed ruling whites,\n Player accepted gratefully and proclaimed that his vengeance against whites will be terrible");
Flag=TRUE;
Refresh();
}

PHASE 5: COMPILING AND LINKING THE SOURCE CODE

At this point save the CSPLClient.h and CSPLClient.csp files and use CSPLCompanion to compile and link SwitchRuler. This will create a CSPLClient executable in the SwitchRuler directory.
To test this example you should start a game with human-controlled white civ (although since we never wrote that human start civ should be white, we can also start with blue, orange, etc. But the MessageCSPL text will seem strange). Start CSPLClient.exe and play your game. Iif all goes well, after ten turns of play you should receive a message informing you of your "Green civ election". From there onwards you should keep playing as the ruler of the green civ.
Notice that this example is far from a finished CSPL event. You should also change the civ information so the green ruler's name reflects your "election", choose a new name for the white ruler, and maybe even change the attitude between your new country and the old one.




Contents / Introduction
Chapter I / Chapter II / Chapter III / Chapter IV / Chapter V / Chapter VI / Chapter VII
Chapter VIII / Chapter IX / Chapter X / Chapter XI / Chapter XII / Chapter XIII
Appendix A / Appendix B / Appendix C