1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

A guide to the API and basic python modding by J_mie6

Discussion in 'Civ4 - Modding Tutorials & Reference' started by j_mie6, Aug 17, 2012.

  1. j_mie6

    j_mie6 Deity

    Joined:
    Dec 20, 2009
    Messages:
    2,963
    Location:
    Bristol (uni)/Swindon (home)
    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 :p

    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&#8217;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&#8217;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
    
    This lowercase word is typically the type of variable it is:
    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)
    
    Lovely isn't it :D Note how pHuman can have methods called on it because it is a pointer to the human's CyPlayer instance.

    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&#8217;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:
    Going Loopy

    After that last discussion on the vices of using for loops and cities (and units for that matter), it&#8217;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&#8217;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&#8217;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&#8217;s dissect this line of code. First we say &#8220;for x&#8221; this creates a variable x and uses it to store the results of each iteration. Next we say &#8220;in xrange(21)&#8221;. 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&#8217;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 &#8211; Making a simple function &#8211; 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&#8217;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
    First off let's look into the CyCity Class, have a look down at number 304, CyCity.happyLevel(). We can safely assume that this will give us the happy level of the city!
    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!

    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!
     
  2. j_mie6

    j_mie6 Deity

    Joined:
    Dec 20, 2009
    Messages:
    2,963
    Location:
    Bristol (uni)/Swindon (home)
    Final Part &#8211; Part 2

    So now let&#8217;s use our knowledge to make a small script that checks every plot of every city of every city and counts the number of cows! We will do this as a function aswell (you never know when you might need this :p) so let's start!

    Code:
    [COLOR="DarkOrange"]def[/COLOR] [COLOR="Blue"]countCowsInEveryCityRadius[/COLOR] ():
        [COLOR="Green"]"""
        Counts all the cow bonuses that are in the BFC of every city in the world (sorted by civs).
        Note that the civ checks are not really necessary but they are used to highlight the for loop!
        """[/COLOR]
    
        iNumCows = 0 [COLOR="Red"]#Create a variable to store the number of cows[/COLOR]
        gc = CyGlobalContext()[COLOR="Red"] #create our CyGlobalContext instance to use for methods later[/COLOR]
        Game = CyGame()[COLOR="Red"]#create our CyGame instance to use for methods later[/COLOR]
        eCow = gc.getInfoTypeForString([COLOR="Green"]"BONUS_COW"[/COLOR]) [COLOR="Red"]#create an enum to represent the cows :D[/COLOR]
    

    Let's start off with the above code. we have defined our function, given it 0 arguments and documented its purpose. Next we define a few variables to help us. iNumCows will be what we return at the end of the function and the next two variables will be used to access various important civ4 methods! The final one actually represents the cows so we can see if a plot has one!

    Next lets continue by going through all the players in the game that are living.

    Code:
        [COLOR="DarkOrange"]for[/COLOR] iPlayer [COLOR="DarkOrange"]in[/COLOR] [COLOR="Purple"]xrange[/COLOR](Game.countCivPlayersEverAlive()):[COLOR="Red"]#we could have used countCivPlayersAlive() but I wanted to highlight the next line[/COLOR]
            pPlayer = gc.getPlayer(iPlayer) [COLOR="Red"]#get a CyPlayer instance of the player who has index iPlayer[/COLOR]
           [COLOR="DarkOrange"]if not[/COLOR] pPlayer.isAlive(): [COLOR="Red"]#if the player is dead[/COLOR]
                [COLOR="DarkOrange"]continue[/COLOR] [COLOR="Red"]#then skip the player and move on to the next one, he clearly doesn't have any cities! [/COLOR]

    What we did here was went through each player in the game and got their CyPlayer instance (what a player is actually made of) and checked if he is alive. If he was dead we use the keyword continue. continue means the iteration is skipped and goes onto the next player NOT continues with the code :D The other important loop keyword is break. break will completely stop the for loop!

    Next lets go through each city of this living player and start to iterate through the plots!

    Code:
            (loopCity, [COLOR="Purple"]iter[/COLOR]) = pPlayer.firstCity([COLOR="Purple"]False[/COLOR]) [COLOR="Red"]#assign loopCity to the first city owned by the player! If you look in the API the False argument here represents whether it should be reversed (ie take the last city) [/COLOR]
            [COLOR="DarkOrange"]while[/COLOR] (loopCity): [COLOR="Red"]#while there is actually a city to go onto! [/COLOR]
                [COLOR="DarkOrange"]for[/COLOR] iIndex [COLOR="DarkOrange"]in[/COLOR] [COLOR="Purple"]xrange[/COLOR] (21): [COLOR="Red"]#why 21? Well there is a total of 21 plots in a city's BFC![/COLOR]
                    pPlot = loopCity.getCityIndexPlot(iIndex) [COLOR="Red"]#get the plot at iIndex and assign it to pPlot (CyPlot)! [/COLOR]
    

    So we have gone through and got the player's cities and started to go through each plot. What happens next is we have to check to see if the plot has a cow, if it does record it!

    Code:
                   [COLOR="DarkOrange"]if[/COLOR] pPlot.getBonusType(-1) == eCow [COLOR="DarkOrange"]or[/COLOR] pPlot.getBonusType(pPlayer.getTeam()) == eCow: [COLOR="Red"]#does the plot have an unowned cow or a cow owned by the city's owner, pPlayer? the -1 here represents noteam as stated in the TeamTypes in the API[/COLOR]
                        iNumCows += 1 [COLOR="Red"]#add one to the num of cows. [/COLOR]
    

    Now we need to start wrapping up the loose ends here! for instance, loopCity can't move on to the next city and is causing an infinite loop! and the function doesn't return the number of cows!

    Code:
                (loopCity, [COLOR="Purple"]iter[/COLOR]) = pPlayer.nextCity([COLOR="Purple"]iter[/COLOR], [COLOR="Purple"]False[/COLOR]) [COLOR="Red"]#move onto the next City[/COLOR]
        [COLOR="DarkOrange"]return[/COLOR] iNumCows [COLOR="Red"]#return the number of cows! [/COLOR]
    

    Whew we are done. This is the full code (without comments):

    Code:
    [COLOR="DarkOrange"]def[/COLOR] [COLOR="Blue"]countCowsInEveryCityRadius[/COLOR] ():
        [COLOR="Green"]"""
        Counts all the cow bonuses that are in the BFC of every city in the world (sorted by civs).
        Note that the civ checks are not really necessary but they are used to highlight the for loop!
        """[/COLOR]
    
        iNumCows = 0
        gc = CyGlobalContext() 
        Game = CyGame
        eCow = gc.getInfoTypeForString([COLOR="Green"]"BONUS_COW"[/COLOR]) 
        [COLOR="DarkOrange"]for[/COLOR] iPlayer [COLOR="DarkOrange"]in[/COLOR] [COLOR="Purple"]xrange[/COLOR](Game.countCivPlayersEverAlive()): 
            pPlayer = gc.getPlayer(iPlayer)
            [COLOR="DarkOrange"]if not[/COLOR] pPlayer.isAlive(): 
                [COLOR="DarkOrange"]continue[/COLOR] 
            (loopCity, [COLOR="Purple"]iter[/COLOR]) = pPlayer.firstCity([COLOR="Purple"]False[/COLOR])
            [COLOR="DarkOrange"]while[/COLOR] (loopCity): 
                [COLOR="DarkOrange"]for[/COLOR] iIndex [COLOR="DarkOrange"]in[/COLOR] [COLOR="Purple"]xrange[/COLOR](21): 
                    pPlot = loopCity.getCityIndexPlot(iIndex) 
                    [COLOR="DarkOrange"]if[/COLOR] pPlot.getBonusType(-1) == eCow [COLOR="DarkOrange"]or[/COLOR] pPlot.getBonusType(pPlayer.getTeam()) == eCow: 
                        iNumCows += 1 
                (loopCity, [COLOR="Purple"]iter[/COLOR]) = pPlayer.nextCity([COLOR="Purple"]iter[/COLOR], [COLOR="Purple"]False[/COLOR]) 
        [COLOR="DarkOrange"]return[/COLOR] iNumCows 
    

    Note that the lines

    Code:
    [COLOR="DarkOrange"]if not[/COLOR] pPlayer.isAlive():
        [COLOR="DarkOrange"]continue[/COLOR]
    

    could have been written:

    Code:
     [COLOR="DarkOrange"]if not [/COLOR]pPlayer.isAlive():[COLOR="DarkOrange"]continue[/COLOR] 

    This is code shorthand and can be used when a block is only one line long, like here!

    As a final note, I want to talk about the two most important files in the Python Modding world: CvEventManager.py and CvGameUtils.py, These files allow you as a modder to actually implement your code and put it to good use. Here is a short explanation on each one:

    CvEventManager

    This file is a large class built up of events. These events range from the start of a turn to when a city&#8217;s culture expands! Code that you put in these events will be executed when that event is triggered, for example if we put the cow finding function in a file called ModFunctions.py in the same directory as CvEventManager.py then this is how we use it:

    First at the top of CvEventManager we need to import ModFunctions so Civ4 can use it:

    Code:
    [COLOR="DarkOrange"]import[/COLOR] CvTechChooser [COLOR="Red"]#this will already be here![/COLOR]
    [COLOR="DarkOrange"]import[/COLOR] ModFunctions
    [COLOR="DarkOrange"]from[/COLOR] Popup [COLOR="DarkOrange"]import[/COLOR] PyPopup [COLOR="Red"]#we want this to be added aswell, you will see why![/COLOR] 
    

    See that we put the statement underneath the already existing statement that imports the CvTechChooser.

    Next let's go down to onBeginGameTurn() in the file and add in code (in bold):

    Code:
    	[COLOR="DarkOrange"]def[/COLOR] [COLOR="Blue"]onBeginGameTurn[/COLOR](self, argsList):
    		[COLOR="Green"]'Called at the beginning of the end of each turn'[/COLOR]
    		iGameTurn = argsList[0]
    
    		[b][COLOR="Red"]#MyMods edits start[/COLOR]
    		[COLOR="DarkOrange"]if[/COLOR] ModsFunctions.countCowsInEveryCityRadius() > 50: [COLOR="Red"]#if the number of cows is greater than 50[/COLOR]
    
                        sHeader = CyTranslator.getText([COLOR="Green"]"TXT_KEY_MOD_COW_EVENT_HEADER"[/COLOR], ()) [COLOR="Red"]#the header, this is defined in the Text files of your Mod! The translator turns the string into the XML definition![/COLOR]
                        sMessage = CyTranslator.getText([COLOR="Green"]"TXT_KEY_MOD_COW_EVENT_MESSAGE"[/COLOR], ()) [COLOR="Red"]#the message, this is defined in the Text files of your Mod! The translator turns the string into the XML definition![/COLOR]
    
                        pPopup = PyPopup() [COLOR="Red"]#create a new Popup[/COLOR]
                        pPopup.setHeaderString(sHeader) [COLOR="Red"]#set the header[/COLOR]
                        pPopup.setBodyString(sMessage) [COLOR="Red"]#set the message[/COLOR]
                        pPopup.launch() [COLOR="Red"]#make it show up on screen!
    		#MyMods edits end[/b][/COLOR]
    
    		CvTopCivs.CvTopCivs().turnChecker(iGameTurn)
    

    What we did here is checked to see if the number of city radius cows was bigger than 50. If it was, we created a Popup to display the event! What we also did is used the CyTranslator to turn a String that represents the XML text tag and turn it into the contained text ie:

    Code:
    sArcher = CyTranslator([COLOR="Green"]"TXT_KEY_UNIT_ARCHER"[/COLOR], ()) [COLOR="Red"]#sArcher is actually "Archer"![/COLOR]
    
    CvGameUtils.py

    This file is a little different and many of the Callbacks it utilises are disabled by default. If you need one then you need to enable it in the PythonCallbackDefines.XML file. Here is an example of how it is used:

    Code:
    	[COLOR="DarkOrange"]def[/COLOR] [COLOR="Blue"]canDoCivic[/COLOR](self,argsList):
    		ePlayer = argsList[0]
    		eCivic = argsList[1]
    		[COLOR="DarkOrange"]return[/COLOR] ePlayer == eGreece [COLOR="DarkOrange"]and[/COLOR] eCivic == eGreeceCivic [COLOR="Red"]#This is the edited line, was originally return False. eGreece and eGreeceCivic defined elsewhere![/COLOR]
    
    So what&#8217;s this doing? Well if the player is Greece (as defined by the scenario the mod uses) and the civic they are trying to swap into is eGreeceCivic then let them. It allows Greece to adopt that civic even if they don't have the Tech! There is a little more to adding code to the CvGameUtils if you are using custom modules like CivPlayer which I won't cover here so try not to use Custom Modules in here if you don't know how. But Modules that house functions should be fine! CivPlayer is a special case :p
    On that note, the Guide is finished!

    Thanks to Baldyr and The_J for their input and helping me to learn python!

    Useful links

    Python textbook
    Baldyr&#8217;s Civ4 Python tutorial (more in-depth of civ4 modding than mine)
    Civplayer
    Quick Python Questions Thread - Not made yet! Will update link when it is!

    If you have spotted any mistakes, have any questions or any comments on this guide post them below!

    Jamie
     
  3. Isabelxxx

    Isabelxxx DoaNE Explorer

    Joined:
    Sep 26, 2010
    Messages:
    381
    Since this is supossed to be a tutorial you should explain better the cases where you have to use one or the other. Some may get a bit confused about it.

    It should be noted that since xrange does not provide a list, you can not replace range with xrange in all the cases. There is no place to misunderstandings!

    list = [x for x in range(20)]
    list = list(range(20))
    a = range(20)
    ...

    In fact a good rule: If you ever need to manipulate the entire list or some of their elements, use range; otherwise, use xrange. Therefore xrange should be left for iteration over elements (cities, units, ...).


    Otherwise well done, it really covers anything needed to start if you have curiosity without being too much technical.
     
  4. j_mie6

    j_mie6 Deity

    Joined:
    Dec 20, 2009
    Messages:
    2,963
    Location:
    Bristol (uni)/Swindon (home)
    True enough, I should have really "used whenever you want to use range in a for loop". will edit that in in a bit Thanks!
     
  5. Isabelxxx

    Isabelxxx DoaNE Explorer

    Joined:
    Sep 26, 2010
    Messages:
    381
    If you are still open to suggestions to expand this I would add something more to popups (like buttons and options) as well as expanding some features.

    For ex. you could introduce default arguments as a way to speedup the code and increase the functionality of your functions at the same time.

    Code:
    def countCowsInEveryCityRadius ( eCow = gc.getInfoTypeForString("BONUS_COW") ):
        """
        Counts all the cow bonuses that are in the BFC of every city in the world (sorted by civs).
        Note that the civ checks are not really necessary but they are used to highlight the for loop!
        By adding some constant variables to functions as default arguments you can optimize even more the code, since that line is processed only once (at startup) instead of every time you call the function.
        Also you can now maintain compatibility with the rest of the mod (calling the function without arguments would do the same) at the same time you can expand it:
       Calling countCowsInEveryCityRadius( eCow = gc.getInfoTypeForString("BONUS_CORN")  would apply the same mechanics for corn bonuses for ex. (better to rename variables here...)
    
        """
    
        iNumCows = 0
        gc = CyGlobalContext() 
        Game = CyGame
        #eCow = gc.getInfoTypeForString("BONUS_COW") #Note this is commented since it is defined along the function!
     
  6. j_mie6

    j_mie6 Deity

    Joined:
    Dec 20, 2009
    Messages:
    2,963
    Location:
    Bristol (uni)/Swindon (home)
    hmmm, buttons and stuff in popups I think extends out a bit from basic concepts. But I might be able to add in a section somewhere in the second post later in time.

    authough I do think default arguements should be elarborated on I don't think in that particular function :lol:. maybee I will write up a thrid example to use default arguements etc. Maybe unit spawning loop that has a default number of units. GOnna have to rename the last part to something othert than final part
     

Share This Page