Contents Back Forward |
3. CSPL, A first analysis |
3.1 First C++ principles Here I'm going to add some information on the C language. Notice that I've copied it from web tutorials and that it's far from complete. Still, you MUST read tutorials to learn a bit of C, so I placed here some information on arguments just to underline that they're really important in CSPL programming. These arguments are:
3.1.1 Global Variables Local variables are declared within the body of a function, and can only be used within that function. This is usually no problem, since when another function is called, all required data is passed to it as arguments. But what happens if we want to realize a variable which is available to all program functions? We need to declare globally our variable: A global variable declaration looks normal, but is located outside any of the program's functions. I suggest you place your global variables definitions in your header file. The variable is not declared again in the body of the functions which access it. 3.1.2 itoa C function used to convert integer to string. You'll find this function very useful to show game data in a messagebox, so let's examine it in depth. (Read information about header files and functions in tutorials) This function is defined in header file stdlib.h its signature is char * itoa ( int value, char * buffer, int radix );The stdlib reference says that itoa ("Integer TO Ascii" in case you're trying to guess what that means): Convert integer to string.its parameters are: valueValue to be represented as a string.bufferBuffer is where to store the resulting string.radixNumeral radix in which value has to be represented, between 2 and 36.Return Value.A pointer to the string.Portability. Not defined in ANSI-C. Supported by some compilers (All win32 compilers as far as I know, so don't worry). Example. /* itoa example */ #include < stdio.h > #include < stdlib.h > main () {    int i;    char buffer [33];    printf ("Enter a number: ");    scanf ("%d",&i);    itoa (i,buffer,10);    printf ("decimal: %s\n",buffer);    itoa (i,buffer,16);    printf ("hexadecimal: %s\n",buffer);    itoa (i,buffer,2);    printf ("binary: %s\n",buffer);    return 0; } Output: Enter a number: 1750 decimal: 1750 hexadecimal: 6d6 binary: 11011010110 3.1.3 \n character \n is a special character which is translated into a new line code. Let's explain this concept with an example: Let's take the following line: printf("Hello World\n"); It is pretty self explanatory. It prints Hello World on the screen. (printf is a std function which prints his first parameter on screen) The \n you will notice doesn't show up on the screen. \n is translated into a new line code. What if you want to print "\n" on screen if \n means new line on screen? Just use a double \ to print a \ character. The code to do that would be: printf("In C \\n means new line"); 3.1.4 #include The preprocessor directive #include is an instruction to read in the entire contents of another file at that point. This is generally used to read in header files for library functions. Header files contain details of functions and types used within the library. They must be included before the program can make use of the library functions. Library header file names are enclosed in angle brackets, < >. These tell the preprocessor to look for the header file in the standard location for library definitions. This is /usr/include for most UNIX systems. For example #include < stdio.h > Another use for #include for the programmer is where multi-file programs are being written. Certain information is required at the beginning of each program file. This can be put into a file called globals.h and included in each program file. Local header file names are usually enclosed by double quotes, " ". It is conventional to give header files a name which ends in .h to distinguish them from other types of file. Our globals.h file would be included by the following line. #include "globals.h" 3.1.5 ExitProcess() ExitProcess is a standard windows function which simply ends the process (terminate the program). It takes one parameter which represents the Exit Status of the process. Nothing particularly interesting, but it's good to know how to end our programs ;) Below I'll report what Microsoft's API reference reports about ExitProcess function: The ExitProcess function ends a process and all its threads. | 3.2 Analyzing templates As seen in Chapter I a CSPL project is divided in several source files; these files have a well defined structure and we will try to show it in this section. PHASE 1: CREATING TEMPLATESThe first thing we need to do is to create a new project. CSPLCompanion will automate this process for us by creating all the templates we need to work. Open a DOS Prompt and change directory to CSPL\Tools subdirectory (ie: if you installed CSPL in C:\CSPL just write "cd c:\CSPL\tools" and return). At this point launch CSPLCompanion using the new project name as parameter (ie: type "CSPLCompanion Example1" and return)If all goes well, a screen similar to the following should pop-up (instead of ".\WorkSpace" you should have "Example1"): ![]() At this point press the A key; CSPLCompanion will create a directory named Example1 (under projects subdirectory) and will put all needed template files in it. Take a look at Example1 directory and you'll find 3 files: * CSPLClient.h * CSPLClient.cpp * CSPL.res (contains pre-compiled graphic information; it shouldn't be changed by the designer) PHASE 2: ANALYZING CSPLClient.h fileIn green we have comments (ignored by the compiler) while in blue we have commands (code read by the compiler):/************************************************************************************************************* SCENARIO NAME: <Place Scenario name here> VERSION: <Put the version number (if any) here> DATE: <Place release date here> AUTHOR: <Place author name here> E-MAIL: <Place author e-mail here> MISC: <Place miscellaneous information here> *************************************************************************************************************/ /*********************************************************************************************************** Offset to Map section in memory: DON'T CHANGE THESE DATA IF YOU'RE NOT SURE! (Useless if map thread is not active) ***********************************************************************************************************/ DWORD START_ADDRESS=0x1a50000; DWORD DIM_ADDRESS=0x60000; DWORD DimPattern=12; /*********************************************************************************************************** String to scn filename (Useless if map thread is not active) ***********************************************************************************************************/ LPCTSTR SCNFileName=".//example.scn"; /*********************************************************************************************************** Thread bit mask: By default no thread is active ***********************************************************************************************************/ BYTE ACTIVITY_FLAG=0; /*********************************************************************************************************** Miscellaneous information TitleCSPL:Title on the top bar of CSPL program (For Example: "CSPL - Red Front") AuthorString:Just a string of information about the scenario (it will pop up in the ABOUT Box) ***********************************************************************************************************/ LPCTSTR TitleCSPL="CSPL - "; LPCTSTR AuthorString="Place scenario information here"; /*********************************************************************************************************** Here we have data for sizing CSPL Main Window XWindow,YWindow:Coords of top-left corner of CSPL Window Width:Width of CSPL Window Height:Height of CSPL Window ***********************************************************************************************************/ int XWindow=600;int YWindow=10;int Width=400;int Height=50; /*********************************************************************************************************** Define here other include information ***********************************************************************************************************/ Let's take another look at the CSPLClient.h template, but this time we'll examine each section separately: /************************************************************************************************************* SCENARIO NAME: <Place Scenario name here> VERSION: <Put the version number (if any) here> DATE: <Place release date here> AUTHOR: <Place author name here> E-MAIL: <Place author e-mail here> MISC: <Place miscellaneous information here> *************************************************************************************************************/ This first section is entirely commented out and gives information about scenario name, author, etc. It helps designers to manage different projects and to publish the source-code. /*********************************************************************************************************** Offset to Map section in memory: DON'T CHANGE THESE DATA IF YOU'RE NOT SURE! (Useless if map thread is not active) ***********************************************************************************************************/ DWORD START_ADDRESS=0x1a50000; DWORD DIM_ADDRESS=0x60000; DWORD DimPattern=12; /*********************************************************************************************************** String to scn filename (Useless if map thread is not active) ***********************************************************************************************************/ LPCTSTR SCNFileName=".//example.scn"; These two sections are very complicated to explain. Since this is an introductory chapter the only thing you must understand is that: Both sections are useless if map thread is not used by your CSPL program; The second section (SCNFileName) contains the path to the SCN file used with CSPL program. Even if the map thread is active, standard values in the first section will work smoothly. Usually, you won't need to change the first section, and the second section should be changed only if Map thread is used. /*********************************************************************************************************** Thread bit mask: By default no thread is active ***********************************************************************************************************/ BYTE ACTIVITY_FLAG=0; This byte (ACTIVITY_FLAG) controls threads; it is set to 0 by default and this means that no thread will be active. possible values are: ACTIVATE_UNIT (Turns on unit thread) ACTIVATE_CITY (Turns on cities thread) ACTIVATE_CIV (Turns on civs thread) ACTIVATE_WONDER (Turns on wonder thread) ACTIVATE_GLOBAL (Turns on global variables thread) ACTIVATE_MAP (Turns on map thread) ACTIVATE_ATTACK (Turns on attack thread) And all possible combinations of these values with "|" operator: so, for example, if I need to use Unit thread AND City thread I'll write "BYTE ACTIVITY_FLAG=ACTIVATE_UNIT | ACTIVATE_CITY;" instead of "BYTE ACTIVITY_FLAG=0;" /*********************************************************************************************************** Miscellaneous information TitleCSPL:Title on the top bar of CSPL program (For Example: "CSPL - Red Front") AuthorString:Just a string of information about the scenario (it will pop up in the ABOUT Box) ***********************************************************************************************************/ LPCTSTR TitleCSPL="CSPL - "; LPCTSTR AuthorString="Place scenario information here"; In this section you'll place information about CSPL program and its author. TitleCSPL is a string used by CSPL as the title of main CSPL windows while AuthorString is just a string which shows up in the CSPL About Box. In the following images you can see a CSPL program with TitleCSPL of "CSPL - RedFront V3.0" and the AboutBox with AuthorString of "In Civ2 it is a masterpiece.\nImagine it in Tot with CSPL...\nPlease Nemo, read this box" (Remember that "\n" means new line in C/C++) ![]() /*********************************************************************************************************** Here we have data for sizing CSPL Main Window XWindow,YWindow:Coords of top-left corner of CSPL Window Width:Width of CSPL Window Height:Height of CSPL Window ***********************************************************************************************************/ int XWindow=600;int YWindow=10;int Width=400;int Height=50; This section simply tells CSPL how to draw its main window. The comment explains the meaning of variables; standard values draw a thin control bar (Height << Width) in the top-left corner of desktop (XWindow >> 0, YWindow > 0). PHASE 3: ANALYZING CSPLClient.csp fileIn the following I'll cut some pieces of source from CSPLClient.csp. This is not important to understand how CSPL works and won't need to be changed./************************************************************************************************************* SCENARIO NAME: <Place Scenario name here> VERSION: <Put the version number (if any) here> DATE: <Place release date here> AUTHOR: <Place author name here> E-MAIL: <Place author e-mail here> MISC: <Place miscellaneous information here> *************************************************************************************************************/ #include "cspl.h" #include "csplclient.h" #include <stdlib.h> //Useful to import itoa standard C function : it converts numbers in strings allowing //the programmer to show Civ data in a messagebox /************************************************************************************************************* THREAD CONTROL ROUTINES : PLACE YOUR CODE IN THE FOLLOWING FUNCTIONS *************************************************************************************************************/ void UnitCheck(Unit Prova) { //Place your code for Unit Thread here } void CityCheck(City Prova) { //Place your code for City Thread here } void WonderCheck() { //Place your code for Wonder Thread here } void MapCheck() { //Place your code for Map Thread here } void GlbCheck(Global Global1) { //Place your code for Global Thread here } void CivCheck(Civ Prova) { //Place your code for Civ Thread here } void AttackCheck(Unit* Attacker,Unit* Attacked,BOOL Winner) { //Place your code for Attack Thread here } void KeyboardCheck(char keypressed) { //Place your code for Keyboard control here } /************************************************************************************************************* CSPL CLIENT TEMPLATE : DON'T TOUCH THIS *************************************************************************************************************/ int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPTSTR lpCmdLine,int nCmdShow) {...} LRESULT CALLBACK WndProc(HWND hWnd,UINT imessage, WPARAM wParam,LPARAM lParam) {...} BOOL MessageCSPL(LPCSTR Testo) {...} int RequestCSPL(LPCSTR Testo) {...} void AboutBox() {...} void AboutScn() {...} Let's take another look at the CSPLClient.csp template, but this time we'll examine each section separately: /************************************************************************************************************* SCENARIO NAME: <Place Scenario name here> VERSION: <Put the version number (if any) here> DATE: <Place release date here> AUTHOR: <Place author name here> E-MAIL: <Place author e-mail here> MISC: <Place miscellaneous information here> *************************************************************************************************************/ Again, this first section is the same as that found in the CSPLClient.h template. #include "cspl.h" #include "csplclient.h" #include <stdlib.h> //Useful to import itoa standard C function : it converts numbers in strings allowing //the programmer to show Civ data in a messagebox This is the include section. By default it includes cspl.h (the library), csplclient.h (the file analyzed in phase 2) and the standard C/C++ library called stdlib.h (it's useful because it defines a function called itoa which converts numbers into the corresponding character set. We'll see an example ahead.) If needed the user can add it's include files here. /************************************************************************************************************* THREAD CONTROL ROUTINES : PLACE YOUR CODE IN THE FOLLOWING FUNCTIONS *************************************************************************************************************/ Here the designer places all the code. Each thread has a corresponding function: If the designer wants to define a particular event which acts on Units, he should redefine Unit function and so on. void UnitCheck(Unit Prova) { //Place your code for Unit Thread here } This function is used to control Unit Thread. Here's how it works: UnitThread continuously scans Unit List in memory; for each unit in unit list Thread executes UnitCheck. void CityCheck(City Prova) { //Place your code for City Thread here } This function is used to control City Thread. Here's how it works: CityThread continuously scans cities List in memory; for each city in cities list Thread executes CityCheck. void WonderCheck() { //Place your code for Wonder Thread here } This function is used to control Wonders Thread. Here's how it works: WonderThread doesn't look in any list, it just executes WonderCheck continuously (This also means that there's no technical reason to write in WonderCheck wonder-only code). void MapCheck() { //Place your code for Map Thread here } This function is used to control Map Thread. Here's how it works: MapThread doesn't look in any list, it just executes MapCheck continuously (This also means that there's no technical reason to write in MapCheck map-only code). Anyway, for technical reasons I suggest you use MapThread only if it's really necessary. void GlbCheck(Global Global1) { //Place your code for Global Thread here } This function is used to control Global Thread. Here's how it works: Global Thread continuously updates Global Data and calls GlbCheck on updated Global data void CivCheck(Civ Prova) { //Place your code for Civ Thread here } This function is used to control Civilizations Thread. Here's how it works: CivThread continuously scan Civilizations List in memory; for each civ in game Thread executes CivCheck. void AttackCheck(Unit* Attacker,Unit* Attacked,BOOL Winner) { //Place your code for Attack Thread here } This function is used to control Attack Thread. Here's how it works: AttackThread continuously scan Civilization memory for battles; for each battle in game Thread executes AttackCheck. void KeyboardCheck(char keypressed) { //Place your code for Keyboard control here } This function is used to get keypresses of user. Here's how it works: When the user presses a key in CSPL main window, the key pressed is stored and KeyboardCheck is executed /************************************************************************************************************* CSPL CLIENT TEMPLATE : DON'T TOUCH THIS *************************************************************************************************************/ As written explicitly the code following this comment shouldn't be changed (at least if you're not sure what you're doing) The functions defined in this section are just main CSPL routine and graphics pre-made functions; we'll see them in the next section. |
3.3 Example 1: Nothing Now you should understand each section of the CSPLClient source files, and it's time to realize your first CSPL program. Actually you should have three template files in subdirectory Example1 created by CSPLCompanion; these files are fully functional (even if they do nothing because ACTIVITY_FLAG is set to 0. If you don't remember what ACTIVITY_FLAG is, go back and read Phase 2) Passing from source-code to executable file is a complex task which needs the execution of two distinct phases: compiling and linking. Fortunately, if well configured, CSPLCompanion should be able to help us in this task. Recover DOS Prompt (remember to change directory to CSPL\Tools dir) and launch "CSPLCompanion Example1". If all goes well, a screen similar to the following should pop-up: ![]() At this time press the B key. If CSPLCompanion is properly configured then it should start the compiling phase and the linking phase, warning you if an error arises during one of these phases. When CSPLCompanion ends its work, you should find a lot of new files in the Example1 directory (they were used by compiler and linker as intermediate files but now you can delete them safely), but the most interesting is CSPLClient.exe. It is your first CSPL program. Let's make a test: Double click on CSPLClient.exe and see what happens: If all goes well, you should see a CSPL MessageBox in which CSPL warns you that no TOT window has been found in the system, so CSPL will exit. This happens because CSPL needs to have ToT loaded in memory, and so it warns you that it can't work. ![]() Open ToT (it's not necessary to start a game, just launch ToT) and start the CSPLClient.exe This time CSPL should start without any problem. You can select the AboutScenario and AboutCSPL from the main menu or exit from CSPL. You can't make anything else since all threads are deactivated but as you can see the system is fully functional. ![]() |
3.4 Using GUI pre-made functions As we saw in section 3.2, the last section of CSPLClient.csp template contains some pre-made graphics functions. Let's examine them in detail: /************************************************************************************************************* CSPL CLIENT TEMPLATE : DON'T TOUCH THIS *************************************************************************************************************/ BOOL MessageCSPL(LPCSTR Text) {...} This function replaces the windows standard MessageBox. It draws a window with a single OK button, the standard CSPL icon and as caption the Text string passed as parameter. In the following image you'll see a MessageBox created using the MessageCSPL function. The code used to create that Box is shown in red: ![]() MessageCSPL("This is a MessageCSPL\nLeft: you can see CSPL icon\n Down: you can see the OK button"); int RequestCSPL(LPCSTR Text) {...} This function is very similar to MessageCSPL. The difference here is that there is no OK button but there are two YES/NO buttons: ![]() RequestCSPL("This is a RequestCSPL\nDown: you can see YES/NO Buttons"); RequestCSPL returns a value. It returns YES_BTN if user selects YES, NO_BTN if user select NO. This can be useful to interact with the scenario player. void AboutBox() {...} This function simply draws the AboutCSPL box. It is standard and should not be changed. ![]() AboutBox(); void AboutScn() {...} This function simply draws the AboutSCN box. It is standard and should not be changed (Although you will change its contents by editing AuthorString String in CSPLClient.h file). ![]() AboutScn();[AuthorString="AboutScn() Example"] |
3.5 Example 2: Hello World! In every programming language book I've read one of the first program example is a useless one which simply prints the string "Hello World!" on screen. It's time to see the CSPL equivalent of this simple program. Due to the specific structure of CSPL we need to face two problems: 1) We need to find one thread in which to write our code. Since we just want to print a message which is not related to civ2 units or cities or wonders, any will do (let's use Unit Thread). 2) Since CSPL threads run continuously we need some instruction to end the program when the message is shown (Windows API function ExitProcess is exactly what we need). PHASE 1: CREATING NEW PROJECTCreate a new project called Example2 with CSPLCompanion (If you still don't remember, this is the last time I explain you the procedure: Open a DOS Prompt, change directory to CSPL\Tools dir, type "CSPLCompanion Example2", and hit return)PHASE 2: ACTIVATE UNIT THREADSince we've decided to use UnitThread we need to activate it by changing ACTIVITY_FLAG:Open CSPLClient.h and set ACTIVITY_FLAG to ACTIVATE_UNIT; this means change row BYTE ACTIVITY_FLAG=0; to BYTE ACTIVITY_FLAG=ACTIVATE_UNIT; PHASE 3: REWRITING UNITCHECK ROUTINEWe need to modify the routine UnitCheck to show "Hello World!" string and to exit after this. As I said before, Windows gives us a function called ExitProcess which terminates the process currently active. ExitProcess also gets one parameter if we want to set exit-status.For example: ExitProcess(-1); terminates the process exiting with exit-code -1. Our HelloWorld procedure should: 1) Write "Hello World!" string. 2) Exit Process. The first task can be obtained with a single call to MessageCSPL() procedure: MessageCSPL("Hello World!"); The second task can be obtained with a single call to ExitProcess() procedure: ExitProcess(0); Now that we have the code of our procedure, it's time to put it into the UnitCheck routine: Open CSPLClient.csp in Example2 directory and change the UnitCheck routine. It was: void UnitCheck(Unit Prova) { //Place your code for Unit Thread here } And became: void UnitCheck(Unit Prova) { MessageCSPL("Hello World!"); ExitProcess(0); } PHASE 4: COMPILING AND LINKINGNow you should compile and link your source code, save what you've done so far (CSPLClient.csp & CSPLClient.h)and launch CSPLCompanion with Example2 as parameter (Remember: DOS Prompt, change directory to CSPL\Tools, type "CSPLCompanion Example2" and then return), select option B (Compile and link a previously written CSPLclient program) and CSPLCompanion should compile and link your source code. PHASE 5: TESTING EXECUTABLELook at the Example2 directory: CSPLCompanion should have created CSPLClient.exe, the executable created using previous source code files. Launch ToT (CSPL programs won't start if ToT is not loaded) and launch CSPLClient.exe.You should see NO message box, so what is the problem? Why doesn't a box containing "Hello World!" text pop up? Well, the problem is that we decided to use Unit Thread to execute our code. Therefore since ToT is on the intro screen there is NO unit list to scan in ToT memory, so CSPL doesn't find a unit and doesn't execute UnitCheck. So, stop the CSPLClient executable, start a new game with ToT (it's not important the type of game, you just need to start a game to have a units list in memory), and restart CSPLClient executable. ![]() This time the message box should pop-up very quickly. Just press the OK button and the CSPLClient program should terminate. Congratulations! You just wrote your second CSPL program. |
3.6 Example 3: RequestCSPL Example In the previous section we learned how to use MessageCSPL in a program. Now let's see how to use RequestCSPL. RequestCSPL asks the user to make a choice by pressing the YES button or NO button. In this example we want to show a messagebox with the message "Should I terminate CSPL?". If the user presses the YES button CSPL terminates, while if the user selects NO, CSPL continues to work. Again, we have to face several problems due to the particular structure of CSPL: 1) As with the first example, any particular thread will do, so again we'll choose UnitThread. 2) Since CSPL threads run continuously we need to ensure ourselves that the RequestCSPL messagebox will be shown only once. (The way to solve this is pretty simple and general: The use of a flag, which is a variable that remembers if MessageBox has been shown or not) First of all we need to activate the Unit Thread, setting the ACTIVITY_FLAG to ACTIVATE_UNIT in CSPLClient.h Then we need one boolean variable to keep the return value of the RequestCSPL function (remember, YES_BTN if user pressed yes else NO_BTN) Then we need another boolean variable to be used as a flag (Note: Flag must be global. This means it should be declared outside of all routines. I usually put global variables in .h source file) So, up to now we have: BOOL Answer=FALSE; BOOL Flag=FALSE; //Should be declared as global! Now we need to call RequestCSPL: Answer=RequestCSPL("Should I terminate CSPL?"); At this point the user had chosen YES or NO and Answer keeps the user choice. Let's use an if statement (see tutorial) to keep track of the user's decision: if (Answer==TRUE) ExitProcess(0); Now, since we never used the Flag variable, when the CSPL program runs it will execute UnitCheck, UnitCheck will call RequestCSPL and the user is prompted with the "Should I terminate CSPL?" question. If the user selects YES, then CSPL calls ExitProcess(0) and terminates. BUT, what happens if the user chooses NO? From what we've coded so far, the ExitProcess won't be called: UnitThread again executes the UnitCheck, UnitCheck calls RequestCSPL once more, and the user is again prompted with the "Should I terminate CSPL?" question. How can we use Flag to correct this behavior? Well, it's simple: We just initialize Flag to FALSE and at the first RequestCSPL call, Flag will be set to TRUE. In that way, if Flag is TRUE it means that RequestCSPL has been shown before and doesn't need to be shown again. Let's highlight this by using blue to show the old code while red identifies the code added to use Flag. Inside CSPLClient.h source file: BYTE ACTIVITY_FLAG=ACTIVATE_UNIT BOOL Flag=FALSE; Inside UnitCheck routine (CSPLClient.csp): int Answer=NO_BTN; if (Flag==FALSE) { Answer=RequestCSPL("Should I terminate CSPL?"); if (Answer==YES_BTN) ExitProcess(0); else Flag=TRUE; } Why should the Flag variable be defined as global? Well, it's easy. All variables declared inside a routine are valid only while the routine is executed and are not preserved between each call. The Flag variable must be used between different executions of the UnitCheck routine so it cannot be defined inside UnitCheck, it must be defined outside (Hence a global variable). Let's test this program: Create a new project called Example3 using CSPLCompanion as you've done in Example2. Edit CSPLClient.h and CSPLClient.csp, launch ToT and start a game, and then launch CSPLClient.exe from example3 directory. You should see the program work smoothly as in the following image: ![]() |