Contents
Back
Forward

12. Keyboard Control


12.1 Keyboard Control overview

From a technical point of view, Keyboard Control is not obtained using threads. However, since it uses a KeyboardCheck(...) function you can
see that the structure works exactly like the previous structures.
Keyboard structure, as the name implies, is used to interact with the CSPL user using the keyboard.
When a user press a key, Windows sends a message to the current active window to warn it of the keypress.
Since I'm not able to intercept messages directed towards ToT (Yes it can be done using hooks, but I just don't want to mess with them in this early version.), I've decided to catch only messages directed towards the CSPL program. So if a user wants to interact with CSPL using the keyboard, he must activate the CSPL window by clicking on it, and only when the CSPL window is active, can the user press the key.
How can the CSPL developer code keyboard control?
Well, I've created a function called KeyboardCheck (character keypressed) which is called each time a key is pressed with CSPL active and
the parameter keypressed is (as you can imagine) the key pressed by the user.
NOTE: By default, the "x" key is treated as a quit message by CSPL, so don't use it in your CSPL programs as it is reserved.



12.2 Keyboard cycle
Just a couple of lines to explain how the KeyboardCheck implementation should be written:

void KeyboardCheck(char keypressed)
{/*Add your Keyboard event here*/}

This is how KeyboardCheck appears as soon as a new project is started using CSPLCompanion.
In the previous section we've seen that KeyboardCheck is called when a key is pressed and the keypressed is stored in the keypress variable.
For example let's try to create a KeyboardCheck which answers to the pressing of the 'e' key, Let's also say that the program simply prints on the screen a messagebox saying "Hey, you've pressed 'e'!".

void KeyboardCheck(char keypressed)
{
if (keypressed=='e')   //if key pressed by the user is 'e'
  MessageCSPL("Hey, you've pressed 'e'!");  //print the MessageBox
}

NOTE: The character 'e' is different from the character 'E'. Thus if you want both upper and lower case 'e' to have the same effect, you'll have to modify the previous section. There are a lot of ways to do this, but the easiest is to change the if condition:
Instead of
if (keypressed=='e') we can type if (keypressed=='e' || keypressed=='E')



