[SDK HOW TO] Add new missions for units.

zafzo

Chieftain
Joined
Aug 20, 2006
Messages
5
Poison City Mod

I decided I was bored the other day and wanted to give spies the ability to poison cities to reduce the population of the cities. I have never modded CIV4 before besides a few xml changes here and there but I thought what the hell.

I finally finished(except for the AI's use of my code which I haven't started yet) and I figured I would do a walkthrough on just about everything I did to make this mod work. Hopefully it will help the not so code savvy people such as myself to some lightweight SDK work.

I also want to thank Kael, and TGA for some of their really informative posts that helped me out and also Dale whose combat mod I installed and used for reference.

I will have code snippets of all of my code and the location of said code as well. All of my incode comments will also be posted. They explain a lot in the C++ code. Hopefully it won't be too hard to read. I'll lay this out in three posts following this one. Note that there is no python coding involved here. I've also probably made a lot of mistakes so if you see one please let me know. I also apologize for all the :p's in the code. I'm too lazy to figure out to take them out. They represent : p with no space. heh.

1. XML changes
2. C++ core changes (CvXXXX)
3. C++ Python interface changes (CyXXXX)
 
Poison City Mod Part 1: XML Changes

List of files changed:

CIV4MissionInfos.xml
CIV4GameTextInfos.xml
CIV4GameText_Help.xml
CIV4UnitInfos.xml
CIV4UnitSchema.xml
GlobalDefines.xml

1. CIV4MissionInfos.xml

This is the file that contains the list of missions. It's pretty straightforward. I basically copied one of the Spy Steal Plans(MISSION_STEAL_PLANS) and used that since it was similar to what I was trying to get at. So I added this xml at the bottom of the file:

Spoiler :
<!--Begin rbl Poison Mod 8/21/2006-->
<MissionInfo>
<Type>MISSION_POISON_CITY</Type>
<Description>TXT_KEY_MISSION_POISON_CITY</Description>
<Help>TXT_KEY_MISSION_POISON_CITY_HELP</Help>
<Waypoint>NONE</Waypoint>
<EntityEventType>ENTEVENT_SABOTAGE</EntityEventType>
<iTime>20</iTime>
<bTarget>0</bTarget>
<bBuild>0</bBuild>
<bSound>1</bSound>
<HotKey/>
<bAltDown>0</bAltDown>
<bShiftDown>1</bShiftDown>
<bCtrlDown>0</bCtrlDown>
<iHotKeyPriority>0</iHotKeyPriority>
<HotKeyAlt/>
<bAltDownAlt>0</bAltDownAlt>
<bShiftDownAlt>0</bShiftDownAlt>
<bCtrlDownAlt>0</bCtrlDownAlt>
<iHotKeyPriorityAlt>0</iHotKeyPriorityAlt>
<bVisible>1</bVisible>
<Button>Art/Interface/Buttons/Actions/Pillage.dds</Button>
</MissionInfo>
<!--End rbl Poison Mod-->

You'll notice a few changes if you compare this one to MISSION_STEAL_PLANS. The <Type>, <Description>, <Help>, <EntityEventType>, <HotKey>, and <Button> are different.

The <Type> specifies the mission enum.
The <Description> specifies the text used in game.
Same with <Help>.
<EntityEventType> determines the visible action of the unit. I used the pillage entity event because I like the explosion and the spy flipping. Plus I don't know how to create my own. ;-)
<HotKey> is, you guessed it. A hot key so you don't have to use your mouse. I just left it blank because I didn't want to put something random in and have a conflict.
<Button> is the actual art on the button in game. I used the pillage button. I hate working with graphics and refuse to mess with them so I just use in game art.

Make sure you add new missions to the bottom of CIV4MissionInfos.xml or your game will crash! Trust me on this!

2. CIV4GameTextInfos.xml

This file contains a list of the descriptions for the mission.

Spoiler :
<!--Begin rbl Poison Mod 8/21/2006-->
<!--Sorry I only speak english and I'm too lazy to look up translations! ;-)-->
<TEXT>
<Tag>TXT_KEY_MISSION_POISON_CITY</Tag>
<English>Poison City</English>
<French>Poison City</French>
<German>Poison City</German>
<Italian>Poison City</Italian>
<Spanish>Poison City</Spanish>
</TEXT>
<!--End rbl Poison Mod-->


If you notice, the <Tag> line contains the same identifier as the <Description> tag in CIV4MissionInfos.xml. That's because it uses this to get the description! Also in this file :

