j_mie6
Deity
A guide to the API and basic python modding by J_mie6
Note: when working with Python ALWAYS have the Python exceptions turned on in your ini. See hidepythonException = 1 and set it to 0!!!
Contents
Python Keywords
Classes
Variable names
Comments
Going Loopy
Final Part
All modders should be familiar with the API (Application Programming Interface) which can be found at this link: Python API
This is a powerful tool which can allow a programmer to create code without diving into the SDK to find python methods. So, how do you use it?
The first thing you need to know about reading the API is what on earth those lovely colourful words at the side of each method mean:
INT – Integer, this means that the method returns a whole number (no decimals). Uses a get prefix
BOOL – This means that the method returns a value of True or False (1 or 0 respectively), it stands for Boolean Value. Uses an is prefix
VOID – This means the method returns nothing or None and is characterised by a set or another command prefix on the method.
STRING – This means the method returns a string. A string is a line of characters attached like a 'string'. It is usually used when getting description of objects. Also uses a get prefix.
FLOAT – This is pretty much the same as INT except it is a decimal (a floating decimal point). A float must always contain a decimal point (e.g. 3.0) and uses the get prefix on its methods.
CLASS – This name will never actually be CLASS so look out for the colour, it means the method returns an instance of a class (like a unit type)
TYPE – This is also never actually going to be TYPE but will have it at the end e.g. ColourType. There is a list of the types of civ4 listed here!
TUPLE – This means the method returns a tuple of objects in parenthesis for example (0, 0) is a tuple.
LIST – pretty much the same as a tuple but it has square parenthesis [0, 0]. The difference between a list and a tuple is that a list’s elements can be reassigned (mutable) i.e.:
Code:
a = [[COLOR="Green"]"a"[/COLOR], [COLOR="Green"]"b"[/COLOR], [COLOR="Green"]"c"[/COLOR]]
a[0] = [COLOR="Green"]"d"[/COLOR]
This means that variable a will be: ["d", "b", "c"]. Try this with a tuple and it will complain