12.3 Example 11 : Artillery
Controlling the keyboard is very easy and I've explained all you need to know in the previous section. Although this example doesn't add anything new to your understanding of Keyboard control, nevertheless I think it's very interesting.
Here we will try to build a new kind of unit, the artillery unit.
I know, ToT already has artillery and howitzer units, but our artillery unit will definitely be more interesting. Imagine having an artillery unit which is able to create a shell unit (a missile) when the player presses a key. Obviously, to avoid players creating infinite shells in the same turn, we need to implement a rule. Let's say that each time an artillery unit "fires", it's health bar goes down. When it is under 50% (let's say), we cannot fire but we have to wait until the health bar goes over 50%. OK, this is exactly what we're going to implement. Let's begin with some assumptions: For artillery we will use the old artillery unit, as shells we will use cruise missiles, Artillery won't be able to fire under 50%, and each time artillery fires its health bar goes down by 1/3.

PHASE 0: CREATING A NEW PROJECT

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

PHASE 1: UNDERSTANDING WHAT WE NEED

The first thing a CSPL designer should ask is, "which thread do I need?"
In this situation we will try to use only Keyboard, forcing all work into the KeyboardCheck function. Since Keyboard isn't managed by a thread you don't have to set any flag to before using KeyboardCheck.

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, "The player has pressed the F(ire) key in CSPL window and an artillery unit is selected"
while BODY is "CSPL should create a new shell unit at the artillery unit's coordinates."

PHASE 4: CODING THE EVENT

When the user presses the fire key, CSPL calls KeyboardCheck with the character 'f' (or 'F') as parameter.
We should make sure the character is really 'f' or 'F', and then we must get the current unit and check its type.
If it is an artillery unit, then a new Shell unit must be created only if health is high enough (we'll use a separate function to create the new unit and check health)
Let's write a bit of code:
void KeyboardCheck(char keypressed)
{
   if (keypressed=='f' || keypressed=='F')
   {
     Unit current; //creates a new empty Unit
     UnitID(GetCurrentUnit(),&current); //fill Unit with current unit data
     if (current.Type==ARTILLERY)
     {
         FireShell(Unit* current);
     }
   }
}

Now we need to implement the FireShell function.
We know that our new unit properties will be:
pos = The map coordinate as the artillery unit
Veteran = False
Type = UNIT_CRUISE
Owner = The same of artillery unit
HealthLost = 0
Commodity = No sense (even if it must be equal to FuelUsed due to a bug of Civ2)
Orders = None
HomeCity = None
MoveLost = 0
FuelUsed=0
ID = Will be set by the CreateUnit library function so we don't care

Let's code this:
void FireShell(Unit* current)
{
   if (current->HealthLost < 10)
  {
     Unit Shell;
     Shell.pos.x=current.pos.x;
     Shell.pos.y=current.pos.y;
     Shell.pos.z=current.pos.z;
     Shell.Veteran=FALSE;
     Shell.Type=UNIT_CRUISE;
     Shell.Owner=current->Owner;
     Shell.HealthLost=0;
     Shell.Orders=NONE;
     Shell.HomeCity=NONE;
     Shell.MoveLost=0;
     Shell.FuelUsed=0;
     Shell.Commodity=Shell.FuelUsed; //Necessary because Commodity byte and FuelUsed byte are the same in Civ2

     CreateUnit(Shell);
     current->HealthLost+=7;
     ReWriteUnit(current,current->ID);
  }

}
Notice that since Artillery HP are max 20 (check ToT cheat menu: Edit Unit -> Set Hit Points) 50% is 10 while 33% is 7 (rounded up) and this explains the numbers I wrote in red.

PHASE 5: 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 don't need to activate any thread:

BYTE ACTIVITY_FLAG=0;

Then we need to write down the signature of user-defined functions:

void FireShell(Unit* current);

And we're finished with CSPLClient.h .

Editing CSPLClient.csp:
We have two things to do.
The first one is to write down the KeyboardCheck implementation:

void KeyboardCheck(char keypressed)
{
   if (keypressed=='f' || keypressed=='F')
   {
     Unit current; //creates a new empty Unit
     UnitID(GetCurrentUnit(),&current); //fill Unit with current unit data
     if (current.Type==ARTILLERY)
     {
         FireShell(Unit* current);
     }
   }
}

And now we need to write down the FireShell function:

void FireShell(Unit* current)
{
   if (current->HealthLost < 10)
  {
     Unit Shell;
     Shell.pos.x=current.pos.x;
     Shell.pos.y=current.pos.y;
     Shell.pos.z=current.pos.z;
     Shell.Veteran=FALSE;
     Shell.Type=UNIT_CRUISE;
     Shell.Owner=current->Owner;
     Shell.HealthLost=0;
     Shell.Orders=NONE;
     Shell.HomeCity=NONE;
     Shell.MoveLost=0;
     Shell.FuelUsed=0;
     Shell.Commodity=Shell.FuelUsed; //Necessary because Commodity byte and FuelUsed
                                     //byte are the same one in Civ2


     CreateUnit(Shell);
     current->HealthLost+=7;
     ReWriteUnit(current,current->ID);
  }

}

And now we're finished with cspl.csp

PHASE 6: COMPILING AND LINKING THE SOURCE CODE

At this point save the CSPLClient.h and CSPLClient.csp files and use CSPLCompanion to compile and link Artillery. This will create a CSPLClient executable in the Artillery directory.
To test this example you should begin a game with an artillery unit, start CSPLClient.exe, select the artillery unit, and press the "f" key. As a result, you should see the artillery health bar goes down by 1/3 while a new cruise missile will be created at the same coordinate as the artillery unit.
Note: Probably you won't see it immediately, but under the Artillery unit your new cruise missile (shell) will be ready to be fired. You can select it by pressing 'w' (making Artillery wait), and ToT will then activate the Cruise Missile. (It would be nice if the Cruise missile appeared first, but this is a CSPL problem. Remember we're writing in ToT memory and directly bypassing all the controls, so obviously some annoyances are inevitable. I think this "problem" can be solved but I wanted to put out this version ASAP so you'll have to wait for the next update.)




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