Spoiler :
<!--Begin rbl Poison Mod 8/21/2006-->
<!--Yup. All english again.-->
<TEXT>
<Tag>TXT_KEY_MISC_SPY_POISONED_CITY</Tag>
<English>Your %s1_UnitName has poisoned %s2_CityName!!!</English>
<French>Your %s1_UnitName has poisoned %s2_CityName!!!</French>
<German>Your %s1_UnitName has poisoned %s2_CityName!!!</German>
<Italian>Your %s1_UnitName has poisoned %s2_CityName!!!</Italian>
<Spanish>Your %s1_UnitName has poisoned %s2_CityName!!!</Spanish>
</TEXT>


This is basically info that the game grabs whenever you are successful poisoning the city. It is retrieved in the SDK code. You can place this code anywhere in this file. I tend to group it with similar tags.


3. CIV4GameText_Help.xml

Like CIV4GameTextInfos.xml, this file stores text info about the mission. Here is the code :
Spoiler :
<!--Begin rbl Poison Mod 8/21/2006-->
<!--Sorry I only speak english and I'm too lazy to look up translations! ;-)-->
<TEXT>
<Tag>TXT_KEY_MISSION_POISON_CITY_HELP</Tag>
<English>The [COLOR_UNIT_TEXT]Spy[COLOR_REVERT] will attempt to poison the city, which kill off population depending on overall city health.</English>
<French>The [COLOR_UNIT_TEXT]Spy[COLOR_REVERT] will attempt to poison the city, which kill off population depending on overall city health.</French>
<German>The [COLOR_UNIT_TEXT]Spy[COLOR_REVERT] will attempt to poison the city, which kill off population depending on overall city health.</German>
<Italian>The [COLOR_UNIT_TEXT]Spy[COLOR_REVERT] will attempt to poison the city, which kill off population depending on overall city health.</Italian>
<Spanish>The [COLOR_UNIT_TEXT]Spy[COLOR_REVERT] will attempt to poison the city, which kill off population depending on overall city health.</Spanish>
</TEXT>
<!--End rbl Poison Mod-->


Again, notice the <Tag> line which is identical to the <Help> tag in CIV4MissionInfos.xml. Noticing a trend? You may place this code anywhere in the file.

4. CIV4UnitInfos.xml

This file stores the attributes for every unit in the game. Since I needed only the spy to have poison capabilities, I needed a new attribute. So I added this line of code :

Spoiler :

<bSabotage>1</bSabotage>
<!--Begin rbl Poison Mod 8/21/2006-->
<bPoisonCity>1</bPoisonCity>
<!--End rbl Poison Mod-->
<bDestroy>1</bDestroy>


I included the firaxis attributes around where I put my code(This is in the UNIT_SPY area) because order is important here. I'll tell you why later.

5. CIV4UnitSchema.xml

This file is the reason that CIV4UnitInfos.xml has to be in a certain order. It is the schema that the info file follows. The code in the schema is like this:

Spoiler :

<ElementType name="bSabotage" content="textOnly" dt:type="boolean"/>
<!--Begin rbl Poison Mod 8/21/2006-->
<ElementType name="bPoisonCity" content="textOnly" dt:type="boolean"/>
<!--End rbl Poison Mod-->
<ElementType name="bDestroy" content="textOnly" dt:type="boolean"/>


Notice how the original sabotage and destroy are above and below mine, respectively. If you switch them around and not CIV4UnitInfos.xml, you will get an xml error.

Code also goes in the "UnitInfo" ElementType

Spoiler :

<element type="bSabotage"/>
<!--Begin rbl Poison Mod 8/21/2006-->
<element type="bPoisonCity" minOccurs="0"/>
<!--End rbl Poison Mod-->
<element type="bDestroy"/>


Again, it has to be in the same order. miOccurs="0" means that you don't have to have the <bPoisonCity>0</bPoisonCity> for every other unit. Just put it in as <bPoisonCity>1</bPoisonCity> for only the units you want to have the ability.

Order is very important in both CIV4UnitInfos.xml and CIV4UnitSchema.xml

6. GlobalDefines.xml

This file is for certain constants used in the SDK. All I added here were tags for use in the SDK for calculating the cost of poisoning.

Code:


Spoiler :
<!--Begin rbl Poison Mod 8/21/2006-->
<Define>
<DefineName>BASE_SPY_POISON_CITY_COST</DefineName>
<iDefineIntVal>500</iDefineIntVal>
</Define>
<Define>
<DefineName>SPY_POISON_CITY_COST_MULTIPLIER</DefineName>
<iDefineIntVal>100</iDefineIntVal>
</Define>
<!--End rbl Poison Mod-->