LONG – this is an INT but just well, longer! It can store more digits inside.
Python keywords
Throughout that last section you were probably wondering what 'method' or 'returns' is. Well here is a list of keywords that you may encounter in this guide.
Functions and Modules – A function is a piece of code that is used to both order a block of code and also to cut down on repetitiveness. A module is a file where code and functions get put. Think of functions and modules like a new office desk, before they come along the desk is full of messy paper full of post-it notes (comments). The module is like a filing cabinet and it keeps things nice and ordered. The functions are both the separators in the cabinet and also some ink stamps which can be used again and again to replicate an image (instead of doing it by hand again).
A Civ4 example of a function is typically a modder has made to help them:
Code:
[COLOR="DarkOrange"]def[/COLOR] [COLOR="Blue"]getPlot[/COLOR](tCoords):
[COLOR="Green"]"""
Returns the CyPlot instance of the plot at tCoords.
"""[/COLOR]
iX, iY = tCoords
[COLOR="DarkOrange"]return[/COLOR] Map.plot(iX, iY)
Method – a method is like a function but is called from as part of a class for example
Code:
CyGlobalContext().getInfoTypeForString([COLOR="Green"]"UNIT_ARCHER"[/COLOR])
[COLOR="Red"]#this returns the number of the archer unit, note how it is called on an instance of CyGlobalContext() this is what makes it a method. Calling this from CyUnit() would return errors because that is not the class it belongs to![/COLOR]
Returns – when a function or method returns something it gives it back to the main code for example
Code:
[COLOR="DarkOrange"]def[/COLOR] [COLOR="Blue"]function[/COLOR](x, y):
[COLOR="DarkOrange"]return[/COLOR] x + y
z = function(1, 2)
[COLOR="DarkOrange"]print[/COLOR] z
[COLOR="Red"]#prints to console: [/COLOR]
3
Above you can see how variable z was assigned to function(1, 2). When this happened function assigns z the value of 1 + 2 (3) because that is what it returns. That’s why when z is printed, 3 is outputted to the console.
Call – when a function or method is called it is being executed from the main code.
Syntax – The grammar of code, not following it will cause you big problems and your code will fail.
Argument – an argument contains the data you want to pass into the function. In the example of returning above the x and y would have been the arguments.
Classes
The first step is to work out what you need. There are many classes that these methods fit into and they make sense really. What to know if a city has a building then you will need to look for a method in the CyCity class. The classes are the most important part of python in CivIV. Most of the important classes are highlighted in bold on the quick bar (see below) but two of the most important classes are CyGame() and CyGlobalContext(). The CyGame() class is important because it contains methods that can be very useful such as the getSorenRandNum() method. The CyGlobalContext() class is also important as it contains one of the most needed methods in modding getInfoTypeForString() which is used like this:
Code:
eArcher = gc.getInfoTypeForString([COLOR="Green"]"UNIT_ARCHER"[/COLOR])
You can then use eArcher in unit spawning codes! It is used to collect the xml strings from the xml itself and use them into INTs that can then be referenced later. It can take any xml type as a string be it "BUILDING_PALACE" or "CIVILIZATION_ROME" (note this is the CivilizationInfo returned not a PlayerType!). Make sure to use this instead of just numbers as it will work even if new types are added! This class also contains the getActivePlayer() method which is used to get the current human. A good practise with classes you intend to use without creating a new object each time is to define it as a constant in your code
Code:
gc = CyGlobalContext()
Game = CyGame()
Map = CyMap()
After you know where you are meant to be looking for things you are ready to navigate the API itself, Along the side is a contents of sorts. You can use it to quickly zoom to a class of your choice. Note the box in the bottom left hand corner. If during your time in the API you come across ColourType etc then clicking on it will display the numbers of the different types in the box in the corner. And alternate method for using types will be outlined later on.
When you are reading a method you have collected from the API it is very important that you pay attention to the types of arguments and what they are, it isn’t good to get them wrong or python will tell you that you aren’t using the correct C++ signature (syntax to you and me). When looking at the method in the API you should look in front of every argument to see what the type is, if it says BOOL make sure you don’t go and put in "True" instead of True.
Code:
>> [COLOR="Purple"]type[/COLOR]([COLOR="Green"]"True"[/COLOR])
[COLOR="Blue"]<type 'string'>[/COLOR]
>> [COLOR="Purple"]type[/COLOR]([COLOR="Purple"]True[/COLOR])
[COLOR="Blue"]<type 'bool'>[/COLOR]
The name of the method can tell you a lot about the method but don’t take it for granted. If there is a description beneath the method in smaller text make sure you read it! You could have the wrong method.
Variable Naming Conventions
When making your own code it is important for you to use proper naming conventions for your variables. This is so that you don’t get confused and other programmers reading your code know quickly what a variable represents. First things first, there is a little thing called Hungarian notation. It is where the word of a variable is in lower case ie:
Code:
iVar = 1
numOfCities = 3
Code:
iVar = 1 [COLOR="Red"]#this variable represents a whole number[/COLOR]
sVar = [COLOR="Green"]"hi" [/COLOR] [COLOR="Red"]#this variable represents a string[/COLOR]
eVar = gc.getInfoTypeForString([COLOR="Green"]"UNIT_ARCHER"[/COLOR]) [COLOR="Red"]#this variable represents an enumerated value (this means it is constant, the Archer will always be the value it is set as) which is a type.[/COLOR]
lVars = [0, 1, 2, 3] [COLOR="Red"]#this variable represents a list of variables[/COLOR]
tVars = (52, 84) [COLOR="Red"]#this variable represents a tuple of variables[/COLOR]
dVars = {0 : [COLOR="Green"]"a"[/COLOR], 1 : [COLOR="Green"]"b"[/COLOR], 2 : [COLOR="Green"]"c"[/COLOR]} [COLOR="Red"]#this variable represents a dictionary[/COLOR]
bVar = [COLOR="Purple"]True[/COLOR] [COLOR="Red"]#this variable represents a Boolean value (True/False) [/COLOR]
fVar = 3.0 [COLOR="Red"]#this variable represents a Float (decimal) number[/COLOR]
pVar = CyUnit()[COLOR="Red"]#this points to an instance of a class[/COLOR]
So with these in mind, if I had a block of code where I was finding the team of the human player it would look a little like this:
Code:
Game = CyGame()
gc = CyGlobalContext()
iHuman = Game.getActivePlayer()
pHuman = gc.getPlayer(iHuman)
iHumanTeam = pHuman.getTeam()
pHumanTeam = gc.getTeam(iHumanTeam)

