Contents
Back
Forward

10. Map Thread


10.1 Map Thread overview

The Map Thread is the thread which manages map information directly in ToT memory.
Development has been very difficult for a number of reasons, so I must say I'm not totally satisfied with how it works. You should use this thread only if it's strictly necessary (at least until problems with it are resolved) .
If properly configured,  Map Thread doesn't scan any byte in memory, it just continuously calls MapCheck. It's up to the designers to check the tiles they are interested in.
A VERY IMPORTANT thing to note is that I was not able to find other map blocks apart from map0 blocks. In effect, this means that in multimap games you'll have access ONLY to map1 tiles information.
The exact source-code of Map Thread is the following (as you can see, it's incredibly silly):

while(true)//Start a continuous cycle
{
MapCheck();//Calls user-defined MapCheck() function
Sleep(1);//wait a bit (just to avoid freezing ToT)
}



10.2 Configuring MapThread
The big problem with MapThread, in contrast to what happens with the other structures, is that the map structure changes its position in memory each time the scenario is loaded. Probably this is caused by some unknown memory structure which precedes the map structure. Anyway, when a SCN is loaded, the result is that CSPL doesn't know where it's map structure will be placed. My solution to the problem was to let CSPL dynamically search for Map structure. This means two things:
1) CSPL needs to establish a valid comparison between the SCN map on file and the one in memory
2) CSPL must be able to find the file map

