Contents Back Forward |
6. City Thread |
6.1 City Thread overview In the last chapter we've seen how Unit Thread works. Here we'll analyze another thread in greater depth, the City Thread. As we will see City Thread is very similar to Unit Thread and nearly all city functions have a corresponding unit function. The city thread continuously scans the city list in memory, and for each unit it executes the CityCheck function. The CSPL designer protects his city-related events by just changing the CityCheck function, leaving untouched the main structure of City Thread. The exact source-code of City Thread is the following: City Temp; while(true)//Starts a continuous cycle { while(ReadNextCity(&Temp))//Select following city in the list {CityCheck(Temp);} //Call CityCheck function on currently-selected city ResetCity();//Reset City Pointer GlobalCheck();//Update Global data (nr of cities particularly) Sleep(1);//Wait a bit (just to avoid to freezing ToT) } | |
6.2 Cities functions In CSPL I've defined several functions to manage cities:
void DeleteCity() Delete last city read from the city list (from the game). void WriteCity(City Temp) Replace the last city read from ToT memory with Temp city passed as parameter. bool ReadNextCity(City* Temp) This function should not be used by CSPL programmers since it is intended for internal library use only. Anyway it can be used to scan the city list outside from City thread (to see how this function should be used look at section 6.1) bool CreateCity(City Temp) This function inserts the Temp city passed as parameter inside the city list (inside the ToT game). Be careful however. If you want to add a new city to a game you also need to change the terrain tile on which you plan to create the city, by setting its 'City present' bit (I refer you again to Allard's Hexediting document which is in the Doc subdirectory of CSPL). bool CityAt(DWORD x,DWORD y,DWORD z,DWORD Offset,City* Temp) This function searches for cities placed at x,y,z (passed as parameter) and places it (if any) in the Temp unit structure. If there is no city at x,y,z the function returns FALSE. Offset parameter is used when you have different cities at the same place. First you call CityAt with Offset 0 and you'll get the first city at x,y,z, then call CityAt with Offset 1 and you'll get the second city at x,y,z, etc. when no more cities are present at x,y,z this function returns FALSE. Normally only a single city can be built at one set of coordinates, but since it has been shown that it's possible to have different cities at the same place using hexediting, I prefer to leave the Offset parameter. This allows the designer to develop events in which different cities are placed at the same x,y,z coordinates. bool CityName(LPSTR Name,DWORD Offset,City* Temp); This function returns TRUE if a city with the name Name is found in memory (and places its data in Temp unit structure), otherwise it returns FALSE. Offset is used if multiple cities with the same name are present in the game; Offset=0 will find the first city, Offset=1 will find the second and so on. It is intended to be used with special cities (Ex: Who owns Stalingrad actually?) bool CityID(DWORD Id,City* Temp) This function returns TRUE if a city with ID equals to Id (passed as parameter) is found (and its data are placed in Temp unit structure). CityID is used to find a particular city in the city list. If you are interested in a particular city you should save its ID when the game starts and then call CityID with the saved ID to find that particular city later in the game. bool ReWriteCity(City Temp,DWORD Id) The ReWriteCity function is used to write Temp city (passed as parameter) in a particular position in the game list (position identified by Id function parameter); As its name suggests, this function should be used in quick read-write cycles such as the following: - Read City (Using CityID, CityName, CityAt or other functions). - Change something on this city. - Write back the city calling ReWriteCity with this city ID as Id parameter. void ResetCity() This function is intended for internal use only. Anyway it resets the internal city pointer: While each call to ReadNextCity function reads the next city in the city list, calling ResetCity will reset the city pointer so that the next call to ReadNextCity will read the first city. | |
6.3 Example 5 : Colonization-like improvements In Colonization, a Sid Meier game very similar to Civ (and based on the same engine), city improvements are used in a slightly different way with respect to Civ improvements: Improvements present in a city determine which units can or cannot be built in that city. So, for example, an artillery unit cannot be built in a city if an armory is not present there. In Civ2 only techs determine which unit can be built and so an event such as the one described above is simply impossible to obtain with ToT. This effect is possible with CSPL and we'll try to code exactly this event. PHASE 0: CREATING A NEW PROJECTAs we've 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 ArmoryImprove. PHASE 1: UNDERSTANDING WHAT WE NEEDThe first thing a CSPL designer should think is : "which thread do I need?"In this situation, since we just want to play with city production, our choice is very easy. We need the City 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 "A city is producing an artillery unit AND there is no armory in the city" while BODY is "clear building orders of the city and show a message (only if civ is human-controlled)" PHASE 3: UNDERSTANDING IMPROVE DATA STRUCTUREActually City data structure is just a copy of city data as it is coded in ToT memory (and described in Allard's hexedit document)Let's quote Allard: " 53-57 bytes are city improvements: 1... .... is 7th improvement .1.. .... is 6th improvement ..1. .... is 5th improvement ...1 .... is 4th improvement .... 1... is 3rd improvement .... .1.. is 2nd improvement .... ..1. is 1st improvement " What does this mean? It means that we have 5 bytes (53->57), or rather 40 bits, and each bit represents an improvement (0 not built, 1 built). To check if a particular improvement has been built in a city, the designer should find in which block (byte) the bit representing the improvement is packed, and then he should control the bit to see if it is raised or not. What, that's not clear? I know, let's say we want to see if barracks are present in a city. First we have the City structure coded in a City variable called Temp: Barracks is the third improvement (as listed in the rules.txt, the first is nothing, then we have palace and then barracks) so the barracks bit is the third (0000 0100 in bits). This means that the bit is in the "first pack" (byte 0) of the Improve data structure. To check that value we can use logic and make an AND between 0000 0100 AND Improve[0]; if this is true (1) barracks are present else it is false (0) and no barracks are present. In C++ this became Improve[0]&4 (Remember that 4 is 00000100 in decimal) I know, it's probably still not clear. Anyway, remember this: I've coded all previously written numbers as constants, so instead of 4 (0000 0100) you can write IMPROVE_BARRACKS and that should be easier to remember. Further, if you want to check if a particular improvement has been built you should first determine the byte in which it is coded (and this is easy since the first eight improvements are coded in byte 0, improvements from 9 to 16 are in byte 1 and so on) and then call C++ code: (Improve[i]&IMPROVE_xxxxx) Where i is the byte nr and xxxxx is the name of the improvement which should be checked. PHASE 4: CODING THE EVENTLet's say that for our colonization scenario we've renamed the barracks improvement as "armory", and the warriors unit as "artillery". We'll also assume that the human player is always the white civ. Now it's time to write a couple of lines of code. So far we know that:if (!(City.Improve[0]&IMPROVE_BARRACKS) && City.ItemProduction==UNIT_WARRIOR) //If City does NOT have barracks (Armory) AND City is building warriors (Artillery) { City.ItemProduction=BUILD_PALACE; //Change production to Palace WriteCity(City); //Writeback changes EmulateKey('c'); //Emulate user c(hange) keypress } Where && is the AND operator in C/C++, & is the bit-wise AND operator and ! is the NOT operator Because that function must be repeated continuously for each city in the city list, it seems that we need to rewrite the CityCheck function: void CityCheck(Unit Temp) { if (!(Temp.Improve[0]&IMPROVE_BARRACKS) && Temp.ItemProduction==UNIT_WARRIOR) //If City does NOT have barracks (Armory) AND City is building warriors (Artillery) { Temp.ItemProduction=BUILD_PALACE; //Change production to Palace WriteCity(Temp); //Writeback changes EmulateKey('c'); //Emulate user c(hange) keypress } } Probably it's also a good thing to warn the human player and tell him why he is not able to build the Artillery (warrior) unit. For this example let's say that the Event will act only if the city is human-owned (this means that the AI can cheat, but in Civ this is nothing new). Let's use the MessageCSPL function with the message "We cannot build Artillery here, Armory needed!" In code: void CityCheck(Unit Temp) { if (!(Temp.Improve[0]&IMPROVE_BARRACKS) && Temp.ItemProduction==UNIT_WARRIOR && Temp.Owner==CIV_WHITE) //If City does NOT have barracks (Armory) AND City is building warriors (Artillery) { MessageCSPL("We cannot build Artillery here, Armory needed!"); Temp.ItemProduction=0xFF; //Change production to nothing WriteCity(Temp); //Writeback changes EmulateKey('c'); //Emulate user c(hange) keypress } } 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 city thread: BYTE ACTIVITY_FLAG=ACTIVATE_CITY; And we're finished with CSPLClient.h . Editing CSPLClient.csp: The only thing we need to do here is edit the CityCheck function as described in previous phase: void CityCheck(Unit Temp) { if (!(Temp.Improve[0]&IMPROVE_BARRACKS) && Temp.ItemProduction==UNIT_WARRIOR && Temp.Owner==CIV_WHITE) //If City does NOT have barracks (Armory) AND City is building warriors (Artillery) { MessageCSPL("We cannot build Artillery here, Armory needed!"); Temp.ItemProduction=BUILD_PALACE; //Change production to Palace WriteCity(Temp); //Writeback changes EmulateKey('c'); //Emulate user c(hange) keypress } } PHASE 6: COMPILING AND LINKING THE SOURCE CODEAt this point save CSPLClient.h and CSPLClient.csp files and use CSPLCompanion to compile and link ArmoryImprove. This will create a CSPLClient executable in the ArmoryImprove directory.To test it (without having to rename barracks to armory and warriors to artillery), simply start a new game in ToT, launch CSPLClient.exe, build a city, and then try to build a warrior in the city. If all goes well a MessageBox should pop-up warning you that an artillery unit cannot be built in the city because there's no armory in it. Notice that this example is far from a complete and working CSPL program because, for example, the AI is able to build without any problem. Another thing is this code changes production to Palace, while a better idea might be to change production to Armory. That way, each city which tries to build an artillery unit without an armory will immediately start building the armory instead. Another possibility is to include the city name in the message box, thus creating a "custom" message such as "We cannot build Artillery in Isabella sir, We need an armory!" |