So that does it for the XML changes. Hope you're still with me!
 
List of changed files :

CvEnums.h
CvInfos.h
CvInfos.cpp
CvUnit.h
CvUnit.cpp
CvSelectionGroup.cpp

1. CvEnums.h

This file contains the enumeration types for the game. The one we are interested in is MissionTypes. We need to add our mission type to this list at the end of the list but before NUM_MISSION_TYPES! This is very important. In fact I will post the entire enumeration code:

Spoiler :
// any additions need to be reflected in GlobalTypes.xml
enum DllExport MissionTypes // Exposed to Python
{
NO_MISSION = -1,

MISSION_MOVE_TO,
MISSION_ROUTE_TO,
MISSION_MOVE_TO_UNIT,
MISSION_SKIP,
MISSION_SLEEP,
MISSION_FORTIFY,
MISSION_AIRPATROL,
MISSION_HEAL,
MISSION_SENTRY,
MISSION_AIRLIFT,
MISSION_NUKE,
MISSION_RECON,
MISSION_AIRBOMB,
MISSION_BOMBARD,
MISSION_PILLAGE,
MISSION_SABOTAGE,
MISSION_DESTROY,
MISSION_STEAL_PLANS,
MISSION_FOUND,
MISSION_SPREAD,
MISSION_JOIN,
MISSION_CONSTRUCT,
MISSION_DISCOVER,
MISSION_HURRY,
MISSION_TRADE,
MISSION_GREAT_WORK,
MISSION_GOLDEN_AGE,
MISSION_BUILD,

MISSION_BEGIN_COMBAT,
MISSION_END_COMBAT,
MISSION_AIRSTRIKE,
MISSION_SURRENDER,
MISSION_CAPTURED,
MISSION_IDLE,
MISSION_DIE,
MISSION_DAMAGE,
MISSION_MULTI_SELECT,
MISSION_MULTI_DESELECT,
//Begin rbl Poison Mod 8/21/2006
//Add to the end(but before the number of missions) so we don't screw up the other mission's index's
MISSION_POISON_CITY,
//End rbl Poison Mod

NUM_MISSION_TYPES
};


Remember, the first non initialized enum will default to 0 in C++ so MISSION_MOVE_TO = 0, MISSION_ROUTE_TO = 1, etc. NUM_MISSION_TYPES at the end is actually the total number of missions even if you add one! (provided you add it before NUM_MISSION_TYPES) Pretty slick.

Oh and the firaxis comment about changes being reflected in GlobalTypes.xml? It's a lie. At least for this mod. :)

2. CvInfos.h

Here we have the header file(class definition) for all the info classes. All info classes inherit the base class CvInfoBase. CvTechInfo, CvUnitInfo, CvSpecialistInfo, CvPromotionInfo, CvMissionInfo.... It goes on and on. There are also inherited classes in between the base class and child class. For example :

CvUnitInfo inherits CvHotkeyInfo which inherits CvInfoBase.

The one we are interested in is CvUnitInfo.

So we find CvUnitInfo and add our new member fields.

Spoiler :
//Begin rbl Poison Mod 8/21/2006
DllExport bool isPoisonCity() const; //Exposed to python
protected:

bool m_bPoisonCity;

//Public again for Dale's awesome mods!
public:

//End rbl Poison Mod


I included Dale's Combat mod because it's... well... AWESOME! But I put my code before his. :) But that's it for this file as well. Pretty simple ain't it?

3. CvInfos.cpp

Now we actually are going to implement isPoisonCity() which we declared in CvInfos.h but first we need to initialize our variable we also declared in CvInfos.h, m_bPoisonCity. The following code goes in the constructor for the CvUnitInfo class:

Spoiler :
m_bSabotage(false),
//Begin rbl Poison Mod 8/21/2006
//Init our poison toggle
m_bPoisonCity(false),
//End rbl PoisonMod
m_bDestroy(false),


Note that I put my code near similar spy variables. This isn't necessary I just like it. Now we implement isPoisonCity():
Spoiler :
bool CvUnitInfo::isDestroy() const
{
return m_bDestroy;
}

//Begin rbl Poison Mod 8/21/2006
bool CvUnitInfo::isPoisonCity() const
{
return m_bPoisonCity;
}
//End rbl Poison Mod

bool CvUnitInfo::isStealPlans() const
{
return m_bStealPlans;
}


Pretty self explanatory I think. :)

Now we get to do some data stream reading and writing for our newfound m_bPoisonCity field.