Solving the first problem entails giving CSPL some clues that tell it when the comparison map is really our map (scanning the entire map is simply not feasible).
To resolve this, I've found that the map block starts in memory somewhere in [0x1a5000,0x1a50000+0x6000] (memory intervals). I don't know if this is valid for every scenario a player could design but I chose a very large range so it's probable that it will work with all scenarios. Anyway if you find that your CSPL program cannot find the map section in memory, try to enlarge the scanning area by changing the START_ADDRESS and DIM_ADDRESS variables defined in CSPLclient.h.
Obviously START_ADDRESS is the address where CSPL begins the scan (0x1a50000 by default) while DIM_ADDRESS (a strange name, but I don't want to change it) represents the width of the area to scan (0x60000 by default).
With respect to the size of the scan area, I find that usually the first 12 bytes are enough to locate the Map block in ToT memory. Again, if you find that you need more bytes to match the map block in memory with the map block in SCN, you can change the value of the DimPattern variable which is defined in CSPLClient.h with a default value of 12.
This leads us to the second problem. Before it can read 12 bytes from the map block, CSPL needs to know where to find our SCN file. The designer who wants to use MapThread functions should change the SCNFileName variable in CSPLClient.h so that it contains the path to the scn file (since scenarios will be loaded on a variety of computers using different directory structures, I suggest you use relative paths. Thus ".\\example.scn" just means that example.scn is in the same directory as the CSPL executable).
NOTE: This also means that if you change something involving first 12 bytes of the map block, CSPL will not find the map section in memory because the first 12 bytes in memory won't match the information extracted by the SCN file. Luckily the first 12 bytes of mapblock refer to a couple of useless north pole terrain tiles, so this is unlikely to be a problem.
This seems extremely complicated and while I'm not real happy with the solution, it's more easy to use than one might think:
Usually the designer leaves START_ADDRESS, DIM_ADDRESS and DimPattern to their default values and just changes SCNFileName to the relative path of his SCN file and that's all. At this point the designer can start using MapThread functions since CSPL will automatically open the SCN, will read the map block, will scan ToT memory searching for a match, and will start MapThread without annoying the designer with additional technical details.



10.3 Global functions
In CSPL I've defined several functions to manage map tiles:
  • OffsetMap : Converts (x,y) coordinates into linear (memory) coordinates.
  • GetTerrain : Returns the terrain of tile (x,y).
  • GetTile : Returns the whole tile block of tile located at (x,y).
  • SetTile : Change the tile block located at (x,y).
  • Get9Tiles : Gets all tiles near a particular one.
Terrain types are identified using pre-defined constants:
TERRAIN TYPE CONSTANT DEFINED
Ocean TERRAIN_OCEAN
Desert TERRAIN_DESERT
Plains TERRAIN_PLAINS
Grassland TERRAIN_GRASSLAND
Forest TERRAIN_FOREST
Hills TERRAIN_HILLS
Mountains TERRAIN_MOUNTAINS
Tundra TERRAIN_TUNDRA
Glacier TERRAIN_GLACIER
Swamp TERRAIN_SWAMP
Jungle TERRAIN_JUNGLE

Plus a couple of constants used to define "no resource" or "river presence" (Look at the A.H. HexEditing document):
TERRAIN TYPE CONSTANT DEFINED
No resource TERRAIN_NORESOURCE
River TERRAIN_RIVER

DWORD OffsetMap(int x, int y)
This function is implemented mainly for internal library use:
Since Map is implemented in Civ as a 1-D data set, while map is 2D, this function gets the coordinates of a particular tile and returns the Civ2 memory offset to tile passed as parameter.

byte GetTerrain(int x,int y)
This function returns the terrain type of the tile located at x,y (passed as parameter).
The value returned is the same of the previous Terrain types table.

void GetTile(Tile* Casella,int x,int y)
This function is similar to the previous one, but instead of returning only the tile terrain type, this function returns whole information about the tile located at x,y.

void SetTile(Tile* Casella,int x,int y)
This function is the opposite of the previous one. It takes a pre-filled Tile structure and writes it in memory, overwriting the old tile located at x,y. Usually this function is used with the previous one in the following way:
  • Get info about a tile using GetTile
  • Check the information extracted and modify it as needed.
  • Writeback tile into memory using SetTile.

void Get9Tiles(TilesBox* Box,int x, int y)
This is a particular function used to take information about all tiles adjacent to a particular tile. It should be used to extract info about tiles adjacent to a particular unit or a particular city:
  • Extract x,y from special unit
  • Get adjacent tiles using Get9Tiles(...,x,y)
  • Scan tiles searching for interesting information (enemy units, enemy cities or everything else)



10.4 Example 9 : GeoBuilder
Finding an example for this particular thread is a bit difficult as there aren't many situations in which controlling the tile information is useful. This is, by the way, a very lucky thing since this thread is the most complex one and suffers from heavy limitations (as explained above in 10.1 and 10.2) such as Map0 only, needs SCN file to be decided at compile-time and other things. Anyway, I can think of two interesting uses for this thread:
1) Miner Unit: Imagine having a CSPL program which, sometimes, changes a tile (obviously if it is not occupied by a city) to a special terrain type (Mine) which was not used on the map at the start. Imagine also a special unit, Miner, and a CSPL coded event of this type: "IF Miner is on Mine THEN Send nr Money/Shields/Food to Miner HomeCity". Obviously, controlling Mines with Miners in this way would be vital for each player (although it's doubtful the AI would be able to "learn" how to do this). You'll also need to know which turn it is to avoid loops; Miners should send resources to the HomeCity only once per turn.
2)Atlantis Island: This requires a pre-drawn map. It consists of taking a portion of the map where an island is located (or where there's ocean) and when a specific turn comes, the island will be replaced by water (or vice versa). By also using the CreateCity and SetCivsInPlay functions, the CSPL designer could even add an entire new civ living on the island when it "surfaces". In this example we will try to implement something similar to event 2, only we will try to simply "write" on with mountain/ocean the text "CSPL" on turn 10. (pretty useless but without a premade map I can't raise an island since I don't know where the ocean is)

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). Begin by creating a new project called GeoBuilder.

PHASE 1: UNDERSTANDING WHAT WE NEED

The first thing a CSPL designer should think is : "which thread do I need?" In this situation we just want to play with the map, so all we need is Map Thread.

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 "Turn is 10"
while BODY is "Write the letters "CSPL" by changing ocean tiles into mountains and terrain tiles into ocean."

PHASE 3: FIXING UP SECONDARY PROBLEMS

How do we write CSPL on screen?

Well, let's say that a letter will be 5x5 tiles while characters will be separated by 2 tiles:
C S P L will be done by :

0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8
0 * * * * * * * * * * * * * * * *        
2 *         *         *       * *        
4 *         * * * * * * * * * * *        
6 *                 * *         *        
8 * * * * * * * * * * *         * * * * *

Note: I used coordinates 0,2,4, etc. because of the strange type of code coordinates used by ToT (probably due to isometric reasons).
So if, for example, we are in tile 0,0 , the tile on the right will be 0,2 while 0,1 is a non-existent tile.
It's very important to be aware that the coordinates you've used with GetTile, SetTile and other Map functions are REAL ToT coordinates. Thus it's an error to call SetTile(...,0,1) because the Tile at coordinate 0,1 does not exist!


