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 deeply another thread: 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 scan continuosly the city list in memory and, for each unit it executes CityCheck function; the CSPL designer projects his city-related events 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 continuos 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 freeze 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 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 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 insert 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've to change also terrain tile in which you want to create the city setting its 'City present' bit (i suggest you to read Allard documentation on hex-edit which is in 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 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 UnitAt 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. Obviously usually only a city can be built at the same coordinates but, since it has been showed that it is possible to have different cities at the same place using hexediting i prefer to leave the Offset parameter, allowing the designer to develop events also in situation 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) elsewhere 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 found 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 late 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 says, this function should be used in quick read-write cycles as the following: - Read City (Using CityID, CityName, CityAt or other functions). - Change something on this city. - Write back the unit calling ReWriteCity with this city ID as Id parameter. void ResetCity() This function is intended for internal use only, anyway it reset internal city pointer: while each call to ReadNextCity function reads the next city in city list, calling ResetCity will reset 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's game very similar to Civ (and based on the same engine), city improvements are used in a slight different way respect to Civ improvements: improvements built in a city decides 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 in that city. In Civ2 only techs determine which unit can be built and so an event as the one described above is simply impossible to obtain with Tot but again,this effect is possible to obtain 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);again create a new project called ArmoryImprove. PHASE 1: UNDERSTANDING WHAT WE NEEDThe first thing a CSPL designer should think is : "which thread i need?"In this situation, since we just want to play with city production, our choise 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 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 shows 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 documentation about hexedit)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 means? This means that we have 5 bytes (53->57), or better, 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, it's not clear? i know, let's say we want to check if barracks are present in a city, 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 barracks bit is the third (0000 0100 in bits); this means that the bit is in the "first pack" (byte 0) of 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 less clear, anyway, remember this: I've coded all previously written numbers as costants, so instead that 4 (0000 0100) you can write IMPROVE_BARRACKS and this should help, more, if you want to check if a particular improvement has been built you should detect the byte in which it is coded (and this should be easy, the first eight improvements are coded in byte 0, improvements from 9 to 16 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 we've redefined, for our colonization scenario, barracks improvement as armory, and warriors unit as artillery unit,let's say also that the human player is always the white Now it's time to write a couple of lines of code,'till now we know that: if (!(City.Improve[0]&IMPROVE_BARRACKS) && City.ItemProduction==UNIT_WARRIOR) //If City has NOT 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 That function must be repeated for each city in city list and continuosly; 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 has NOT 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 a good thing to warn the human player telling him why he is not able to build Artillery (warrior) unit; For this example let's say also that the Event will act only if the city is human-owned (this means that 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 has NOT 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 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've finished with CSPLClient.h . Editing CSPLClient.csp: The only thing we've to do here is to edit the CityCheck function as descrived in previous phase: void CityCheck(Unit Temp) { if (!(Temp.Improve[0]&IMPROVE_BARRACKS) && Temp.ItemProduction==UNIT_WARRIOR && Temp.Owner==CIV_WHITE) //If City has NOT 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;you should obtain a CSPLClient executable in 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 and build a city, 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 'cause, for example, AI is able to build without any problem, again, this code changes production to Palace, while a good idea could be to change production to Armory, in that way, each city which try to build an artillery unit without an armory will start to build one. Another thing, we could use the city name in the message box, to have a "custom" message as "We cannot build Artillery in Isabella sir, We need an armory!" |