This code goes into the CvUnitInfo::read(FDataStreamBase* stream) method.
Spoiler :
//Begin rbl Poison Mod 8/21/2006
stream->Read(&m_bPoisonCity);
//End rbl Poison Mod


This code goes into the CvUnitInfo::write(FDataStreamBase* stream) method.
Spoiler :
//Begin rbl Poison Mod 8/21/2006
stream->Write(m_bPoisonCity);
//End rbl Poison Mod


The above read and writes are used to load and save information during game load and game save. Someone please correct me if I'm wrong because there is a good chance I am.


Now we have another read method(CvUnitInfo::read(CvXMLLoadUtility* pXML)) we need to add stuff to. Go go polymorphism.

Spoiler :
//Begin rbl Poison Mod 8/21/2006
//Get the info from the xml file CIV4UnitInfos.xml
pXML->GetChildXmlValByName(&m_bPoisonCity, "bPoisonCity");
//End rbl Poison Mod


This one actually gets the information that is from CIVUnitInfos.xml. Remember putting bPoisonCity in that file?

Done with this file.

4. CvUnit.h

Here is where the CvUnit class is defined. We need to add our method signatures here and that's it.
Spoiler :
//Begin rbl Poison Mod 8/21/2006

DllExport int poisonCityCost(const CvPlot* pPlot) const; //Exposed to Python
DllExport int poisonCityProb(const CvPlot* pPlot, ProbabilityTypes eProbStyle = PROBABILITY_REAL) const; // Exposed to Python
DllExport bool canPoisonCity(const CvPlot* pPlot, bool bTestVisible = false) const; // Exposed to Python
bool poisonCity();
//End rbl Poison Mod


5. CvUnit.cpp

This is where the meat of our code goes and how we implement the actual action of poisoning a city. We will implement the four methods we defined in CvUnit.h. Most of the commenting is in the code so I'm not going to retype it out here.

poisonCityCost() determines the cost of poisoning a city. Code:
Spoiler :
//Return the cost of poisoning a city
int CvUnit::poisonCityCost(const CvPlot* pPlot) const
{
CvCity* pCity;

//Grab pointer to the city on the current plot
//getPlotCity returns a pointer to the city on the current plot or null if there is no city.
pCity = pPlot->getPlotCity();

//If there isn't a city then it shouldn't cost a damn thing.
if (pCity == NULL)
{
return 0;
}

// The cost to poison the city is the BASE_SPY_POISON_CITY_COST + (<city population> * SPY_POISON_CITY_COST_MULTIPLIER)
// Both values can be found and changed in GlobalDefines.xml
return (GC.getDefineINT("BASE_SPY_POISON_CITY_COST") + ((pCity->getPopulation()) * GC.getDefineINT("SPY_POISON_CITY_COST_MULTIPLIER")));
}


The method takes a pointer to a CvPlot object which is the plot of land your unit is on and returns an int. The amount of gold it's going to cost you. Notice the BASE_SPY_POISON_COST and SPY_POISON_CITY_COST_MULTIPLIER that we defined in GlobalDefines.xml. This is where we use it. GC.getDefineINT takes the text argument and returns it's value from the global hash map civ4 uses.

