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
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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:
Plus a couple of constants used to define "no resource" or "river presence" (Look at the A.H. HexEditing document):
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:
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:
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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 PROJECTAs 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 NEEDThe 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 EVENTThe 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 PROBLEMSHow 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 :
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 EVENTOf course it's better to create a separate function to do our write-stuffWe 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.
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:
and we're finished with ChangeTile.Now it's time to write the "S" character:
Now it's time to write the "P" character:
{//Write the 'P' char starting from coordinates Offsetx,Offsety Now it's time to write the "L" character:
And we're finished writing characters. Now let's see the main skeleton of the WriteIt function:
void WriteIt()
Let's see the MapCheck(): (Here we need a global variable called Flag initialized to true to avoid loops on turn 10)
void MapCheck()
And we've finished with MapCheck. PHASE 5: MERGING THE RESULTING SOURCE CODENow 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; Then we have to define the auxiliary functions we want to use: void WriteIt(); 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:
Then we have to define two auxiliary functions:
void ChangeTile(Tile* tile,int x, int y) Ok, we've finished, let's compile it! PHASE 6: COMPILING AND LINKING THE SOURCE CODEAt 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. |