No announcement yet.

Jon Shafer's Guide to Python in Civ4

This is a sticky topic.
  • Filter
  • Time
  • Show
Clear All
new posts

  • Jon Shafer's Guide to Python in Civ4

    No, I haven't developed a split personality disorder and don't think I'm Jon . Apolyton's Modiki has been offline forever now, and it's pretty hard to find a copy of this document. It's a guide to Python in Civ4 by Jon Shafer (aka Trip), written back in 2004, but still very much relevant and highly useful to those who want to begin using Python in Civ4.

    Everything below and in the next post is written by Jon. I am merely reposting.


    Well, I took some time off from working on my own mod projects to work on this, hopefully stirring up a bit more interest among other people. Modding in Python is really not difficult, but getting over the first hump is intimidating - at least it was for me. Now that I've learned it, I know some of the stumbling blocks I hit when trying to learn Python and particularly how it was implimented in CIV. Hopefully this resource will assist other people. If no one uses it and makes mods I'm going to give you all such a pinch. :P

    I'm not really going to go over some of the basic things in programming, since there are better sources out on the net and in the real world than me for that sort of thing. I'm also not going to go over a lot of the stuff in Python itself for the same reason. For basics on both programming and Python itself, check out Gingerbread Man's thread over at CFC, as he's done a fine job in organizing things there.

    What I will cover is a bit of OOP (Object Oriented Programming), as it's not something covered in GBM's lessons too extensively (he has a lesson on classes, but pretty much everything in CIV Python stuff relies on OOP), but I will also go over a few more basic things just to set things up the way I want, as well as for review.

    Basic Python

    If you have some prior programming experience or have gone through GBM's lessons you know that the primary structure of programs involves functions and classes. Classes contain functions and variables and are used to organize ones that are related into 'objects' that can be accessed by calling the class and the variable/function together. I will give examples of this later, but for now it's simply important to know the structure of things.

    One thing that's unique about Python when compared to most languages is that variables do not actually store data, but reference 'objects' (different concept from what we've already covered). In most other languages data is stored in memory and given a type (integer, string, etc.) and label (the name of the variable). In Python data is stored in memory as an object and the type is stored there, while variables simply REFER, or point to the location of that data in memory (known as pointers in other languages). What this means is that when you instantiate, or create a variable, you don't need to set a type, because the type is stored with the object itself and not the variable. There are also other things that differ from other languages when working with pointers instead of data containers, but those already experienced with programming will already know, and those who aren't won't need to know. To keep things fairly simple (for me, mostly) I will simply refer to variables with normal variable terminology rather than treating them specially like the pointers they are.

    Something important to remember in Python is that there is no use of brackets to denote encapsulation - everything is done through indentation. It's important to keep an eye on your indentation and make sure nothing is stray in your code. I like to delete all of the white space at the end of what I'm working with just to make sure nothing is screwed up.

    Classes in Python

    CIV relies heavily on the use of classes, so knowing how to use, manipulate and create them is important. As explained elsewhere, a class is simply created by using the "class Mod:" command, where the name of the class being created is "Mod". Functions are denoted with the "def ExFunction:" command, with "ExFunction" being its name. A function present in most classes is "__init__". This is a special function that is run the first time a class is called by CIV and never again (until the next time the program/mod is run). This is a good place to set up things that you want available later on without having to continually set them up.

    Functions inside classes must always have at least one argument: "self". This refers to the class and allows you to use other parts of the class in that function. However, when CALLING a function you can ignore the argument. For example, a variable named "tempVar" would be created in a class' __init__ function through the command "self.tempVar = x" with "x" being the data you want to store in it. The variable would be referenced in the class's functions in the same way: self.tempVar. The more you use classes (either already created or of your own design) to mod the more you will run across these types of references. To call other functions inside a class it works the same way. A function created through "def testFunction(self):" would be called from another function with the command "self.testFunction()." This is obviously not the way to call functions from other files or classes, but I will get to that shortly.

    Functions always return something, that is, calling them gives you a value. For example, the calling of "Math().getPi()" may return 3.14. In other languages you can denote a function that you don't want to return something (i.e. you just want it to do something) by giving it the type Void, but that's not necessary in Python because as with variables, function types are stored internally. Inside of a function you can use a "return x" to exit the function and replace the function call with the value of "x", or simply say "return 0" to exit - though a return statement is not necessary if you don't want to use one.

    Here's a short example showing the usage of the things I've already presented:

    # File PyTestMod
    # First Class
    class TestClass:
    		# Create First Variable
    		TestVariable = "Test String"
    		def __init__(self):
    				# Change the variable
    				self.TestVariable = "String Test"
    		def TestFunction(self):
    				self.TestVariable = self.FunctionTest()
    		def FunctionTest(self):
    				return "Wheeeeeee"
    This does a few basic things: it creates a class named "TestClass" and an "instance" variable as it's called (meaning a variable belonging to a class) named "TestVariable" and sets it to the value of "Test String". When the "TestClass" is first called __init__ is ran, which changes TestVariable to the value "String Test". At this point, the class is inactive, waiting for an outside call of one of its other functions. If the function "TestFunction" is called from outside, it sets the instance variable TestVariable equal to the value returned by the instance function FunctionTest, which is the string "Wheeeeeee". That's basically all there is to it.

    Now then, calling functions and references variables within a class is fairly easy, but doesn't hold much power. Calling functions in other files and classes is usually much more important, as that is how programmers will have to access CIV data - you may be able to write a great class of your own, but without info from CIV it's not going to do much!

    To access information and functions in another file, that file must first be imported. This is a simple task, completed through the use of the "import" command. If you have a file named PyTestMod, then to import it and its contents (classes and functions) you simply use the command "import PyTestMod". This allows you to call things within that file through "PyTestMod.blah", with "blah" being a function, class or variable in that file. However, if you plan on using the contents of this file extensively, you can take a shortcut and remove this step by saying "from PyTestMod import bleh". What this does is that it makes it so that you can do a straight call to whatever "bleh" is, be it a class, function or variable, without having to include the "PyTestMod." with it - which saves time and code. The drawback is that if you import a lot in this way it can be hard to track down where you're actually accessing things from, because there's no "PyTestMod." label in front of the references. To import everything in a file you use an asterisk: "from PyTestMod import *".

    Let's return to the previous code example to illustrate how this works.

    # Another File
    class ClassX:
    		import PyTestMod
    		def __init__(self):
    class ClassY:
    		from PyTestMod import *
    		def __init__(self):
    As you can see here, both classes function in the exact same way. However, the syntax is different, with the second class having less to type, which is advantageous. As warned above though, it's not easy to tell what file TestClass() comes from. Well, maybe it's not hard to track in this example, but when you get a lot more code flying around it can be.

    I will now speak briefly about encapsulation, which fits in well with the current topic. Encapsulation means that certain parts (usually an import or a variable) of a file or class or function are only accessible 'locally', meaning within the context of the file/class/function you're working with. In the above example PyTestMod is imported twice - once in each class. This is necessary because each import call is "encapsulated" within each class - one call doesn't carry over. To make a single call carry over to both files you would use the import command above both classes within the "scope" (as it's called) of the entire file. Likewise, if you had used the import command inside one of the classes __init__ functions, the import would only be valid within that single function, and if there were any other functions in that class you would need to import in those as well. Note though that an import or variable creation does not carry to HIGHER levels (you can't use an import in a function throughout the entire class), but it does carry over to LOWER levels - if you have an import in a class then all of the functions in that class "see" the contents of the import as well.

    There is a way to cheat and make a variable or object usable throughout the entire class even if it's created within a function (and thus, normally out of the scope of the whole class). The command is "global" - "global MyGlobalVariable" for example, and is used occasionally in CIV.

    Python in CIV

    There are other ways to simplify the calling of certain classes and functions. One way is through the assignment of simpler names (there's a better term for this, I know it, but I can't think of it :P) to certain complex calls. In CIV you will often have object/class calls that go 4 or 5 or more objects deep, and typing "CyGlobalContext().getPlayer(1).getUnit(0).getDamag e()" over and over again gets tedius - fast. But there is a solution. You can substitute part of this string with a simpler call if you so choose. You can make it as simple as you want. For example, you can assign the previous call to a single term - "Damage". To do this you simply say "Damage = CyGlobalContext().getPlayer(1).getUnit(0).getDamag e()". Every time you say "Damage" thereafter it will make that entire long reference call. Note what I said about object references a while back. Python doesn't actually assign the value of that term to "Damage", but it tells the program that variable "Damage" to POINT to the value of that long term. Again, this may not be significant for those who aren't familiar with reference calls and OOP, but for those who are, the differences between this and normal assignment is pretty obvious.

    While the value of that long term is equal to an integer value (the damage of that particuar) unit, you can assign simpler names to object and class calls as well. For example, you can assign the unit object (CyGlobalContext().getPlayer(1).getUnit(0)) to "Unit" through "Unit = CyGlobalContext().getPlayer(1).getUnit(0)". You can then do things like "Unit.getDamage()" or "Unit.getName()" or other functions that the unit class/object possesses. You can also use a combination of these terms to cover a wider variety of things.

    For example, this is something I like to do a lot in the Python console in games just to make things easier to work with:

    GC = CyGlobalContext()
    Player = GC.getPlayer(1)
    Unit = Player.getUnit(0)
    Damage = Unit.getDamage()
    This does the exact thing as above, only it also allows me to use GC, Player and Unit independently of the single "Damage" I had above. With the above code I could now do something simple like "Unit.getName()" and it will give me the name of that unit - rather than having to to use CyGlobalContext().getPlayer(1).getUnit(0).getName( ). Quite a bit of the CIV code does things along these lines, and if you take a look you'll see there's usually a gc (CyGlobalContext) in most every file. After getting some experience using them, it's quite useful in cutting down on all the stuff you have to read and type. However, it can be kind of tricky to keep track of all that's being done, and a lot may look alien at first.

    Here's an example of a wrapper class ( in the py folder) that Jesse wrote. As he and Gramps explained elsewhere the wrapper grabs the big long code and puts it into smaller and more manageable sections.

    gc = CyGlobalContext()
    class PyPlayer:
    	"CyPlayer Helper Functions - Requires Player ID to initialize instance"
    	def __init__(self, playerID):
    		" Called whenever a new PyPlayer instance is created "
    		self.ID = playerID
    		self.player = gc.getPlayer(self.ID)
    	def isNone(self):
    		"boolean - Is the CyPlayer Instance Valid?"
    		return self.player.isNone()
    Here you can see "gc" takes the place of "CyGlobalContext()" and Jesse creates the PyPlayer class. The class inputs a single argument, the Player's ID. On the initialization of the class, the player's ID is stored in an instance variable ID (accessed in the class through "self.ID") and the actual player OBJECT is stored in self.player by using a GlobalContext function that grabs a player based upon what ID you feed it.

    The isNone() function then takes the self.player object and returns whether or not the player is valid (exists) through Player.isNone(), a function in the Player object.

    To call this class you would first import the Player file, then call the class and its functions. To call the isNone() function above you would use the command "Player.PyPlayer(0).isNone()" - where Player is the imported file, PyPlayer(0) is the wrapper class from above and its single input argument, and isNone() is the function that is being requested. It could be done the same way by the command: "CyGlobalContext().getPlayer(0).isNone()", it's just that the first is shorter, which is why Jesse wrote the wrapper classes to begin with.

    This concludes the section dedicated to OOP. Next I will present other topics, such as iteration and other useful info. Finally, I will create a detailed guide on the classes and functions available for a modder's machinations.

    This next segment is somewhat related to the previous one, but I wanted to break it up because it was getting too long.

    Iteration (Looping)

    The next section I will cover is iteration, as it is different from other languages and rather important in most CIV modding.

    Iteration is basically the process of going through every element in a related set. This is usually done if you want to find a particular object that matches a certain criterion, to set a particular attribute for every object in a set, or other related things. One thing I've used it for is in my Rebellion mod to cycle through every player in the game (and in turn cycling through every city each player has ) in order to find what player to spawn "rebels" for.

    An example of iteration through a player's list of cities follows:

    # NewObj: Active player
    Player = Player.PyPlayer(GC.getActivePlayer().getID())
    CyPlayer = CyGlobalContext().getPlayer(GC.getActivePlayer().getID())
    # NewObj: Determine player's list of cities
    CityList = Player.getCityList()
    # NewObj: Loop through each of the active player's cities
    for City in CityList:
    		# Blah...
    The first lines set up my object reference shortcuts (Player and CyPlayer are the same, but reference the Player object in different ways - the first through Jesse's Player wrapper, and the other without it), and the final line is the iteration.

    "CityList" is a List (Python object type) that contains City object instances (actual cities) like an array. You could pick out individual cities through their index: CityList[0] grabs the player's first city, while CityList with no index simply refers to the whole structure. "City" is what you're using to store the City object instance that is currently being looked at. The whole line loops through the player's city list, making "City" the current object. Within the for loop structure (after the # blah) you can then do all sorts of things: for example, City.getName() would grab the current city's name. City.hasReligion(0) returns whether or not this city has religion '0' (1 means it does, 0 means it doesn't).

    Sometimes you might just want to loop through the INDEX of how many Cities there are - that is, you would want City to be an integer rather than a City object. To do that you modify the line slightly:

    for CityIndex in range(CityList):
    		# Blah...
    "range" allows us to use the length of the structure inside the parenthesis - in this case it gets the length of the player's city list. What this code snippet does is loops through every city index - so "print(CityIndex)" in this case would print out an integer (0 through the length of the list). Note that in the previous example trying "print(City)" will result in an error because while you can print out an integer, you cannot print out a complex City object!

    The ability to iterate through objects and lists in this way gives great power in checking things and changing things with many of the things in CIV. Other things you can loop through is the player list, a player's unit list, religions list and more.

    CIV Function Assignment

    Merely checking values is only part of modding - programmers are also able to change certain values in CIV (though not as many as I'd like ).

    There are a great many functions in a variety of classes that allow you to change things about the game, but I will only cover a few here. Later I will make a massive catalogue of the most useful classes and functions as a reference, so specifics now are less important than the general structure of things.

    One of the more tricky assignments that I had to hunt down was the ability to change the religions present in a city. Using Python you can add or subtract any religion in a city, as well as set each religion's influence in said city.

    The function in the City class to set a city's religion info is ".setHasReligion(int Religion ID, int Religion Influence, bool Announce)"

    This means that the setHasReligion() function accepts 3 arguments, two integers and a boolean. Religion ID 0 is Judaism, so any time you're dealing with a TYPE of religion of "0" that means you're working with Judaism. I will use it for my following example. A religion's influence is how strong a religion is in this city - an influence of 0 means it's not present. The third argument determines whether or not the game announces to the player that the city has converted to this religion (if you go from 0 influence to any other positive integer), and it used in the normal game to announce when a religion spreads to a new city. Setting it to 1 or 0 makes no difference when removing a religion from a city.

    This following example can be easily done in the CIV Python console. To enter it, press Ctrl-Shift-C. To make this work, you need to found your first city:

    GC = CyGlobalContext()
    Player = GC.getPlayer(0)
    City = Player.getCity(0)
    Entering this in line-by-line will make your first/capital city Jewish with an influence of 10, and additionally a message will pop up on the screen.

    This next part will remove Judaism from your capital, by making its influence in this city 0:

    That concludes this section. I'll take things a bit farther in the next section.

    Creating a Unit in CIV

    Changing whether or not there's a religion in a city is one thing, but creating a brand new unit out of thin air is quite another. Thankfully, it's not that difficult if you know the right function. A similar function exists for cities, but unfortunately not for players.

    The function to create a new unit belongs to the Player class, and has the structure as follows: ".initUnit(int UnitType, int XLoc, int YLoc, obj UnitAI)". UnitType is an integer from 0 to something (the last unit); XLoc is the x-value where the unit appears, YLoc is the y-value where the unit appears, and UnitAI is an object which contains stuff related to what AI the unit uses. Right now you should only use the string "UnitAITypes.NO_UNITAI" in this location - using something else later may be possible, but for now that's all that should be used.

    Now then, to create a unit things are fairly straightforward. We will create a Warrior, which has an ID of 9. The next two arguments are a bit trickier. Where on the map do you want the Warrior to appear? Well, just picking numbers out of thin air is possible, but it might land the unit on top of an enemy city, on a peak or even in the water - which probably wouldn't end well. So we need to be careful in where we put it - how about in the capital city?

    First build your first city then try the following in the CIV console:

    GC = CyGlobalContext()
    Player = GC.getPlayer(0)
    City = Player.getCity(0)
    CityX = City.plot().getX()
    CityY = City.plot().getY()
    Player.initUnit(9, CityX, CityY + 1, UnitAITypes.NO_UNITAI)
    The first 3 lines I've already covered.

    The next two involve a new class/object: plot. Plots are the different tiles on the map. Plot (0,0) is in the far upper left-hand corner (I think, either that or the lower left-hand) and the Plot object can be accessed through the "plot()" function in the CyCity class (accessed from CyPlayer using the getCity(x) function... and so on). After we find the capital city's plot, we then find its X and Y values and assign them to a new pair of variables. The final line is the actual function which creates the unit. As outlined above, the first argument is the Warrior's ID, the second the unit's X, the third the unit's Y and the final the Unit's AI. When called as above, a new Warrior will appear in the capital! Voila.

    As I said, something similar can be done to create cities. Under the player class is the initCity() function, which takes two parameters that we've seen before, the city's location: the first being X, and the second Y. It shouldn't be that hard to figure out by now, but if anyone tries and needs some help, just go ahead and post.

    I've covered the basics now. I can't think of anything else to add to this section, but if I do I will post it later. The final section I have planned is a nice organized list of useful classes and functions for easy reference.
    Solver, WePlayCiv Co-Administrator
    Contact: solver-at-weplayciv-dot-com
    I can kill you whenever I please... but not today. - The Cigarette Smoking Man

  • #2
    Catalogue of Python Functions


    Object Retrieval:

    Includes function calls that grabs an object of some type (e.g. a player object, a unit object, etc.) related to the title.


    Includes function calls that gets information of some type about the title.


    Includes function calls that assign a value to a parameter related to the title.


    Includes function calls that 'do something' related to the title.

    ************************************************** ************************************************** ********************

    General Game

    Object Retrieval:

    CyGlobalContext().getGame() - Returns (obj) the active Game instance.

    CyGlobalContext().getActivePlayer() - Returns (obj) the Player instance of the active player.

    CyGlobalContext().getCivilizationInfo(int CivID) - Returns (obj) the CivilizationInfo instance associated with 'CivID'.

    CyGlobalContext().getLeaderHeadInfo(int LeaderID) - Returns (obj) LeaderHeadInfo instance associated with 'LeaderID'.

    CyGlobalContext().getEngine() - Returns (obj) the current Engine instance.

    CyGlobalContext().getMissionInfo(int MissionID) - Returns (obj) the MissionInfo instance associated with 'MissionID'.

    CyGlobalContext().getActionInfo(int ActionID) - Returns (obj) the ActionInfo instance associated with 'ActionID'.

    CyGlobalContext().getCivicInfo(int CivicID) - Returns (obj) the CivicInfo instance associated with 'CivicID'.

    CyGlobalContext().getCivicOptionInfo(int CivicID - Returns (obj) [unknown]. (Appears to be very similar to getCivicInfo)

    CyGlobalContext().getEmphasizeInfo(int EmphasizeID) - Returns (obj) EmphasizeInfo instance associated with 'EmphasizeID'.

    CyGlobalContext().getEraInfo(int EraID) - Returns error: No Python class registered for C++ class CvEraInfo.

    CyGlobalContext().getGoodyInfo(int GoodyID) - Returns (obj) GoodyInfo instance associated with 'GoodyID'.

    CyGlobalContext().getHandicapInfo(int HandicapID) - Returns (obj) HandicapInfo instance associated with 'HandicapID'.


    CyGame().getGameTurn() - Returns (int) the current game turn (starting with 0).

    CyGame().getGameTurnTime() - Returns (int) AD or BC year (negatives for BC).

    CyGame().getNumGameTurnActive() - Returns (int) the number of turns played in this current session?

    CyGame().getNumHumanGameTurnActive() - Returns (int) the number of turns played by a human in this current session?

    CyGame().getTotalPopulation() - Returns (int) the total world population.

    CyGame().getVictory() - Returns (int) victory type ID of the kind of victory achieved? (-1 means no victory yet)

    CyGame().getHandicapType() - Returns (int) the difficulty level (0 being Beginner).

    CyGame().getStartEra() - Returns (int) the Era ID the game started in (0 being Ancient).

    CyGlobalContext().getMAX_CIVIC_OPTIONS() - Returns (int) the number of categories of civics available? (5 in testing)

    CyGlobalContext().getMIN_ANIMAL_STARTING_DISTANCE( ) - Returns (int) the closest an animal can spawn to a unit/visible tile. (Default is 2)

    CyGlobalContext().getMIN_BARBARIAN_STARTING_DISTAN CE() - Returns (int) the closest a barb can spawn to a unit/visible tile. (Default is 2)

    CyGlobalContext().getMIN_BARBARIAN_CITY_STARTING_D ISTANCE() - Returns (int) the closest a barb city can spawn to a unit/visible tile. (Default is 2)

    CyGlobalContext().getEventTypes(int EventID) - Returns (string) event name associated with 'EventID'. (e.g. ENTEVENT_DEFAULT or ENTEVENT_MOVE)

    CyGame().getGameSpeedType() - Returns (int) Game speed (0 fast, 1 normal, 2 epic?).

    CyGame().isSpeedMode() - Returns (bool) whether or not the game is in Speed Mode. (What is that?)

    CyGame().isNone(obj Game) - Returns (bool) whether or not the instance 'Game' is valid (not sure how a modder might use this one).

    CyGame().isDebugMode() - Returns (bool) whether or not the game is in debug mode.

    CyGame().isInWorldBuilderMode() - Returns (bool) whether or not the game is in World Builder mode.

    CyGame().isMultiplayer() - Returns (bool) whether or not the game is a Multiplayer game.

    CyGlobalContext().getInvisibleTypes(int InvisibleID) - Returns (string) the name of the invisibility type associated with .InvisibleID'. (Only one functioning now is 0, which returns INVISIBLE_SUBMARINE)

    CyGlobalContext().getGOODY_DIE_SIDES() - Returns (int) the number of goody hut outcomes there are. (Default is 20)

    CyGlobalContext().getFlavorTypes(int FlavorID) - Returns (string) the name of the FlavorType associated with 'FlavorID'. (e.g. 0 returns FLAVOR_MILITARY and 1 returns FLAVOR_RELIGION)

    CyGlobalContext().getBASE_ANARCHY_LENGTH() - Returns (int) the number of turns of anarchy automatically added onto each set of anarchy. (0 in testing)

    CyGame().getName() - Returns (string) the Name of [unknown]. (Seems to return the name of the player/host's civilization leader, at least in SP games; e.g. "Frederick")

    CyGlobalContext().getMAX_AUTOSAVES() - Returns (int) the maximum number of autosaves to keep. (Default is 5)

    CyGame().getNumAIGameTurnActive() - Returns (int) [unknown].

    CyGame().getNumFreeBonuses(int BonusID) - Returns (int) [unknown].

    CyGlobalContext().getMAX_SELECTION_LIST_SIZE() - Returns (int) [unknown]. (6 in testing)

    CyGlobalContext().getMAX_YIELD_STACK() - Returns (int) [unknown]. (5 in testing)

    CyGlobalContext().getMOVE_DENOMINATOR() - Returns (int) [unknown]. (60 in testing)

    CyGlobalContext().getMOVE_EFFECT_LENGTH() - Returns (int) [unknown]. (3 in testing)

    CyGlobalContext().getCIV4_VERSION() - Returns (int) the version number. (Patch v#)


    CyGame().setName(string Name) - Makes [unknown] set to 'Name'. (Doesn't change the name of any of the players in the game)


    CyGame().toggleBonusMode() - Makes bonus resources either appear or disappear.

    CyGame().toggleCityInfoMode() - Makes city Name/food/production labels on the map either appear or disappear.

    CyGame().toggleShowUnitsMode() - Makes unit flags and health bars, etc. either appear or disappear. (This is an odd one... you'd think it would make the units themselves either appear or disappear)

    CyGame().toggleUnitInfoMode() - Makes unit health bars, etc. either appear or disappear. Only seems to work if you already have Health bars and enabled in the options menu.

    CyGame().viewCity() - Seems to do nothing (from the console).


    Object Retrieval:

    CyGlobalContext().getCommerceInfo(int CommerceID) - Returns (obj) CommerceInfo instance associated with 'CommerceID'.

    CyGlobalContext().getHurryInfo(int HurryID) - Returns (obj) HurryInfo instance associated with 'HurryID'.


    CyGlobalContext().getINITIAL_TRADE_ROUTES() - Returns (int) the initial amount of trade routes given to each city when built. (Default is 1)

    CyGlobalContext(0.getMAX_TRADE_ROUTES() - Returns (int) the maximum number of trade routes a city can have. (Default is 8)

    CyGlobalContext().getMAX_DISTANCE_CITY_MAINTENANCE () - Returns (int) the maximum amount a city can have in maintenance due to distance. (Default is 20)

    CyGlobalContext().getFOOD_CONSUMPTION_PER_POPULATI ON() - Returns (int) the amount of food eaten by each citizen. (Default is 2)

    CyGlobalContext().getCOMMERCE_PERCENT_CHANGE_INCRE MENTS() - Returns (int) [unknown]. (10 in testing)

    CyGlobalContext().getFRESH_WATER_HEATLH_CHANGE() - [sic] Returns (int) the amount of health given by building a city next to a source of fresh water. (Default is 2)

    CyGlobalContext().getBASE_CITY_GROWTH_THRESHOLD() - Returns (int) the amount of food necessary to grow (I think) - default is 20.

    CyGlobalContext().getCITY_GROWTH_MULTIPLIER() - Returns (int) [unknown]. (Was 2 in testing)

    CyGlobalContext().getCONSCRIPT_POPULATION_COST() - Returns (int) the number of population points removed from a city to conscript a unit. (Default is 1)




    Object Retrieval:


    CyGame().getNumCivPlayers() - Returns (int) number of players in the game (not including barbs).

    CyGame().getActiveCivilizationType() - Returns (int) civ ID of the active player's civilization.

    CyGame().getActivePlayerIdx() - Returns (int) player ID of the active player.

    CyGame().getBarbarianPlayer() - Returns (int) player ID of the barbs.

    CyGlobalContext().getBARBARIAN_CIVILIZATION() - Returns (int) the civilization of the Barbs (unique, I think - 19).

    CyGlobalContext().getBARBARIAN_HANDICAP() - Returns (int) the barb difficulty level (0 being Beginner - which I think is the default here).

    CyGlobalContext().getAI_HANDICAP() - Returns (int) the AI difficulty level (0 being Beginner).

    CyGame().getNumHumanPlayers() - Returns (int) number of human players in the game.

    CyGame().getNumNonHumanPlayers() - Returns (int) number of NON human players.

    CyGame().getPlayerRank(int PlayerID) - Returns (int) the rank of the 'PlayerID'.

    CyGame().getRankPlayer(int PlayerID) - Returns (int) the rank of the 'PlayerID'.

    CyGame().getPlayerScore(int PlayerID) - Returns (int) the 'PlayerID's score.

    CyGame().getLastPlayerRank(int PlayerID) - Returns (int) the 'PlayerID's score on the last turn (?).


    CyGame().setActivePlayer(int PlayerID) - Makes 'PlayerID' the active player; seems to be buggy.

    CyGame().setWinner(int PlayerID) - Makes 'PlayerID' the winning player.



    Object Retrieval:


    CyGame().isTeamGame() - Returns (bool) whether or not the game has multiple players on each team.

    CyGame().getNumTeams() - Returns (int) number of teams in the game (including barbs).

    CyGame().getBarbarianTeam() - Returns (int) team ID of the barbs.

    CyGame().getNumCivTeams() - Returns (int) number of teams with ACTUAL civs (not including barbs).

    CyGame().getRankTeam(int TeamID) - Returns (int) the rank of the 'TeamID'.

    CyGame().getTeamRank(int TeamID) - Returns (int) the rank of the 'TeamID'.

    CyGame().getTeamScore(int TeamID) - Returns (int) the 'TeamID's score.

    CyGame().getWinner() - Returns (int) the player ID of the winning team? (-1 if there is no winner)




    Object Retrieval:

    CyGlobalContext().getBuildInfo(int BuildID) - Returns (obj) BuildInfo instance associated with 'BuildID'.

    CyGame().getHolyCity(int Religion) - Returns (obj) holy City instance of type 'Religion'.

    CyGlobalContext().getCityAssetInfo(int CityID) - Returns (obj) CityAssetInfo instance associated with 'CityID'?

    CyGlobalContext().getCityArtInfo(int CityID) - Returns (obj) CityArtInfo instance associated with 'CityID'?


    CyGame().getNumCities() - Returns (int) the total number of cities in the game.

    CyGlobalContext().getINITIAL_CITY_POPULATION() - Returns (int) the initial city size when built for all cities. (Default is 1)

    CyGlobalContext().getINITIAL_CITY_MAINTENANCE() - Returns (int) the initial amount of maintenance for all cities. (Default is 0)

    CyGlobalContext().getGREAT_PEOPLE_THRESHOLD() - Returns (int) the amount of GP points necessary to spawn a GP. (Default is 50)

    CyGlobalContext().getGREAT_PEOPLE_THRESHOLD_INCREA SE() - Returns (int) the amount of GP points increase necessary to get another GP? (Default is 100)

    CyGlobalContext().getMIN_CITY_RANGE() - Returns (int) the fewest number of tiles that must be between two cities. (Default is 2)

    CyGlobalContext().getMAX_CULTURE_RANGE() - Returns (int) the farthest a city's culture can reach? (6 in testing)

    CyGlobalContext().getFREE_CITY_CULTURE() - Returns (int) [unknown]. (2 in testing)

    CyGlobalContext().getFREE_CITY_ADJACENT_CULTURE() - Returns (int) [unknown]. (1 in testing)

    CyGlobalContext().getCULTURE_FACTOR() - Returns (int) the exponential increase necessary to increase cultural borders? (Default is 10)

    CyGlobalContext().getDIRTY_POWER_HEATLH_CHANGE() - [sic] Returns (int) the amount

    CyGlobalContext().getCITY_HEAL_RATE() - Returns (int) the amount units heal when in a city? (Was 20 in testing)

    CyGlobalContext().getCITY_PLOTS_DIAMETER() - Returns (int) [unknown]. (Was 0 in testing)

    CyGlobalContext().getCITY_PLOTS_RADIUS() - Returns (int) [unknown]. (Was 2 in testing)

    CyGlobalContext().getCITY_SEE_FROM_CHANGE() - Returns (int) [unknown]. (Was 0 in testing)




    Object Retrieval:


    CyGlobalContext().getCommandTypes(int CommandID) - Returns (string) the string associated with 'CommandID'. (Example: COMMAND_PROMOTION or COMMAND_UPGRADE)

    CyGame().isSpecialUnitValid(int UnitID) - Returns (int? bool?) whether or not 'UnitID' is a valid unit? (Spies?)


    CyGame().makeSpecialUnitValid(int UnitID) - Makes 'UnitID' a valid unit.



    Object Retrieval:

    CyGame().getHolyCity(int Religion) - Returns (obj) Holy City of type 'Religion'.


    CyGame().isReligionFounded(int ReligionID) - Returns (bool) whether or not 'ReligionID' has been founded yet (exists in any city).

    CyGame().getReligionLevels(int ReligionID) - Returns (int) the number of cities that have 'ReligionID'?

    CyGlobalContext().getFOUND_RELIGION_CITY_RAND() - Returns (int) [unknown]. (10 in testing)




    Object Retrieval:

    CyGlobalContext().getMap() - Returns (obj) the current Map instance.

    CyGlobalContext().getLandscape() - Returns (obj) the current Landscape instance.

    CyGlobalContext().getFeatureInfo(int FeatureID) - Returns (obj) FeatureInfo instaance associated with 'FeatureID'.

    CyGlobalContext().getBonusInfo(int BonusID) - Returns (obj) BonusInfo instance associated with the 'BonusID'.

    CyGlobalContext().getBonusClassInfo(int BonusClassID) - Returns (obj) BonusClassInfo instance associated with the 'BonusClassID'.

    CyGame().getMapRand() - Returns (obj) [unknown].


    CyGlobalContext().getFeatureTypeForString(string FeatureName) - Returns (int) the ID of 'FeatureName'. (e.g. FEATURE_FOREST returns 4)

    CyGlobalContext().getMAX_PLOT_LIST_SIZE() - Returns (int) [unknown]. (11 in testing)

    CyGame().getMapRandNum() - Returns (int) [unknown].

    CyGlobalContext().getHIGHLANDS_SEE_FROM_CHANGE() - Returns (int) the amount of extra tiles seen when on a highlands tile? (Default is 1)

    CyGlobalContext().getHIGHLANDS_SEE_THROUGH_CHANGE( ) - Returns (int) [unknown]. (Default is 1)

    CyGlobalContext().getBonusTypeForString(string BonusType) - Returns (int) the Bonus Type ID of the string you input - follows the format of "BONUS_HORSE".

    CyGlobalContext().getBonusClassTypeForString(strin g BonusClass) - Returns (int) the Bonus Class ID of the string you input - follows the format of "BONUSCLASS_GENERAL" and "BONUSCLASS_METAL".

    CyGlobalContext().getHOME_PLOT() - Returns (int) [unknown]. (Default is 0)




    Object Retrieval:

    CyGlobalContext().getBuildingInfo(int BuildingID) - Returns (obj) instance of BuildingInfo associated with 'BuildingID'.

    CyGlobalContext().getBuildingClassInfo(int BuildingClassID) - Returns (obj) instance of BuildingClassInfo associated with 'BuildingClassID'.

    CyGlobalContext().getBuildingArtInfo(int BuildingID) - Returns (obj) instance of ArtInfo associated with 'BuildingID'.


    CyGame().getBuildingClassCount(int BuildingClass) - Returns (int) the number of buildings of type 'BuildingClass' in the game for all players.

    CyGame().getBuildingClassPrereqBuilding(int BuildingClass, int PrereqID) - Returns (int) the prerequisite building ID of 'BuildingClass' slot 'PrereqID'.

    CyGame().isBuildingClassMaxedOut(int BuildingClass, int ?) - Returns (bool) whether or not the maximum instances of 'BuildingClass' are present. ? is an argument I'm not sure the function of.




    CyGame().getKnownTechNumTeams(int TechID) - Returns (int) the number of teams that know 'TechID'? (Testing results confusing)

    CyGlobalContext().getBASE_RESEARCH_RATE() - Returns (int) [unknown]. (2 in testing)

    CyGlobalContext().getNUM_AND_TECH_PREREQS() - Returns (int) [unknown]. (4 in testing)



    Object Retrieval:


    CyGlobalContext().getMemoryTypes(int MemoryID) - Returns (string) the name of the AI Memory type associated with 'MemoryID'. (0 returns MEMORY_DECLARED_WAR and 1 returns MEMORY_RAZED_CTY and 2 returns MEMORY_CONTACTED_US)

    CyGlobalContext().getDiplomacyPowerTypes(int PowerID) - Returns (string) the name name of the power relation between civs associated with 'PowerID'. (e.g. "DIPLOMACYPOWER_WEAKER" or "DIPLOMACYPOWER_EQUAL")

    CyGlobalContext().getDiploCommentTypes(int CommentID) - Returns (string) the name of the diplo comment associated with 'CommentID'. (e.g. "AI_DIPLOCOMMENT_FIRST_CONTACT" or "AI_DIPLOCOMMENT_REFUSE_TO_TALK")

    CyGlobalContext().getAIDiploCommentTypes(int CommentID) - Returns (string) the name of the AI diplo comment associated with 'CommentID'.

    CyGlobalContext().getAttitudeTypes(int AttitudeID) - Returns (string) the name of the different types of AI attitudes (e.g. ATTITUDE_FURIOUS) associated with the 'AttitudeID'.




    Object Retrieval:


    CyGlobalContext().getMAX_HIT_POINTS() - Returns (int) the amount of hit points units have at full strength. (Default is 100)

    CyGlobalContext().getFRIENDLY_HEAL_RATE() - Returns (int) the amount of damage healed in friendly territory per turn? (15 in testing)

    CyGlobalContext().getNEUTRAL_HEAL_RATE() - Returns (int) the amount of damage healed in neutral territory per turn? (10 in testing)

    CyGlobalContext().getENEMY_HEAL_RATE() - Returns (int) the amount of damage healed in enemy territory per turn? (5 in testing)

    CyGlobalContext().getCOMBAT_DAMAGE() - Returns (int) the amount of damage done in combat? (30 in testing)

    CyGlobalContext().getAIR_COMBAT_DAMAGE() - Returns (int) the amount of damage done in air combat? (30 in testing).

    CyGlobalContext().getHIGHLANDS_EXTRA_DEFENSE() - Returns (float) the extra strength modifier given to units being attacked on highlands. (Default is 0.20000000298)

    CyGlobalContext().getAMPHIB_ATTACK_MODIFIER() - Returns (float) a fraction that amphibious attacks are multiplied by with units that don't have the Amphibious promotion (-0.5 right now).

    CyGlobalContext().getBARBARIAN_ATTACK_EXPERIENCE_M ODIFIER() - Returns (int) the fraction * 100 modifier for experience against barbs (-25 right now, meaning -25%).

    CyGlobalContext().getBARBARIAN_DEFENSE_EXPERIENCE_ MODIFIER() - Returns (int) the fraction * 100 modifier for experience against barbs (-25 right now, meaning -25%).

    CyGlobalContext().getCOMBAT_DIE_SIDES() - Returns (int) the multiplier for random numbers when used in combat? (1000 in testing)




    Object Retrieval:

    CyGlobalContext().getLeaderheadArtInfo(int LeaderID) - Returns (obj) LeaderheadArtInfo instance associated with 'LeaderID'.

    CyGlobalContext().getInterfaceArtInfo(int InterfaceID) - Returns (obj) InterfaceArtInfo instance associated with 'InterfaceID'.

    CyGlobalContext().getImprovementArtInfo(int ImpID) - Returns (obj) ImprovementArtInfo instance associated with 'ImpID'.

    CyGlobalContext().getFeatureArtInfo(int FeatureID) - Returns (obj) FeatureArtInfo instance associated with 'FeatureID'.

    CyGlobalContext().getMiscArtInfo(int MiscArtID) - Returns (obj) MiscArtInfo instance assocaited with 'MiscArtID'.


    CyGlobalContext().getLanguageTypes(int LanguageID) - Returns (string) the name of the language associated with 'LanguageID'. (e.g. 0 returns LANGUAGE_ENGLISH and 1 returns LANGUAGE_FRENCH)

    CyGlobalContext().getFunctionTypes(int FunctionID) - Returns (string) the name of the FunctionType associated with 'FunctionID'. (e.g. 0 returns FUNC_NOINTERP and 1 returns FUNC_LINKEY - don't ask me what these mean!)

    CyGame().getCloseView() - Returns (int) [unknown].

    CyGlobalContext().getControlTypes(int ControlID) - Returns (string) of the Control Type associated with 'ControlID'. (e.g. "CONTROL_NEXTCITY" or "CONTROL_FORCEENDTURN")

    CyGame().getSymbolID(int ID) - Returns (int) the ID of ?

    CyGame().getSyncRand() - Returns (obj) Random object?

    CyGame().getSyncRandNum(int ?, string ?) - Returns (int) [unknown].

    CyGame().isCloseViewBars() - Returns (int) [unknown].

    CyGame().isCloseViewUnits() - Returns (int) [unknown].

    CyGlobalContext().getDYING_PAUSE() - Returns (int) [unknown]. (8 in testing... I have no clue what this does. It may belong in the Unit section but until I get more info on it it goes to General)


    Solver, WePlayCiv Co-Administrator
    Contact: solver-at-weplayciv-dot-com
    I can kill you whenever I please... but not today. - The Cigarette Smoking Man


    • #3
      I am thinking about including an author page as well.
      Tattoo laser removal free trial - Tattoo removal cream before and after pictures


      • #4
        I'm trying to follow this guide to the best of my ability and am doing my own Python script as I go along. Then I spotted this in your code:
        Originally posted by Solver View Post
        CityList = Player.getCityList()
        I realized that there probably would be a getUnitList also, but I can't for the life of me find neither one in the API(s)!

        What's up with that?

        Anyways, it would be pretty helpful to have such functions, I believe. Could someone maybe code a CityList and a UnitList function - not for city/unit names, but for the actual IDs (the pointers, I suppose). Because then it would be possible to choose a city or unit by ID. Right?


        • #5
          Don't look at this guide as an example of valid APIs. Some are, some aren't. As the intro text notes, it was written in 2004, over a year before Civ4 got released. And then we've had two expansions and multiple patches - the API has changed a lot. This guide is more to show some basic principles of how to write.

          And I don't immediately see why you'd ever want to choose units by ID. They're internal IDs, they're not meant to be used much in this fashion. If you need to do something to a player's units, there's probably a better way.
          Solver, WePlayCiv Co-Administrator
          Contact: solver-at-weplayciv-dot-com
          I can kill you whenever I please... but not today. - The Cigarette Smoking Man


          • #6
            I actually found what I was looking for in PyHelpers:
            	def getCityList(self):
            		' PyCitylist - list of PyCity player owns '
            		lCity = []
            		(loopCity, iter) = self.player.firstCity(false)
            			cityOwner = loopCity.getOwner()
            			if ( not loopCity.isNone() and loopCity.getOwner() == self.getID() ): #only valid cities
            				city = PyCity( self.getID(), loopCity.getID() )
            			(loopCity, iter) = self.player.nextCity(iter, false)
            		return lCity
            	def getUnitList(self):
            		' UnitList - All of the players alive units '
            		lUnit = []
            		(loopUnit, iter) = self.player.firstUnit(false)
            		while( loopUnit ):
            			if ( not loopUnit.isDead() ): #is the unit alive and valid?
            				lUnit.append(loopUnit) #add unit instance to list
            			(loopUnit, iter) = self.player.nextUnit(iter, false)
            		return lUnit
            Very helpful, indeed.

            I'm having a hard time following the code myself, but I guess it works nonetheless...
            Last edited by Baldyr; January 9, 2010, 12:46.


            • #7
              very useful... Thanks mate!!


              • #8
                Wonderful instruction, Jon.
                Thanks for posting this Solver.
                Apolyton is an excellent resource for the development of humans.

                Sent from my XT1650 using Tapatalk

                To us, it is the BEAST.