poisonCityProb() determines the probability of success. Code:
Spoiler :
int CvUnit::poisonCityProb(const CvPlot* pPlot, ProbabilityTypes eProbStyle) const
{
CvCity* pCity;
CvPlot* pLoopPlot;
int iDefenseCount;
int iCounterSpyCount;
int iProb;
int iI;

//Grab pointer to city.
pCity = pPlot->getPlotCity();

//Yeah. Not much probability of poisoning a city if there ain't one there.
if (pCity == NULL)
{
return 0;
}

//isGovernmentCenter() can be found in CvCity.cpp
//This one returns true if the city is the capital, has the forbidden palace, or versailles.
//It makes it harder to poison these.
iProb = ((pCity->isGovernmentCenter()) ? 20 : 0);

//This next line gets the number of defenders in the city.
//Okay I'll describe how plotCount works:
//In this instance, it takes PUF_canDefend(which is a method), two data values(-1, -1), NO_PLAYER, and the team that owns the plot(the city owner in this case)
//It iterates through the units on the plot(the city in this case) and calls PUF_canDefend on each unit
//plotCount calls PUF_canDefend on each unit on the plot returns the total count of units where PUF_canDefend is true
//So it won't count settlers, workers, etc. but will count spearmen, tanks, etc.
//The implementation of plotCount can be found in CvPlot.cpp, PUF_canDefend is in CvGameCoreUtils but basically just calls
//canDefend() in CvUnit.cpp which is this file.
iDefenseCount = pPlot->plotCount(PUF_canDefend, -1, -1, NO_PLAYER, pPlot->getTeam());

//Get the number of counter spies on the plot
//Guess what. This works the same as above
//PUF_isCounterSpy is a method in CvGameCoreUtils.cpp that calls isCounterSpy() which is in this class. CvUnit.cpp
iCounterSpyCount = pPlot->plotCount(PUF_isCounterSpy, -1, -1, NO_PLAYER, pPlot->getTeam());

//Okay. Got all that? Good

//Now. Our directions N, S, E, W, NE, NW, SW, SE are set in CvEnums.h If you take a look at it(search for DirectionTypes in CvEnums.h)
//you will see that NO_DIRECTION = -1, DIRECTION_NORTH = 0, DIRECTION_NORTHEAST = 1, etc... and hey!
//NUM_DIRECTION_TYPES = 8 which in fact there are 8 directions! Sneaky programmers...
//Oh yeah, The first enumeration that isn't explicitly set to a value automatically defaults to 0 and continues adding 1 to each which
//is why DIRECTION_NORTH = 0, DIRECTION_NORTHEAST = 1, and so on.
//So now we are looping through each direction 0 thru 7 which is all 8 directions
for (iI = 0; iI < NUM_DIRECTION_TYPES; iI++)
{
//((DirectionTypes)iI) on the next line is casting the loop counter int to a direction type.
//getX_INLINE() and getY_INLINE() return the plots x and y coordinate. These are both declared in CvPlot.h
//Inlines are usually declared in the .h files.
//plotDirection can be found in CvGameCoreUtils.h and is also an inline method.(inline == faster for small amounts of code)
//plotDirection basically grabs the plots in the specified direction around the current plot(the city in this case)
//This for loop we are in is iterating through the plots around the city.
//If you look at the plotDirection code you'll see that it calls more methods. I won't go into those but I'll point you in
//the right direction.
//Look at line 229 in CvGameCoreUtil.h : return GC.getMapINLINE().plotINLINE((iX + GC.getPlotDirectionX()[eDirection]), (iY + GC.getPlotDirectionY()[eDirection]));
//This returns a plot. GC.getPlotDirectionX()[eDirection] gets a number defined in CvGlobals.cpp(lines 160-182) which helps the plot go left or right one plot
//Same for the y-axis.
//CvMap.h contains some of the map inline methods used.
//You will see this kind of for loop with the directions a lot in the SDK so try to understand it.
pLoopPlot = plotDirection(pPlot->getX_INLINE(), pPlot->getY_INLINE(), ((DirectionTypes)iI));

//So now we have our plot adjacent to the city
if (pLoopPlot != NULL)
{
//The plot is there so let's add the counter spies hanging out around the city. Not just inside the city.
iCounterSpyCount += pLoopPlot->plotCount(PUF_isCounterSpy, -1, -1, NO_PLAYER, pPlot->getTeam());
}
}

//We always take into account defenders and population. Can't make it too easy.
iProb += (iDefenseCount * 2);
//getPopulation should be self explanatory and is found in CvCity.cpp
//I divide here because I'm wanting it to be easier with higher populations(more places for the spy to hide)
iProb += 20 / (pCity->getPopulation());

//Probability types are set in CvEnums.h at line 1111(see the trend for user enum types?)
//So whatever probability we pass into our poisonCityProb method affects our probability.
//I haven't tested these numbers so I'm sure they will change when the AI poisons me to death with success every time.

switch (eProbStyle)
{
//If we want a high probability of success, well let's just not count the counter spies!
case PROBABILITY_HIGH : iCounterSpyCount = 0;
break;

//Low probability? Let's multiply the counter spy weight by 5.
case PROBABILITY_LOW : iCounterSpyCount *= 5;
break;

//Real? counter spy weight multiplied by 3
case PROBABILITY_REAL : iCounterSpyCount *= 3;
break;

//Something weird? Do nothing at all.
default :
break;
}

iProb += (50 / (iCounterSpyCount + 1));

//So in the end a capital with 10 pop, 5 spies, 5 defenders with PROBABILITY_REAL should return a 41 if my math is right.
//Prob from capital = 20
//Prob from defense = 5 * 2 = 10
//Prob from population = 20 / 10 = 2
//Prob from counter spies = 5 * 3 = 15
//20 + 10 + 2 + 15 = 47. So roughly a 53% chance which is pretty darn good so I'll most likely change these values.
return iProb;
}


Again this method takes a CvPlot object and a probability type.

