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

Sample Python Code

Discussion in 'Civ4 - Modding Tutorials & Reference' started by Kael, Jan 8, 2006.

  1. Kael

    Kael Deity

    Joined:
    May 6, 2002
    Messages:
    17,401
    Location:
    Ohio
    A little present for the folks trying to learn Python (I had never heard of Python before Civ4 was released so I'm still learning too). This is a little custom function I wrote for my mod that I thought displayed a few ways to do things that other may find interesting.

    Im not the best programmer (not even a good one) but I thought if you guys learn like I do then the more examples you are exposed to the better you will be.

    Please feel free to use this thread to post sample code of your own (please don't use this thread to post broken code, I want a common place people can trade working ideas).

    Code:
    	def FFHFireAura(self, iDmg, pUnit):
    
    		iX = pUnit.getX()
    		iY = pUnit.getY()
    		iPlayer = pUnit.getOwner()
    		pPlayer = gc.getPlayer(iPlayer)
    		eTeam = pPlayer.getTeam()
    		for iiX in range(iX-1, iX+2, 1):
    			for iiY in range(iY-1, iY+2, 1):
    				pPlot = CyMap().plot(iiX,iiY)
    				for iUnit in range(pPlot.getNumUnits()):
    					insDmg = iDmg
    					pUnit = pPlot.getUnit(iUnit)
    					iStr = pUnit.baseCombatStr() * 3
    					iLevel = pUnit.getLevel() * 3
    					insDmg = iDmg - (iStr + iLevel)
    					if pUnit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_MAGIC_RESISTANCE')):
    						insDmg = insDmg /2
    					if insDmg >= 1:
    						pUnit.setDamage(pUnit.getDamage() + insDmg, True)
    						if pUnit.getOwner() != iPlayer:
    							p2Player = gc.getPlayer(pUnit.getOwner())
    							e2Team = gc.getTeam(p2Player.getTeam())
    							e2Team.declareWar(eTeam, False)
    
    What it does: passed a dmg amount and a unit object it does dmg to all units within 1 tile of the selected unit (including the unit itself). The amount of dmg that is done is mitigated by the effected units level and strength. Lastly if the unit that is hurt isn't the players own then they will declare war on the player.

    Use: In my mod I have a guy that called this function every turn. He is a very nasty guy to hang around. I will also be using it as a spell that damages everyone in the area. There is nothing to keep you from a smiliar function to perform a check, add or remove promotions, etc etc.
     
  2. The Great Apple

    The Great Apple Big Cheese

    Joined:
    Mar 24, 2002
    Messages:
    3,361
    Location:
    Oxford, England
    Great idea! I wish I had this sort of thing when I was learning (I also learnt with Civ 4).

    Code:
    def areaExp(self, pCity):
    	
    	### Set up counters (could do with a dictionary, but this way is simpler)
    	iForest = 0
    	iJungle = 0
    	iHill = 0
    	iCoast = 0
    			
    	iPlotX = pCity.getX()
    	iPlotY = pCity.getY()	
    		for iXLoop in range(iPlotX - 2, iPlotX + 3):
    		for iYLoop in range(iPlotY - 2, iPlotY + 3):
    			### Checks for tiles not in city radius
    			if ((iXLoop != 2) and (iYLoop != 2)) or \
    			   ((iXLoop != -2) and (iYLoop != 2)) or \
    			   ((iXLoop != 2) and (iYLoop != -2)) or \
    			   ((iXLoop != -2) and (iYLoop != -2)):
    			
    				lPlot = CyMap().plot(iXLoop, iYLoop)
    			
    				if lPlot.getTerrainType() == gc.getInfoTypeForString("TERRAIN_HILL"):
    					iHill += 1
    				if lPlot.getTerrainType() == gc.getInfoTypeForString("TERRAIN_COAST"):
    					iCoast += 1
    				if lPlot.getFeatureType() == gc.getInfoTypeForString("FEATURE_FOREST"):
    					iForest += 1
    				if lPlot.getFeatureType() == gc.getInfoTypeForString("FEATURE_JUNGLE"):
    					iJungle += 1
    What it does: Checks all the squares in a cities workable radius (the fat cross), and counts how maiy of certain features there are in this radius,

    Use: In my dynamic experience mod I used this to allocate data to units on creation, based upon the area around where they were created, with the idea that units being made in cities surrounded by forests would be more likely to get a woodsman promotion, for example.

    Things to note: The range function does not include the upper value, so the upper value in the code has to be one greater than the actual maximum value needed. I hadn't noticed this when I first wrote the code.
     
  3. Quitos

    Quitos Chieftain

    Joined:
    Jan 2, 2006
    Messages:
    37
    thanks. i have a silly question. where to run this functions :)
     
  4. Kael

    Kael Deity

    Joined:
    May 6, 2002
    Messages:
    17,401
    Location:
    Ohio
    I import a python file that I create all of these custom functions. The logic of the functions are useful to those wrestling with Python, its good to see sample code that works. But these aren't helpful as cut and pastes to be dropped into files and gain the functions. More work than that is required. Functions need to be written or altered to use them.

    So any of the files in the Assets/Python/ directory could use them, though if your not working on programming in python I don't think these will be very useful to you.
     
  5. RogerBacon

    RogerBacon King

    Joined:
    Nov 16, 2003
    Messages:
    649
    Great Apple and Kael,

    Why does both of your code seem to go one farther to the right than to the left when you are both doing something in a fixed radius aroung a point?

    Kael has:
    for iiX in range(iX-1, iX+2, 1):
    for iiY in range(iY-1, iY+2, 1):

    and Great Apple has:
    for iXLoop in range(iPlotX - 2, iPlotX + 3):
    for iYLoop in range(iPlotY - 2, iPlotY + 3):

    Both seem to go farther to the right than to the left.

    Roger Bacon
     
  6. Kael

    Kael Deity

    Joined:
    May 6, 2002
    Messages:
    17,401
    Location:
    Ohio
    I have no idea. I just know when I first wrote it to be (iX-1, iX+1, 1) it didn't cover the complete area so I had to extend it an additional step to the east. I don't know if the range function doesn't act on the last step or if there is something unusual about the coordinates that the getX() and getY() functions return.

    I suspect the range function doesn't kick on the last pass. I have seen units skipped on processing when they are the last unit in the range.
     
  7. Quitos

    Quitos Chieftain

    Joined:
    Jan 2, 2006
    Messages:
    37
    i am starting to learn modding... i learned some Python, and my problem is how to build the files that will be called by the game.. now i am trying to dissect some simple mods to figure it out... not successful yet
     
  8. The Great Apple

    The Great Apple Big Cheese

    Joined:
    Mar 24, 2002
    Messages:
    3,361
    Location:
    Oxford, England
    - Basically what Kael said. It doesn't include the final value.
     
  9. RogerBacon

    RogerBacon King

    Joined:
    Nov 16, 2003
    Messages:
    649
    Wow, that's good to know. I hope they change that in the next version of Python.

    Roger Bacon
     
  10. vbraun

    vbraun Raytracing

    Joined:
    Jul 7, 2003
    Messages:
    3,530
    Location:
    Arizona, USA
    I doubt it. Just add a +1 in the high value.

    Also you don't need all 3 argumants. The only one you really need is the high one.

    range(10) will return:

    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
     
  11. Aussie_Lurker

    Aussie_Lurker Deity

    Joined:
    Jul 21, 2003
    Messages:
    7,782
    Location:
    Adelaide, South Australia
    Well, as many of you here will know, I have had my fair share of problems with the Python language. However I did get THIS one to work:

    Code:
    def onBeginPlayerTurn(self, argsList):
    		'Called at the beginning of a players turn'
    		iGameTurn, iPlayer = argsList
    
    		player = gc.getActivePlayer()
    		if (player.isCivic(gc.getInfoTypeForString("CIVIC_SLAVERY"))):
    			for i in range(player.getNumCities()):
    				player.getCity(i).setFreeSpecialistCount(gc.getInfoTypeForString("SPECIALIST_SLAVE"), 1)
    		else:
    			for i in range(player.getNumCities()):
    				player.getCity(i).setFreeSpecialistCount(gc.getInfoTypeForString("SPECIALIST_SLAVE"), 0)
    What it Does: Looks to check what Civic you are in and-if it matches the one it is looking for-then it grants a free specialist of the type mentioned in every city.

    Use Not Currently using it in this form (as I wanted a different way to make slavery more....interesting. However, in modified form, it will allow certain Civics to grant you quite unique benefits (like Theocracy might grant you a free priest).

    Yours,
    Aussie_Lurker.
     
  12. Déja

    Déja Beyond the Mod

    Joined:
    Dec 19, 2005
    Messages:
    353
    I've been programming for over a decade and using python for a few years, so I apologize if this gets too technical.

    The reason it does this is because the following code snippet:

    Code:
    for i in range([B]start[/B], [B]end[/B]):
      ...blah...
    
    Will actually end up looking like this: (pseudocode)

    Code:
    set i to [B]start[/B]
    run the code in ...blah...
    check if i is [COLOR="Red"]less than[/COLOR] [B]end[/B]
    if it is, add [B]step[/B] to i (default step is 1)
    go back up to run the code in ...blah...
    ...etc...
    
    The reason it does this is that most things in computer programming are on scales from 0 to n-1 where n is the number of values.

    So... if you want to check 4 values, you would usually check the values 0, 1, 2, 3 (still four values, just starting with 0 instead of 1)

    If you're interested in the why's of the 0-index system, do some google-work.

    Hope this helps.
     
  13. Kael

    Kael Deity

    Joined:
    May 6, 2002
    Messages:
    17,401
    Location:
    Ohio
    You need all three when you aren't starting at 0.
     
  14. Kael

    Kael Deity

    Joined:
    May 6, 2002
    Messages:
    17,401
    Location:
    Ohio
    I made my first array to make some of my increasingly complex checks easier (and to keep them standard). This is what I added to cvEventManager.py:

    Up in the top of the file, before the globals are defined:

    Code:
    livingUnitCombats =	[	gc.getInfoTypeForString('UNITCOMBAT_ARCHER'),
    				gc.getInfoTypeForString('UNITCOMBAT_MELEE'),
    				gc.getInfoTypeForString('UNITCOMBAT_RECON'),
    				gc.getInfoTypeForString('UNITCOMBAT_DWARF'),
    				gc.getInfoTypeForString('UNITCOMBAT_ELF'),
    				gc.getInfoTypeForString('UNITCOMBAT_ADEPT'),
    				gc.getInfoTypeForString('UNITCOMBAT_DISCIPLE'),
    				gc.getInfoTypeForString('UNITCOMBAT_MOUNTED')	]
    Then in any of the functions I just do the following:

    Code:
    		if pPlayer.getCivics(gc.getInfoTypeForString('CIVICOPTION_LABOR')) == gc.getInfoTypeForString('CIVIC_SLAVERY'):
    [b]			if pLoser.getUnitCombatType() in livingUnitCombats:[/b]
    				iRnd = CyGame().getSorenRandNum(100, "Bob")
    				if iRnd <= 25:
    					CyInterface().addMessage(pWinner.getOwner(),True,25,'Slave captured.','AS2D_DISCOVERBONUS',1,'Art/Interface/Buttons/Units/Slave.dds',ColorTypes(8),pWinner.getX(),pWinner.getY(),True,True)
    					newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNIT_SLAVE'), pWinner.getX(), pWinner.getY(), UnitAITypes.NO_UNITAI)
    It seems to work out pretty well and keeps me from having to go back and change all my checks whenever I add a new unitcombat. Instead I just add it to the appropriate arrays and the checks start working with it on their own.
     
  15. TheLopez

    TheLopez Deity

    Joined:
    Jan 16, 2006
    Messages:
    2,525
    Location:
    Oregon
    Code:
    # Returns the distance between city A and city B
    def getCityDistance(self, objCityA, objCityB):
    [TAB]' int - the distance between objCityA and objCityB'
    [TAB]return self.getPlotDistance(objCityA.plot(), objCityB.plot())
    [TAB][TAB]
    [TAB][TAB]
    # Calculates the distance between two plots using the formula:
    # MAX((X2-X1), (Y2-Y1))
    def getPlotDistance(self, objPlotA, objPlotB):
    [TAB]' iDist - the distance between objPlotA and objPlotB'
    [TAB]dX = abs(objPlotB.getX() - objPlotA.getX())
    [TAB]dY = abs(objPlotB.getY() - objPlotA.getY())
    [TAB]dist = max(dX,dY)
    
    [TAB]# if you want it in one line to avoid variable assignments: 
    [TAB]# return max(abs(objPlotB.getX()-objPlotA.getX()), abs(objPlotB.getY()-objPlotA.getY()))
    [TAB]return int(dist)
    

    What it does: Gets the distance between two plots or two cities


    On another note, please include comments in your example codes in your posts, it will make it easier for new modders to understand your code. (I have made an effort to do this within the mercenaries mod.)

    EDIT: Updated to use 12monkeys code instead
     
  16. 12monkeys

    12monkeys Prince

    Joined:
    Nov 24, 2003
    Messages:
    440
    Location:
    Germany, Europe
    If you want to calculate the theoretical distance between two plots by pythagoras than this method is OK. But it will be of no use, if you want to calculate the distance in plots to move.

    Example : you're target plot is away 4 x-plots and and 3 y-plots. Using your function the distance is 5 plot. In fact the distance is 4 plots, because it doesn't matter if you're moveing to straight or diagonal.

    So the correct calculation for the distance should be :

    Code:
    dist = max(abs(FromPlot.getX()-ToPlot.getX()), abs(FromPlot.getY()-ToPlot.getY()))
    
     
  17. TheLopez

    TheLopez Deity

    Joined:
    Jan 16, 2006
    Messages:
    2,525
    Location:
    Oregon
    Hmmm.... good point, fixing my post :), thanks 12monkeys
     
  18. naf4ever

    naf4ever Dread Lord

    Joined:
    Feb 8, 2003
    Messages:
    405
    Location:
    Southeast Washington State
    Please keep posting stuff here. The tutorials have taught me the basics of python and how to do things like merge existing mods, but they dont actually help you learn how to create new stuff.

    These examples are more useful than any of the tutorials. Please post many more when you get the chance.
     
  19. Kael

    Kael Deity

    Joined:
    May 6, 2002
    Messages:
    17,401
    Location:
    Ohio
    Ok, will do. I'll put up my cannotTrain function from the CvGameInterface.py file:

    Code:
    def cannotTrain(argsList):
    	pCity = argsList[0]
    	eUnit = argsList[1]
    	bContinue = argsList[2]
    	bTestVisible = argsList[3]
    	ePlayer = pCity.getOwner()
    	pPlayer = gc.getPlayer(ePlayer)
    
    	if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_WARRIOR')):
    		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_BARRACKS'), False, False, False):
    			return True
    		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_ARCHERY_RANGE'), False, False, False):
    			return True
    
    	if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_SCOUT')):
    		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_HUNTING_LODGE'), False, False, False):
    			return True
    		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_STABLES'), False, False, False):
    			return True
    
    	if eUnit == gc.getInfoTypeForString('UNIT_EIDOLON'):
    		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ORDER'):
    			return True
    		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_RUNES_OF_KILMORPH'):
    			return True
    
    	if eUnit == gc.getInfoTypeForString('UNIT_PALADIN'):
    		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ASHEN_VEIL'):
    			return True
    		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_OCTOPUS_OVERLORDS'):
    			return True
    
    	if eUnit == gc.getInfoTypeForString('UNIT_HIGH_PRIEST'):
    		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ASHEN_VEIL'):
    			return True
    
    	if eUnit == gc.getInfoTypeForString('UNIT_DEMON_SUMMONER'):
    		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ORDER'):
    			return True
    
    	if eUnit == gc.getInfoTypeForString('UNIT_ROYAL_GUARD'):
    		if pPlayer.getCivics(gc.getInfoTypeForString('CIVICOPTION_GOVERNMENT')) != gc.getInfoTypeForString('CIVIC_HEREDITARY_RULE'):
    			return True
    
    	if eUnit == gc.getInfoTypeForString('UNIT_BEAST_OF_AGARES'):
    		if pCity.getPopulation() <= 5:
    			return True
    
    	return False
    What it does: cannotTrain is run through everytime the game gives you the option to build units. If the function returns True then you "cannotTrain" the given eUnit. Returning False means you can train the unit.

    This function is very valuable because it allows you to tie the ability to build units to virtualy any variable that exists in the game. There are similiar functions for researching Techs and Buildings too.

    Use:

    Lets go through the functions one by one. The first stops the AI from being able to build Warriors if they are able to build Barracks or Archery Ranges. It does not effect human players and I needed it to keep the AI from building lower class units when it should have been upgrading its cities to be able to produce the higher class units:

    Code:
    	if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_WARRIOR')):
    		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_BARRACKS'), False, False, False):
    			return True
    		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_ARCHERY_RANGE'), False, False, False):
    			return True
    The next does exactly the same thing for Scouts:

    Code:
    	if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_SCOUT')):
    		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_HUNTING_LODGE'), False, False, False):
    			return True
    		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_STABLES'), False, False, False):
    			return True
    The following check doesn't allow the player to build Eidolon's if they have either The Order or Runes of Kilmorph as their state religion. Notice that you can already require a religion for a unit in the XML but by using this function you can get much more grnaular with it (in this case allowing the unit to be built by 3 of the 5 religions in the game).

    Code:
    	if eUnit == gc.getInfoTypeForString('UNIT_EIDOLON'):
    		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ORDER'):
    			return True
    		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_RUNES_OF_KILMORPH'):
    			return True
    

    The next does the same for Paladins, it keeps them by being built if the players State religion if the Ashen Veil or Octopus Overlords.

    Code:
    	if eUnit == gc.getInfoTypeForString('UNIT_PALADIN'):
    		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ASHEN_VEIL'):
    			return True
    		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_OCTOPUS_OVERLORDS'):
    			return True
    A single block that keeps civs with the Ashen Veil state religion from building High Priests.

    Code:
    	if eUnit == gc.getInfoTypeForString('UNIT_HIGH_PRIEST'):
    		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ASHEN_VEIL'):
    			return True
    A single block that keeps civs with the Order state religion from building Demon Summoners.

    Code:
    	if eUnit == gc.getInfoTypeForString('UNIT_DEMON_SUMMONER'):
    		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ORDER'):
    			return True
    The following doesn't allow the player to build royal Guard units unless their Government civic is set to Hereditary Rule. Notice that I couldn't make it "if the players has Hereditary Rule return False" because if the players civic was anything else it would have kept going through the cannotTrain function and eventually returned False anyway. I have to trap on anything that isn't allowed, not the things that are.

    Code:
    	if eUnit == gc.getInfoTypeForString('UNIT_ROYAL_GUARD'):
    		if pPlayer.getCivics(gc.getInfoTypeForString('CIVICOPTION_GOVERNMENT')) != gc.getInfoTypeForString('CIVIC_HEREDITARY_RULE'):
    			return True
    The last one just requires that the building city be a certain size before the unit is allowed to be built. I do this because when the unit comes into play it kills 4 population in the city.

    Code:
    	if eUnit == gc.getInfoTypeForString('UNIT_BEAST_OF_AGARES'):
    		if pCity.getPopulation() <= 5:
    			return True
    Thats everything Im blocking on, but by no means is it the limit to which you can block on things. Have fun!
     
  20. naf4ever

    naf4ever Dread Lord

    Joined:
    Feb 8, 2003
    Messages:
    405
    Location:
    Southeast Washington State
    Thanks Kael. This kind of stuff helps immensely. The hardest part about learning python I think is that its hard to create something totally new unless you've seen some examples of something similiar first, then you can understand how some of the strings and functions are suppose to operate.

    I know your busy with FfH but check back here a few times a month if you can and post more if possible. Same for anyone else.
     

Share This Page