Comments
Note how the previous block of code can also be expressed as:
Code:
iHumanTeam = CyGlobalContext().getPlayer(CyGame().getActivePlayer()).getTeam()
Not very readable is it? It's always good practice to break up lines like this into many variables to make code easy to understand for you and anybody who wants to use your code. Another way to ensure readability is using comments and docstrings, as well as using variables names that make sense!
Here's a simple example before we tackle something a bit scarier from my own mod.
Code:
[COLOR="DarkOrange"]def[/COLOR] [COLOR="Blue"]turkey[/COLOR](*args):
iOwner, pCity = args
iTurkeyConvertPercent = 75
if iOwner == eTurkey:
iForeignCultureToConvert = (((pCity.countTotalCultureTimes100() - pCity.getCultureTimes100(eTurkey))/100) * iTurkeyConvertPercent)
iCurrentTurkishCulture = pCity.getCultureTimes100(eTurkey)
iTurkishCulture = iForeignCultureToConvert + iCurrentTurkishCulturepCity.setCultureTimes100(eTurkey, iTurkishCulture, [COLOR="Purple"]True[/COLOR])
Although this code is pretty easy to understand you as a programmer might want to add further details to explain to other programmers or to remind yourself what the code does!
Code:
[COLOR="DarkOrange"]def[/COLOR] [COLOR="Blue"]turkey[/COLOR](*args): [COLOR="Red"]#onCityAcquiredAndKept (this comment tells me where to call the function)[/COLOR]
[COLOR="Green"]"""Power of Culture - Majority of foreign culture converted to turkish"""[/COLOR]
iOwner, pCity = args [COLOR="Red"]#*args in a function takes unlimited arguements, though really this function takes two iOwner and pCity from the EventManager's onCityAquiredAndKept![/COLOR]
iTurkeyConvertPercent = 75 [COLOR="Red"]#define the amount of culture to convert[/COLOR]
if iOwner == eTurkey: [COLOR="Red"]#if the owner of the city is turkey[/COLOR]
iForeignCultureToConvert = (((pCity.countTotalCultureTimes100() - pCity.getCultureTimes100(eTurkey))/100) * iTurkeyConvertPercent) [COLOR="Red"]#get the percentage of foreign culture to convert[/COLOR]
iCurrentTurkishCulture = pCity.getCultureTimes100(eTurkey) [COLOR="Red"]#get turkey's influence in the city[/COLOR]
iTurkishCulture = iForeignCultureToConvert + iCurrentTurkishCulture [COLOR="Red"]#add them together[/COLOR]
pCity.setCultureTimes100(eTurkey, iTurkishCulture, [COLOR="Purple"]True[/COLOR]) [COLOR="Red"]#adjust the city's culture accordingly[/COLOR]
This looks a little better now!
Also consider this code:
Code:
[COLOR="DarkOrange"]def[/COLOR] [COLOR="Blue"]countWorkingTilesForTypeInCity[/COLOR](pCity, eTerrain):
i = 0
[COLOR="DarkOrange"]for[/COLOR] x [COLOR="DarkOrange"]in[/COLOR] [COLOR="Purple"]xrange[/COLOR](21):
pPlot = pCity.getCityIndexPlot(x)
[COLOR="DarkOrange"]if[/COLOR] (pPlot.isBeingWorked()[COLOR="DarkOrange"]and[/COLOR] pCity.canWork(pPlot)) [COLOR="DarkOrange"]or[/COLOR] pPlot.isCity():
[COLOR="DarkOrange"]if[/COLOR] pPlot.getTerrainType() == eTerrain:
i += 1
[COLOR="DarkOrange"]return[/COLOR] i
[COLOR="DarkOrange"]def[/COLOR] [COLOR="Blue"]countWorkingTilesYieldInCity[/COLOR](pCity, iYield):
i = 0
[COLOR="DarkOrange"]for[/COLOR] x [COLOR="DarkOrange"]in[/COLOR] [COLOR="Purple"]xrange[/COLOR](21):
pPlot = pCity.getCityIndexPlot(x)
[COLOR="DarkOrange"]if[/COLOR] (pPlot.isBeingWorked()[COLOR="DarkOrange"]and[/COLOR] pCity.canWork(pPlot)) [COLOR="DarkOrange"]or[/COLOR] pPlot.isCity():
i += pPlot.getYield(iYield)
[COLOR="DarkOrange"]return[/COLOR] i
[COLOR="DarkOrange"]def[/COLOR] [COLOR="Blue"]egyptPenalty[/COLOR]():
pEgypt = pointer([COLOR="Green"]"Egypt"[/COLOR], CyPlayer)
(loopCity, [COLOR="Purple"]iter[/COLOR]) = pEgypt.firstCity([COLOR="Purple"]False[/COLOR])
[COLOR="DarkOrange"]while[/COLOR] (loopCity):
pCity = loopCity
iCurrentPlains = countWorkingTilesForTypeInCity(pCity, eEgyptianTerrain)
iCurrentCityYield = countWorkingTilesYieldInCity(pCity, eProduction)
iProductionForPlains = gc.getTerrainInfo(eEgyptianTerrain).getYield(eProduction)
iDifference = iCurrentCityYield -[COLOR="Purple"] int[/COLOR] ([COLOR="Purple"]round[/COLOR] (iCurrentPlains * [COLOR="Purple"]float[/COLOR](iProductionForPlains)/2))
pCity.setBaseYieldRate(eProduction, iDifference)
(loopCity, [COLOR="Purple"]iter[/COLOR]) = pEgypt.nextCity([COLOR="Purple"]iter[/COLOR], [COLOR="Purple"]False[/COLOR])
It’s pretty difficult for somebody new to this code to figure out what it is doing. So what you as a programmer should really be doing is providing helpful hints along the way. This is done with the use of a comment (which starts with # everything past that on the same line is ignored!)
Code:
[COLOR="orange"]def[/COLOR] [COLOR="blue"]countWorkingTilesForTypeInCity[/COLOR](pCity, eTerrain):
[COLOR="green"] """
Counts the number of tiles that the city pCity (CyCity) is working of eTerrain (TerrainType)
"""[/COLOR]
i = 0 [COLOR="Red"]#declare variable to hold the number of tiles[/COLOR]
[COLOR="orange"]for[/COLOR] x [COLOR="orange"]in[/COLOR] xrange(21): [COLOR="Red"]#cycle through numbers 0 to 20 and perform below operations on them[/COLOR]
pPlot = pCity.getCityIndexPlot(x) [COLOR="red"]#accesses the city plot x (where x is cycled through 0 to 20)[/COLOR]
[COLOR="orange"]if[/COLOR] (pPlot.isBeingWorked() [COLOR="orange"]and[/COLOR] pCity.canWork(pPlot)) [COLOR="orange"]or[/COLOR] pPlot.isCity(): [COLOR="red"]#if the plot is worked (by that city!) or the plot itself is a city[/COLOR]
[COLOR="orange"]if[/COLOR] pPlot.getTerrainType() == eTerrain: [COLOR="red"]#and the terrain matched the terrain specified[/COLOR]
i += 1 [COLOR="red"]#make i equal to i + 1[/COLOR]
[COLOR="orange"]return[/COLOR] i [COLOR="red"]#returns number of working types of type eTerrain[/COLOR]
[COLOR="orange"]def[/COLOR] [COLOR="blue"]countWorkingTilesYieldInCity[/COLOR](pCity, iYield):
[COLOR="Green"] """
Counts the total Yield of type specified by iYield (food, hammers, commerce) of the tiles worked by the city
"""[/COLOR]
i = 0 [COLOR="red"]#declare variable to hold the total yield[/COLOR]
[COLOR="orange"]for[/COLOR] x [COLOR="orange"]in[/COLOR] xrange(21): [COLOR="Red"]#cycle through numbers 0 to 20 and perform below operations on them[/COLOR]
pPlot = pCity.getCityIndexPlot(x) [COLOR="red"]#accesses the city plot x (where x is cycled through 0 to 20)[/COLOR]
[COLOR="orange"]if[/COLOR] (pPlot.isBeingWorked() [COLOR="orange"]and[/COLOR] pCity.canWork(pPlot)) [COLOR="orange"]or[/COLOR] pPlot.isCity(): [COLOR="red"]#if the plot is worked (by that city!) or the plot itself is a city[/COLOR]
i += pPlot.getYield(iYield) [COLOR="red"]#then get the yield of the tile and increase i by that much[/COLOR]
[COLOR="orange"]return[/COLOR] i [COLOR="red"]#return the total yield of type eYield from the city[/COLOR]
[COLOR="orange"]def[/COLOR] [COLOR="blue"]egyptPenalty[/COLOR]():
[COLOR="Green"] """
In order to balance Egypt's power 0.5 of a hammer is removed for every plains tile worked. Rounded up.
"""[/COLOR]
pEgypt = pointer([COLOR="Green"]"Egypt"[/COLOR], CyPlayer) [COLOR="red"]#Get a pointer for Egpyt (this is using Baldyr's incredible CivPlayer utility[/COLOR]
(loopCity, [COLOR="Purple"]iter[/COLOR]) = pEgypt.firstCity([COLOR="Purple"]False[/COLOR]) [COLOR="red"]#get egypt's first city[/COLOR]
[COLOR="Orange"]while[/COLOR] (loopCity): [COLOR="red"]#while there is actually a city[/COLOR]
pCity = loopCity [COLOR="Red"]#assign pCity to the loopCity value[/COLOR]
iCurrentPlains = countWorkingTilesForTypeInCity(pCity, eEgyptianTerrain) [COLOR="red"]#use function to calculate number of plains worked by city[/COLOR]
iCurrentCityYield = countWorkingTilesYieldInCity(pCity, eProduction) [COLOR="red"]#use function to calculate the total hammer yield in city[/COLOR]
iProductionForPlains = gc.getTerrainInfo(eEgyptianTerrain).getYield(eProduction) [COLOR="red"]#how many hammers is a plains tile worth?[/COLOR]
iDifference = iCurrentCityYield - [COLOR="Purple"]int[/COLOR] ([COLOR="Purple"]round[/COLOR] (iCurrentPlains * [COLOR="Purple"]float[/COLOR] (iProductionForPlains)/2)) [COLOR="red"]#times the number of plains by the hammers they are worth and half it. then remove it from city's yield[/COLOR]
pCity.setBaseYieldRate(eProduction, iDifference) [COLOR="red"]#make the city's yield for hammers equal to the new value iDifference[/COLOR]
(loopCity, [COLOR="Purple"]iter[/COLOR]) = pEgypt.nextCity([COLOR="Purple"]iter[/COLOR], [COLOR="Purple"]False[/COLOR]) [COLOR="red"]#move on to the next city[/COLOR]
Muuuuuch better. Now we can better understand what is happening here. Note the triple quotation marks: that is a docstring and allows functions and methods to be given a description. If any more experienced python programmers are reading and think why did he not use:
Code:
[COLOR="DarkOrange"]for[/COLOR] iCity [COLOR="DarkOrange"]in[/COLOR] [COLOR="Purple"]xrange[/COLOR](pEgpyt.getNumCities()):
pCity = pEgypt.getCity(iCity)
Here is a note from fellow pythonista Platyping on that problem when I made it ages ago:
Be careful when iterating over cities!!! He also elaborates on the cause:[that code] is a bad idea. When cities are conquered, there will still be a empty slot left behind, resulting in those cities in the last few entries skipped.
Example:
I have 5 cities, A to E
City 0 refers to A
City 4 refers to E
numCities = 5
Now C is conquered
City 0 = A
City 1 = B
City 2 = None
City 3 = D
City 4 = E
numCities = 4
So using for loop, you will skip City E now.
However, building a new city F will fill up the empty slot.
Going Loopy
After that last discussion on the vices of using for loops and cities (and units for that matter), it’s probably about time to talk about control loops. There are two main types of loop: For in loop and While loop. You may need to access both types of loop during your python career. For example a while loop can be used to iterate over a players cities or units. Let’s see how while loops work:
Code:
x = 1 [COLOR="Red"]#make a new variable x[/COLOR]
[COLOR="DarkOrange"]while[/COLOR] x <= 10: [COLOR="Red"]#so long as x <= 10 is true, do the condition inside the loop[/COLOR]
[COLOR="DarkOrange"]print[/COLOR] x [COLOR="Red"]#print out x to the console[/COLOR]
x += 1 [COLOR="Red"]#make x equal to x + 1[/COLOR]
[COLOR="Red"]#this prints to the console: [/COLOR]
1
2
3
4
5
6
7
8
9
10[COLOR="Red"] #doesn’t go any further as x is now 11. [/COLOR]
The pseudo code for a while loop is:
Code:
while condition:
do something
In contrast a for loop iterates over something. In the case of the plots of a city we said:
Code:
[COLOR="DarkOrange"]for[/COLOR] x [COLOR="DarkOrange"]in[/COLOR] [COLOR="Purple"]xrange[/COLOR](21):
Let’s dissect this line of code. First we say “for x” this creates a variable x and uses it to store the results of each iteration. Next we say “in xrange(21)”. The xrange(n) or range(n) functions return a list of all the numbers between 0 and n-1 (including those numbers). The in specifies that x takes values from the xrange(21) statement. On the first iteration the for loop takes the 0th value of xrange(21) (which is 0) and passes it to x. The second iteration takes the 1st value of xrange(21) which happens to be 1
So the while loop number print code could have been written as:
Code:
[COLOR="DarkOrange"]for[/COLOR] x [COLOR="DarkOrange"]in[/COLOR] [COLOR="Purple"]xrange[/COLOR](10): [COLOR="Red"]#assign x to a value in range of 0 to 9[/COLOR]
[COLOR="DarkOrange"]print[/COLOR] x + 1 [COLOR="Red"]#print x + 1, this causes the program to print values between 1 and 10 instead of 0 and 9. [/COLOR]
What’s the difference between xrange and range? Well range provides a physical list (ie: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] whereas xrange provides an iterator. This means that xrange is faster and more memory efficient. It should be used whenever you would want to use range inside a for loop. Outside a for loop xrange is useful only when you know how to use iterators! Note that iterators do not allow the accessing of a specific element, it has to be iterated through using it's next() method!
Of course we could put in our own list into a for loop as well:
Code:
[COLOR="DarkOrange"]for[/COLOR] fruit [COLOR="DarkOrange"]in[/COLOR] [[COLOR="Green"]"apple"[/COLOR], [COLOR="Green"]"pear"[/COLOR], [COLOR="Green"]"grapes"[/COLOR]]:
[COLOR="DarkOrange"]print[/COLOR] fruit
This prints to the console:
apple
pear
grapes
as we would expect.
Final Part – Making a simple function – Part 1
This pretty much concluded the basic guide to python concepts used in civ4, but it really needs to put what we learnt into a civ4 perspective!
First off let’s make a function to spawn a unit of type eUnit for every excess happy face in the city!
The first thing we want to do here is go the API and find some methods to use, we need the following methods:
- A Method to get the number of happy faces in the City
- A Method to create units
Next let's look in CyPlayer (as a unit belongs to a player) and we find the method 321 useful:
We will look into this function when writing the function but we can see we need to supply a type, coords an AI and a direction!CyUnit initUnit (UnitType iIndex, INT iX, INT iY, UnitAIType eUnitAI, DirectionType eFacingDirection)
NOTE: Always use UnitAITypes.NO_UNITAI
Code:
[COLOR="DarkOrange"]def[/COLOR] [COLOR="Blue"]spawnUnitForEveryHappy[/COLOR](eUnit, pCity):
[COLOR="Green"]"""
spawns one unit of type eUnit for every happy point in pCity
"""[/COLOR]
iX, iY = pCity.getX(), pCity.getY() [COLOR="Red"]#double assignment[/COLOR]
so first we defined a function with two arguments: eUnit and pCity. we added a docstring to say what it does. Then we made two variables iX and iY, and did an assignment to pCity.getX() and getY() respectively!
Code:
iNumUnits = pCity.happyLevel() [COLOR="Red"]#get the cities happy level and assign it to iNumUnits[/COLOR]
[COLOR="DarkOrange"]for[/COLOR] i [COLOR="DarkOrange"]in[/COLOR] [COLOR="Purple"]xrange[/COLOR](iNumUnits): [COLOR="Red"]#perform the loop iNumUnits times for iNumUnits to spawn[/COLOR]
pPlayer.initUnit(eUnit, iX, iY, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION) [COLOR="Red"]#spawn a Unit! note we did as the API said and set the UnitAITypes to NO_UNITAI and the direction to NO_DIRECTION[/COLOR]
Now our methods come into play. Note we called happyLevel() on an instance of CyCity, as it was found in the API. Then we created a loop so that we can spawn iNumUnits of units! And then we created a unit. Our final code:
Code:
[COLOR="DarkOrange"]def[/COLOR] [COLOR="Blue"]spawnUnitForEveryHappy[/COLOR](eUnit, pCity):
[COLOR="Green"]"""
spawns one unit of type eUnit for every happy point in pCity
"""[/COLOR]
iX, iY = pCity.getX(), pCity.getY()
iNumUnits = pCity.happyLevel()
[COLOR="DarkOrange"]for[/COLOR] i [COLOR="DarkOrange"]in[/COLOR] [COLOR="Purple"]xrange[/COLOR](iNumUnits):
pPlayer.initUnit(eUnit, iX, iY, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION)
and it is called by this code:
Code:
gc = CyGlobalContext() [COLOR="Red"]#initialize an instance of CyGlobalContext to use with methods[/COLOR]
eSwordsman = gc.getInfoTypeForString([COLOR="Green"]"UNIT_SWORDSMAN"[/COLOR]) [COLOR="Red"]#assign eSwordsman to represent The swordsman in the XML[/COLOR]
pCity = gc.getActivePlayer().getCapitalCity()[COLOR="Red"]#get the human player (getActivePlayer) and his capital and assign it to pCity[/COLOR]
spawnUnitForEveryHappy(eSwordmans, pCity) [COLOR="Red"]#spawn a Swordsman for every happy point in the human's capital! [/COLOR]
End of Part 1!