canPoisonCity() determines if this unit can actually poison a city.
Spoiler :
bool CvUnit::canPoisonCity(const CvPlot* pPlot, bool bTestVisible) const
{
CvCity* pCity;

//Checks to see if the unit can poison or not.
//isPoisonCity can be found in CvInfos.cpp
//Remember that CvUnit inherits CvInfos so that's why it's there and we can call it here.
if (!(GC.getUnitInfo(getUnitType()).isPoisonCity()))
{
return false;
}

//Don't want to poison our own cities... Hopefully...
//getTeam() by itself calls the UnitInfo class version of getTeam()
//pPlot->getTeam() calls the PlotInfo class version of getTeam() to make sure the unit doing
//the poisoning isn't on the same team as the plot owner. Gotta love polymorphism.
if (pPlot->getTeam() == getTeam())
{
return false;
}

//Grab a pointer to the city.
pCity = pPlot->getPlotCity();

//Dump if there is no city there.
if (pCity == NULL)
{
return false;
}

//Not really sure about bTestVisible. I haven't really researched it but I put it in because it is in canStealPlans
//which I shamelessly copy and pasted most of these methods from and just modified them. ;-)
if (!bTestVisible)
{
//Make sure we have enough gold to pay for it!
if (GET_PLAYER(getOwnerINLINE()).getGold() < poisonCityCost(pPlot))
{
return false;
}
}

return true;
}


Comments in code. Self explanatory.

poisonCity actually does the dirty work.
Spoiler :
bool CvUnit::poisonCity()
{
CvCity* pCity;
CvWString szBuffer;
bool bCaught;
int iPopulationChange = 0;
int iCurrentPopulation = 0;

if (!canPoisonCity(plot()))
{
return false;
}

//Get a random number between 0 and 100 and compare it to our probability number to see if we get caught!
bCaught = (GC.getGameINLINE().getSorenRandNum(100, "Spy: Poison City") > poisonCityProb(plot()));

//Grab the city pointer
pCity = plot()->getPlotCity();
//I didn't delve into firaxis' assertions but I put it here because firaxis has it in similar places.
//Usually assertions are in place to catch errors and handle them.
FAssertMsg(pCity != NULL, "City is not assigned a valid value");

//Suck our gold away to pay for our mass killings of people!
GET_PLAYER(getOwnerINLINE()).changeGold(-(poisonCityCost(plot())));

//We didn't get caught let's do some damage!
if (!bCaught)
{
//Get the city's current population
iCurrentPopulation = pCity->getPopulation();

//Over population of 3
if (iCurrentPopulation > 3)
{
//Yeah. I'm reducing the population by 75% to start. It's a killer!
iPopulationChange = iCurrentPopulation * (-3) / 4;

//Give 25% pop back if player is using environmental civic
//getInfoTypeForString is in CvGlobals.cpp and returns the int index of the corresponding enum type
//It checks the global hash map where the game stores the xml info that is read
//getCivics returns the civic being currently used for the civic option. i.e. there are 5 civic option types
//and 5 civic types per option type.
if (GET_PLAYER(pCity->getOwnerINLINE()).getCivics((CivicOptionTypes)GC.getInfoTypeForString("CIVICOPTION_ECONOMY")) == (CivicTypes)GC.getInfoTypeForString("CIVIC_ENVIRONMENTALISM"))
{
iPopulationChange += iCurrentPopulation / 4;
}

//Give another 25% if the city has a hospital
//Check for a hospital in the city
if (pCity->hasActiveBuilding((BuildingTypes)GC.getInfoTypeForString("BUILDING_HOSPITAL")))
{
iPopulationChange += iCurrentPopulation / 4;
}
}
else
{
//Only kill one pop if city is size 2 or 3
if (iCurrentPopulation > 1)
{
iPopulationChange = -1;
}
//Do nothing if city is size 1 for now. I'll probably add some checking here to spit out different text though.
else
{
iPopulationChange = 0;
}
}
//Change the populations
pCity->changePopulation(iPopulationChange);
//Finish moves
finishMoves();

//Write info text.
szBuffer = gDLL->getText("TXT_KEY_MISC_SPY_POISONED_CITY", getNameKey(), pCity->getNameKey());
gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), true, GC.getDefineINT("EVENT_MESSAGE_TIME"), szBuffer, "AS2D_STEALPLANS", MESSAGE_TYPE_INFO, GC.getUnitInfo(getUnitType()).getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pCity->getX_INLINE(), pCity->getY_INLINE());