OK, now that we know how to design the characters we need, let's move to the coding phase.

PHASE 4: CODING THE EVENT

Of course it's better to create a separate function to do our write-stuff

We will have void WriteIt();

The CSPL text starts from Tile 4,4 (in other words the upper-left corner of the text "CSPL" we're going to write on the map will be at coordinate (4,4). Thus we'll begin writing from x = 4, y = 4.

Let's start writing the "C" character. We will use the GetTile function to grab x,y Tile and if it contains land we will substitute an ocean square and write it back with SetTile, while if it contains ocean we will substitute that with mountains.

{//Write the 'C' char starting from coordinates Offsetx,Offsety
   Tile Temp;int x;
   for (int y=0;y < 10;y+=2)
   {
      for (x=0;x < 10;x+=2)
      {
         GetTile(&Temp,Offsetx+x,Offsety+y);
         if (y==0 || y==8) ChangeTile(&Temp,Offset+x,Offset+y);
         if (x==0 && y!=0 && y!=8) ChangeTile(&Temp,Offset+x,Offset+y);
      }
   }
}

OK, here's what that did: I used two nested for-loops to run a cycle scan of all 5x5 tile squares, and only when I was on a square to be changed did I call the ChangeTile function.
This means we also need to define another ChangeTile function which takes as parameter a Tile, changes it to desert, and rewrites it back to memory. It's very easy to implement:

void ChangeTile(Tile* tile,int x, int y)
{
   tile->Terrain=TERRAIN_DESERT;
   SetTile(tile,x,y);
}

and we're finished with ChangeTile.
Now it's time to write the "S" character:

{//Write the 'S' char starting from coordinates Offsetx,Offsety
   Tile Temp;int x;
   for (int y=0;y < 10;y+=2)
   {
      for (x=0;x < 10;x+=2)
      {
         GetTile(&Temp,Offsetx+x,Offsety+y);
         if (y==0 || y==8 || y==4) ChangeTile(&Temp,Offsetx+x,Offsety+y);
         if (y < 4 && y > 0 && x==0) ChangeTile(&Temp,Offsetx+x,Offsety+y);
         if (y > 4 && y < 8 && x==8) ChangeTile(&Temp,Offsetx+x,Offsety+y);
      }
   }
}

Now it's time to write the "P" character:

{//Write the 'P' char starting from coordinates Offsetx,Offsety
   Tile Temp;int x;
   for (int y=0;y < 10;y+=2)
   {
      for (x=0;x < 10;x+=2)
      {
         GetTile(&Temp,Offsetx+x,Offsety+y);
         if (y==0 || y==4) ChangeTile(&Temp,Offsetx+x,Offsety+y);
         if (y < 4 && y > 0 && x==0) ChangeTile(&Temp,Offsetx+x,Offsety+y);
         if (y > 4 && y < 8 && x==0) ChangeTile(&Temp,Offsetx+x,Offsety+y);
      }
   }
}

Now it's time to write the "L" character:

{//Write the 'L' char starting from coordinates Offsetx,Offsety
   Tile Temp;int x;
   for (int y=0;y < 10;y+=2)
   {
      for (x=0;x < 10;x+=2)
      {
         GetTile(&Temp,Offsetx+x,Offsety+y);
         if (y==8) ChangeTile(&Temp,Offsetx+x,Offsety+y);
         if (y >= 0 && y < 8 && x==0) ChangeTile(&Temp,Offsetx+x,Offsety+y);
      }
   }
}

And we're finished writing characters.
Now let's see the main skeleton of the WriteIt function:

void WriteIt()
{
   int Offsetx=4;int Offsety=4;

   /* Write 'C' */

   Offsetx=Offsetx+14;

   /* Write 'S' */

   Offsetx=Offsetx+14;

   /* Write 'P' */

   Offsetx=Offsetx+14;

   /* Write 'L' */

}

Let's see the MapCheck():
(Here we need a global variable called Flag initialized to true to avoid loops on turn 10)

void MapCheck()
{
   WORD CurrentTurn=TurnPassed();
   if (CurrentTurn==10 && !Flag)
   {
      Flag=true;
      WriteIt();
      Refresh();//We need to refresh the screen to allow changes to be seen on screen
   }
}

And we've finished with MapCheck.


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 need to activate the Map thread and to define the Global variable Flag:

BYTE ACTIVITY_FLAG=ACTIVATE_MAP;
BOOL Flag=false;


Then we have to define the auxiliary functions we want to use:

void WriteIt();
void ChangeTile(Tile* tile);


Now there's a very important remaining task: configuring MapThread.
As we saw in 10.2 we need to check the variable values in the first two sections of CSPLClient.h.
We know that default values are OK for the majority of scenarios,  so let's use the standard values for START_ADDRESS, DIM_ADDRESS, and DimPattern.
Next take a look at SCNFilename. The standard value is ".\\example.scn", which means that CSPL will try to extract map info from the "example.scn" file in the same directory as the CSPLClient program. If this were a real scenario, we would have to change this to the real scenario file name (and directory). However, since we're just testing features of CSPL we will use the "example.scn" name and later we'll create an "example.scn" file during testing.
Since we don't have to change any of these values from their defaults, we're finished with CSPLClient.h.

Editing CSPLClient.csp:
Here we need to modify the MapCheck function:

void MapCheck()
{
   WORD CurrentTurn=TurnPassed();
   if (CurrentTurn==10 && !Flag)
   {
      Flag=true;
      WriteIt();
      Refresh();
   }
}

Then we have to define two auxiliary functions:

void ChangeTile(Tile* tile,int x, int y)
{
   tile->Terrain=TERRAIN_DESERT;
   SetTile(tile,x,y);
}

void WriteIt()
{
   int Offsetx=4;int Offsety=4;

   {//Write the 'C' char starting from coordinates Offsetx,Offsety
      Tile Temp;int x;
      for (int y=0;y < 10;y+=2)
      {
         for (x=0;x < 10;x+=2)
         {
            GetTile(&Temp,Offsetx+x,Offsety+y);
            if (y==0 || y==8) ChangeTile(&Temp,Offsetx+x,Offsety+y);
            if (x==0 && y!=0 && y!=8) ChangeTile(&Temp,Offsetx+x,Offsety+y);
         }
      }
   }

   Offsetx=Offsetx+14;

   {//Write the 'S' char starting from coordinates Offsetx,Offsety
      Tile Temp;int x;
      for (int y=0;y < 10;y+=2)
      {
         for (x=0;x < 10;x+=2)
         {
            GetTile(&Temp,Offsetx+x,Offsety+y);
            if (y==0 || y==8 || y==4) ChangeTile(&Temp,Offsetx+x,Offsety+y);
            if (y < 4 && y > 0 && x==0) ChangeTile(&Temp,Offsetx+x,Offsety+y);
            if (y > 4 && y < 8 && x==8) ChangeTile(&Temp,Offsetx+x,Offsety+y);
         }
      }
   }

   Offsetx=Offsetx+14;

   {//Write the 'P' char starting from coordinates Offsetx,Offsety
      Tile Temp;int x;
      for (int y=0;y < 10;y+=2)
      {
         for (x=0;x < 10;x+=2)
         {
            GetTile(&Temp,Offsetx+x,Offsety+y);
            if (y==0 || y==4) ChangeTile(&Temp,Offsetx+x,Offsety+y);
            if (y < 4 && y > 0 && x==0) ChangeTile(&Temp,Offsetx+x,Offsety+y);
            if (y > 4 && y < 8 && x==0) ChangeTile(&Temp,Offsetx+x,Offsety+y);
         }
      }
   }

   Offsetx=Offsetx+14;

   {//Write the 'L' char starting from coordinates Offsetx,Offsety
      Tile Temp;int x;
      for (int y=0;y < 10;y+=2)
      {
         for (x=0;x < 10;x+=2)
         {
            GetTile(&Temp,Offsetx+x,Offsety+y);
            if (y==8) ChangeTile(&Temp,Offsetx+x,Offsety+y);
            if (y >= 0 && y < 8 && x==0) ChangeTile(&Temp,Offsetx+x,Offsety+y);
         }
      }
   }
}


Ok, we've finished, let's compile it!

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 GeoBuilder. This will create a CSPLClient executable file in the GeoBuilder directory.
To test this example you should create a game, SAVE IT AS "example.scn" scenario, start CSPLClient.exe, and wait until the tenth turn. At that point the map should change to show our little sign.




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