szBuffer = gDLL->getText("TXT_KEY_MISC_CITY_POISONED", pCity->getNameKey());
gDLL->getInterfaceIFace()->addMessage(pCity->getOwnerINLINE(), false, GC.getDefineINT("EVENT_MESSAGE_TIME"), szBuffer, "AS2D_STEALPLANS", MESSAGE_TYPE_INFO, GC.getUnitInfo(getUnitType()).getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pCity->getX_INLINE(), pCity->getY_INLINE(), true, true);

//Didn't research this but at first glance it looks like it just pushes our poison city mission
//But I'm probably lying because I didn't really look.
if (plot()->isActiveVisible(false))
{
NotifyEntity(MISSION_POISON_CITY);
}
}


The base stuff is here but definitely needs changing to make the game fair. I'm in the process of trying different things with it to see what works. My first implementation could destroy a city in two turns with one spy and lots of money. :) Not so fair.

6. CvSelectionGroup.cpp


In this file, the giant switch statements about MissionTypes is what we are interested in.

The code is simple and self explanatory:
Spoiler :

firaxis code here
//Begin rbl Poison Mod 8/21/2006
//Check to see if we can start poison a city!
case MISSION_POISON_CITY:
if (pLoopUnit->canPoisonCity(pPlot, bTestVisible))
{
return true;
}
//End rbl Poison Mod
firaxis code here....
//Begin rbl Poison Mod 8/21/2006
//These missions can be started so we do nothing
case MISSION_POISON_CITY:
//End rbl Poison Mod
firaxis code here
//Begin rbl Poison Mod 8/21/2006
//Start the poison mission!
case MISSION_POISON_CITY:
if (pLoopUnit->poisonCity())
{
bAction = true;
}
break;
//End rbl Poison Mod
firaxis code
//Begin rbl Poison Mod 8/21/2006
//Continue mission
case MISSION_POISON_CITY:
//End rbl Poison Mod
//Begin rbl Poison Mod 8/21/2006
//Last mission plot
case MISSION_POISON_CITY:
//End rbl Poison Mod




As you can see there are a lot of places where you just have :
case MISSION_POISON_CITY: and nothing else. I won't say exactly where because there are a bunch. Check out the file for more specifics. :)


Wow. That's it for the Cv core stuff. On the the C++ python interface.
 
List of files changed:

CyInfoInterface1.cpp
CyUnit.h
CyUnit.cpp
CyUnitInterface1.cpp

1. CyInfoInterface1.cpp

Exposes the core CvInfo methods to python. This one exposes the isPoisonCity() method we wrote in CVInfos.cpp. Code:

Spoiler :
.def("isDestroy", &CvUnitInfo::isDestroy, "bool ()")
//Begin rbl Poison Mod 8/21/2006
//Give Python an interface.
.def("isPoisonCity", &CvUnitInfo::isPoisonCity, "bool ()")
//End rbl Poison Mod
.def("isStealPlans", &CvUnitInfo::isStealPlans, "bool ()")


I again put the code near similar methods like all the rest just in case. I'm not sure if order matters here. I wouldn't think so but you never know. Also notice that even though we defined isPoisonCity in the CvInfoBase class, we can still call it through the CvUnitInfo class. I love oop and inheritance.

2. CyUnit.h

We define our wrapper methods in this class that will access the corresponding exported methods we implemented in CvUnit.

poisonCityCost()
poisonCityProb()
canPoisonCity()

We just declare them here. Code:
Spoiler :
//Begin rbl Poison Mod 8/21/2006
int poisonCityCost( CyPlot* pPlot);
int poisonCityProb( CyPlot* pPlot, int /*ProbabilityTypes*/ eProbStyle);
bool canPoisonCity( CyPlot* pPlot, bool bTestVisible);
//End rbl Poison Mod


3. CyUnit.cpp

Now we implement the methods declared above to call the methods we implemented in CvUnit.cpp. Who says wrapper classes aren't fun? Code :
Spoiler :
//Begin rbl Poison Mod 8/21/2006
//Call CvUnit's poisonCityCost()
int CyUnit::poisonCityCost(CyPlot* pPlot)
{
return m_pUnit ? m_pUnit->poisonCityCost(pPlot->getPlot()) : -1;
}
//Call CvUnit's poisonCityProb()
int CyUnit::poisonCityProb(CyPlot* pPlot, int /*ProbabilityTypes*/ eProbStyle)
{
return m_pUnit ? m_pUnit->poisonCityProb(pPlot->getPlot(), (ProbabilityTypes) eProbStyle) : -1;
}
//Call CvUnit's canPoisonCity()
bool CyUnit::canPoisonCity(CyPlot* pPlot, bool bTestVisible)
{
return m_pUnit ? m_pUnit->canPoisonCity(pPlot->getPlot(), bTestVisible) : false;
}
//End rbl Poison Mod


Cake and pie, no?

4. CyUnitInterface1.cpp

Here we are just giving python an interface to call the methods above. Code:
Spoiler :
//Begin rbl Poison Mod 8/21/2006
.def("poisonCityCost", &CyUnit::poisonCityCost, "int (CyPlot* pPlot)")
.def("poisonCityProb", &CyUnit::poisonCityProb, "int (CyPlot* pPlot, int /*ProbabilityTypes*/ eProbStyle)")
.def("canPoisonCity", &CyUnit::canPoisonCity, "bool (CyPlot* pPlot, bool bTestVisible)")
//End rbl Poison Mod


And that's it!
 
Now we have a working poison mod! Cool.

Now I haven't implemented any code for the AI to use this yet. I want to work out the details on exactly how powerful I want this to be. Any and all suggestions are greatly appreciated. You are welcome to use my code in any way provided my original comments stay in place.

If anyone wants to make some art/animations for the mod please go right ahead! I hate messing with graphics.

Thanks again to Kael, TGA, and Dale for the information they unknowingly provided me!

My source is provided in the zip along with Dale's Combat Mod source since I had that installed first and didn't do a fresh mod. Don't worry Dale! Your comments and code are untouched! ;-)

Click here for source.
 
I can't understand why you didn't get cheers so far for this. Very good job :goodjob:

For sure the code can be improved but the merit is to start something, I always have been mumbling about these firaxians lazy spies that can do almost nothing, but now you open my eyes.

Thanks

btw if you already made the steal technology code I would appreciate (I have little time for this unfortunately) :blush:
 
He man not have got many cheers but I for one had the page book marked. Nice job. If you wan't to write anymore tutorials I know you will have atleast one reader. :mischief:
 
excuse me a question.
But I have cvenum.h under Warlord CvGameCoreDell directory...
how could I implement this for civ4? I mean could I make it working? and modifying which files?
 
This is a really good tut. thank you. I have been working on a design .doc for the last couple of months. Now I want to start turning it into an actual mod. The PROBLEM: I barley understand what a variable is.
Again great work on this, I would love to see more done to this level, especially with the SDK, as I now realize that I am going to have to do a lot in it.
later.
 
This is INCREDIBLY useful, thank you so much for posting this gem.
 
sorry Zafzo, but I finding some prb about a similar modification:
I'm trying to add an integer Tag to ImprovementInfo.xml for just 1 kind of improvement but after compiling everything in the way you rappresent here (obviusly putting the declarations and the get/change/set in the correct place so in other files) when I load the game - loading Xml unchached files - it returns me an error about TerrainInfos.xml.

It refers to GetXml and SetVariables section of terrain infos...
What have gone wrong? (I've added the shoot to this thread - http://forums.civfanatics.com/showthread.php?t=195251 ).

I ask it here becuase seems noone knows the reason of this error and I've recived no replys, maybe you can solve it. More than this I'm complitely stucked with programmation at this point.
Thx.
 
with NO disrespect to Zafzo implied...

has anyone followed this...and did it work? (again no disprespect intneded, just I've used this type of thing (borrowed code/tut's) and nothing worked...and before I spend the time I wanted to make sure this tut was the real deal.)
 
This's been incredibly useful.
However, there's still an issue.

I've created a mission to make a unit create a particular type of building.
After doing that, the unit is killed.
Since that point on, EVERY new unit trained/created/given/addedByEditor appears with a particular animation. If, for example, I create my mission by copying-and-pasting the MISSION_SKIP tag as template, every new units will appear on a 'Skip' status. If I use a MISSION_DISCOVER as template, every new Great Person will be shown as when they discover a tech!! If I move that unit, it will be shown correctly since then.

What am I missing? Am I the only one with such an odd problem??? Help!!
 
Very good tutorial, yes. But it's missing one thing - AI.

If you follow the instructions above you'll just improve the way a human can play. In order to make the game more fair, you need to teach AI to use new missions and that can be a problem.
 
I've followed the above steps, but I'm unable to observe any changes in functionality once in game. Do I also need to make changes to certain python scripts to cause the new button to be a part of the interface, or does the python dynamically cobble together all the appropriate buttons based on the xml?

I also see that the initial topic is 3 years old. Should I be able to create a new mission + button by following this tutorial, at this time?

One more fast question: Is there a built in easy way to display debug information? I'm curious to see if the game is ever entering the code that I added as per the above tutorial.

Thanks a bunch
 
Top Bottom