View Full Version : Sample Python Code
Kael Jan 08, 2006, 04:02 PM 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).
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('PROM OTION_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.
The Great Apple Jan 08, 2006, 04:26 PM Great idea! I wish I had this sort of thing when I was learning (I also learnt with Civ 4).
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.
Quitos Jan 09, 2006, 11:53 AM thanks. i have a silly question. where to run this functions :)
Kael Jan 09, 2006, 12:30 PM thanks. i have a silly question. where to run this functions :)
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.
RogerBacon Jan 09, 2006, 01:31 PM 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
Kael Jan 09, 2006, 01:58 PM 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
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.
Quitos Jan 09, 2006, 02:10 PM 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.
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
The Great Apple Jan 09, 2006, 02:59 PM 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.
- Basically what Kael said. It doesn't include the final value.
RogerBacon Jan 09, 2006, 03:30 PM - Basically what Kael said. It doesn't include the final value.
Wow, that's good to know. I hope they change that in the next version of Python.
Roger Bacon
vbraun Jan 09, 2006, 04:20 PM Wow, that's good to know. I hope they change that in the next version of Python.
Roger Bacon
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]
Aussie_Lurker Jan 09, 2006, 05:09 PM 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:
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.getInf oTypeForString("SPECIALIST_SLAVE"), 1)
else:
for i in range(player.getNumCities()):
player.getCity(i).setFreeSpecialistCount(gc.getInf oTypeForString("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.
Déja Jan 11, 2006, 05:49 PM 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
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:
for i in range(start, end):
...blah...
Will actually end up looking like this: (pseudocode)
set i to start
run the code in ...blah...
check if i is less than end
if it is, add step 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.
Kael Jan 11, 2006, 06:01 PM 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]
You need all three when you aren't starting at 0.
Kael Mar 01, 2006, 08:56 AM 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:
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:
if pPlayer.getCivics(gc.getInfoTypeForString('CIVICOP TION_LABOR')) == gc.getInfoTypeForString('CIVIC_SLAVERY'):
if pLoser.getUnitCombatType() in livingUnitCombats:
iRnd = CyGame().getSorenRandNum(100, "Bob")
if iRnd <= 25:
CyInterface().addMessage(pWinner.getOwner(),True,2 5,'Slave captured.','AS2D_DISCOVERBONUS',1,'Art/Interface/Buttons/Units/Slave.dds',ColorTypes(8),pWinner.getX(),pWinner.ge tY(),True,True)
newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNIT_SLA VE'), 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.
TheLopez Mar 01, 2006, 09:18 AM # 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
12monkeys Mar 01, 2006, 09:30 AM # Returns the distance between city A and city B
def getCityDistance(self, objCityA, objCityB):
' int - the distance between objCityA and objCityB'
return self.getPlotDistance(objCityA.plot(), objCityB.plot())
# Calculates the distance between two plots using the formula:
# | ______________________ | = length of shortest route
# |_ V (X2-X1)^2 + (Y2-Y1)^2 _| between objPlotA and objPlotB
#
def getPlotDistance(self, objPlotA, objPlotB):
' iDist - the distance between objPlotA and objPlotB'
dX = pow(objPlotB.getX()-objPlotA.getX(),2)
dY = pow(objPlotB.getY()-objPlotA.getY(),2)
dist = math.floor(math.sqrt(dX+dY))
# if you want it in one line to avoid variable assignments:
# return floor(math.sqrt(pow(objPlotB.getX()-objPlotA.getX(),2)+pow(objPlotB.getY()-objPlotA.getY(),2)))
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.)
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 :
dist = max(abs(FromPlot.getX()-ToPlot.getX()), abs(FromPlot.getY()-ToPlot.getY()))
TheLopez Mar 01, 2006, 09:46 AM 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 :
dist = max(abs(FromPlot.getX()-ToPlot.getX()), abs(FromPlot.getY()-ToPlot.getY()))
Hmmm.... good point, fixing my post :), thanks 12monkeys
naf4ever Mar 08, 2006, 10:48 PM 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.
Kael Mar 09, 2006, 02:16 AM Ok, will do. I'll put up my cannotTrain function from the CvGameInterface.py file:
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('BUIL DING_BARRACKS'), False, False, False):
return True
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_ARCHERY_RANGE'), False, False, False):
return True
if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_SCOUT')):
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_HUNTING_LODGE'), False, False, False):
return True
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_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_KILMORP H'):
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_OVERLORD S'):
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('CIVICOP TION_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:
if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_WARRIOR')):
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_BARRACKS'), False, False, False):
return True
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_ARCHERY_RANGE'), False, False, False):
return True
The next does exactly the same thing for Scouts:
if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_SCOUT')):
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_HUNTING_LODGE'), False, False, False):
return True
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_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).
if eUnit == gc.getInfoTypeForString('UNIT_EIDOLON'):
if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ORDER'):
return True
if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_RUNES_OF_KILMORP H'):
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.
if eUnit == gc.getInfoTypeForString('UNIT_PALADIN'):
if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ASHEN_VEIL') :
return True
if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_OCTOPUS_OVERLORD S'):
return True
A single block that keeps civs with the Ashen Veil state religion from building High Priests.
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.
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.
if eUnit == gc.getInfoTypeForString('UNIT_ROYAL_GUARD'):
if pPlayer.getCivics(gc.getInfoTypeForString('CIVICOP TION_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.
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!
naf4ever Mar 09, 2006, 11:22 AM 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.
Lord Olleus Mar 09, 2006, 02:25 PM Incase anybody here wanted to know the game uses 1.5 as the square root of 2. And when using the getPathDistance function (which finds the passable distance between 2 plots) it does use pythagoras. Just a very inacurate value.
The Butler Mar 09, 2006, 04:43 PM Hey I was wondering if anyone can help me im trying to get the hang on python and understand the concept but it hasn't twigged yet what I can and can't do and how to go about it so I was wondering if someone more knowledgeabla can help me out
Upon reading forums and experimenting I've implemented a terraform mod into the mod i'm making that allows a workboat to turn a sea tile into a land tile
elif(iImprovement==lowerelev):
if(pPlot.getPlotType()==PlotTypes.PLOT_LAND):
numberofunits = pPlot.getNumUnits()
while(numberofunits > 0):
pPlot.getUnit(0).jumpToNearestValidPlot()
numberofunits = pPlot.getNumUnits()
pPlot.setPlotType(PlotTypes.PLOT_OCEAN, true, true)
else:
pPlot.setPlotType(PlotTypes.PLOT_LAND, true, true)
if(pPlot.getTerrainType()==gc.getInfoTypeForString ("TERRAIN_DESERT") and pPlot.isRiver()):
pPlot.setFeatureType(gc.getInfoTypeForString("FEATURE_FLOOD_PLAINS"), 0)
pPlot.setImprovementType(-1)
(stolen from tywiggins terraform mod)
upon completion of the terraform i'd like either a settler unit or a city to be built on the tile too.
From reading I discovered that something like this
def onCityBuilt(self, argsList):
'For player 1, create a Warrior (index 17) in every city that is built'
city = argsList[0]
if city.getOwner() == 1:
city.getOwner().initUnit(17, city.getX(), city.getY(), UnitAITypes.NO_UNITAI)
is responsible for creating a warrior in a city when it was built so what I want to know is how/is it possible to adapt this code and implement it into the above terraform code so that when a tile is improved a unit is created?
Even better would be if when a tile is improved, in this case that means terraformed from an ocean to grass tile, a city is built. However I don't know where to start with city building.
Thanks for your time reading and any help would be wonderful
Kael Mar 22, 2006, 08:55 AM Hey I was wondering if anyone can help me...
This thread is just for posting working code, please post questions in the creation and customization forum and Im sure people would be glad to help.
Kael Mar 22, 2006, 09:03 AM In celebration of C.Rolands awesome "fire elemental" I give you the code I am using with it in Phase 2 (I call it a Pyre Zombie).
http://forums.civfanatics.com/showthread.php?t=164035
if pLoser.getUnitType() == gc.getInfoTypeForString('UNIT_PYRE_ZOMBIE'):
pPlot = pWinner.plot()
point = pPlot.getPoint()
for i in range(pPlot.getNumUnits()):
pUnit = pPlot.getUnit(i)
iRnd = CyGame().getSorenRandNum(30, "Bob") - 10
if pUnit.isHasPromotion('PROMOTION_MAGIC_RESISTANCE') :
iRnd = iRnd - 10
if pUnit.isHasPromotion('PROMOTION_FIRE_RESISTANCE'):
iRnd = iRnd - 10
if pUnit.isHasPromotion('PROMOTION_VULNERABLE_TO_FIRE '):
iRnd = iRnd + 10
if iRnd >= 1:
pUnit.setDamage(pUnit.getDamage() + iRnd, True)
CyEngine().triggerEffect(cf.FFHgetEffectInfo('EFFE CT_ARTILLERY_SHELL_EXPLODE'),point)
CyAudioGame().Play3DSound("AS3D_UN_GRENADE_EXPLODE",point.x,point.y,point.z)
What it does:
When the Pyre Zombie is killed he explodes on the stack the winning unit is in. There is a explosion effect and sound that goes along with it. Damage is applied randomly and about 2/3 of the stack will take 0-20% damage from the explosion. This damage is modified by some of the promotions the units have.
Use:
Its really pretty contained code. This function is run out of onCombatResult in the CvEventManager.py file. I like it because its a good short sample that does a lot of interesting stuff and it shows how you can write your own functions to add some detail to game functions (in this case collateral damage) that may not do exactly what you want. I didn't like collateral for this effect because I only wanted it applied when the zombie died and I wanted it adjusted based on some promotions.
Enjoy!
naf4ever Mar 22, 2006, 03:10 PM Wow that code rocks Kael.... BTW ive used the .getSorenRandNum() function before. What is really suppose to go in the place where you typed "bob"? And does it affect anything besides your own enjoyment?
Lord Olleus Mar 22, 2006, 03:18 PM 'bob' is the name of the random number generated and is only used by the debug files. How exactly do you create new missions in python? I've heard of mods where a unit could be used to create another unit but how do you do this *cough* Kael-I-know-you-did-this *cough*
Kael Mar 22, 2006, 03:50 PM 'bob' is the name of the random number generated and is only used by the debug files. How exactly do you create new missions in python? I've heard of mods where a unit could be used to create another unit but how do you do this *cough* Kael-I-know-you-did-this *cough*
And I think its always "Bob" in my code because The Great Apple listed a sample way way back and he called it "Bob" so now everyone has Bob in their code for no real reason. :)
Making units is pretty easy, the hard part is what python event do you intercept to do it. I have some in onCombatresult (thats where defeated units are made into slaves, werewolves are created and defeated animals are captured).
But for more user initiated summons I intercept in the promotion function. every turn I reset my spellcasters xp and level so they are set to level. Then they get to pick their "promotion". I intercept the promotion function, see which one they picked and perform some action based on which promotion/spell they picked. Then I strip the promotion away and start over next turn.
The nice thing about the promotion method is the AI understands it. By changing the supposed effect of the promotion (for those of you wondering why the summon demon promotion gives +150% strength when it only lasts for millisecond) you can give the AI a clue as to which one it whould pick. So for example if it can fireball or summon a demon it will summon the demon.
Here is the code:
if iPromotion == gc.getInfoTypeForString('PROMOTION_SUMMON_DEMON'):
newUnit = pPlayer.initUnit(gc.getInfoTypeForString('UNIT_DEM ON'), pUnit.getX(), pUnit.getY(), UnitAITypes.NO_UNITAI)
pUnit.setHasPromotion(gc.getInfoTypeForString('PRO MOTION_SUMMON_DEMON'), False)
cf.FFHApplySpellMods(newUnit, pUnit)
Chinese American Mar 23, 2006, 05:37 PM For some reason the game doesn't have functions to return the number of turns left before a city advances to the next Culture or Great People level. But it's very easy to find that value and show it on your city's culture or GP bar. see attachments to see new culture and GP bars that show turns to completion.
First open CvMainInterface.py (backup if necessary) and look for these strings:
screen.setLabel( "CultureText",
screen.setLabel( "GreatPeopleText",
These are the labels you see on the city screen that say something like [Culture_Icon]: Poor ( +2/Turn ). We're gonna append the turns left.
So we find the value for turns left for culture. Thus:
## FU edit start -- append Turns to Complete
iCulRate = pHeadSelectedCity.getCommerceRate(CommerceTypes.CO MMERCE_CULTURE)
if ( iCulRate > 0 ):
iCulNext = pHeadSelectedCity.getCultureThreshold()
iCulNow = pHeadSelectedCity.getCulture(pHeadSelectedCity.get Owner())
iCulTurns = (iCulNext-iCulNow+iCulRate-1)/iCulRate
szTurnsToComplete = " %d Turns" %(iCulTurns)
szBuffer = szBuffer + szTurnsToComplete
szTurnsToComplete = ""
## FU edit end
screen.setLabel( "CultureText", "Background", szBuffer, CvUtil.FONT_CENTER_JUSTIFY, 125, yResolution - 184, -1.3, FontTypes.GAME_FONT, WidgetTypes.WIDGET_GENERAL, -1, -1 )
szBuffer holds the string that shows up on screen. We put together szBuffer + szTurnsToComplete.
We do the same for Great People.
## FU edit start -- append turns to completion
iGPRate = pHeadSelectedCity.getGreatPeopleRate()
if ( iGPRate > 0 ):
iGPNext = gc.getPlayer( pHeadSelectedCity.getOwner() ).greatPeopleThreshold()
iGPNow = pHeadSelectedCity.getGreatPeopleProgress()
iGPTurns = (iGPNext-iGPNow+iGPRate-1)/iGPRate
szTurnsToComplete = " %d Turns" %(iGPTurns)
szBuffer = szBuffer + szTurnsToComplete
szTurnsToComplete = ""
## FU edit end
screen.setLabel( "GreatPeopleText", "Background", szBuffer, CvUtil.FONT_CENTER_JUSTIFY, xResolution - 146, yResolution - 176, -1.3, FontTypes.GAME_FONT, WidgetTypes.WIDGET_GENERAL, -1, -1 )
iCulTurns and iGPTurns have those equations so that we don't have to import math and use ceil function. With math.ceil, it would be iGPTurns = math.ceil(float(iGPNext-iGPNow)/iGPRate)
Lord Olleus Mar 26, 2006, 01:59 AM Someone should sticky this thread. I know I find it very usefull and its a nuissance to have to keep looking for it.
Kael Mar 26, 2006, 06:51 AM def startWar(iPlayer,i2Player):
iTeam = gc.getPlayer(iPlayer).getTeam()
i2Team = gc.getPlayer(i2Player).getTeam()
eTeam = gc.getTeam(iTeam)
if (eTeam.isAtWar(i2Team) == False and iTeam != i2Team):
eTeam.declareWar(i2Team, False)
What it does:
This is a very simple function, it literally just causes iPlayer to declare war on i2Player. Both of the players have to be passed to the function. The function checks to make sure they aren't already at war and that they aren't on the same team (which also keeps players from declaring war on themselves, a problem I had in early FfH versions).
Use:
It may seem werid to have such a simple function seperated out. But I found that as I made more and more functions that caused players to declare war on each other that it was very handy to have one function that did all the hard work. Also the fact that players don't declare war on player's teams declare war on teams was always confusing me and it seemed like I always made a mistake when I tried to rewrite these functions. Mostly because a "Team object" delcares war on a "Team int" instead of another team object.
Note that the first player passed to the function declares war on the 2nd, which is significant when pacts and such are calculated.
IVZanIV Mar 26, 2006, 08:41 AM I second the sticky suggestion.
12monkeys Mar 26, 2006, 01:49 PM I want to add my part to that thread with that function below. It is used in the next version of my Plot List Enhancements and calculates the heal factor of a unit. The heal factor is needed to calculate the number of turns a wounded units needs to get 100% healed.
The function is called with only the unit to be examined as parameter.
Here is the code:
def getPlotHealFactor(pUnit):
# heal rates for certain areas. They are usually stored in the GlobalDefines.XML but can't be read out with a standard API function.
# So I placed them here as constants.
ENEMY_HEAL_RATE = 5
NEUTRAL_HEAL_RATE = 10
FRIENDLY_HEAL_RATE = 15
CITY_HEAL_RATE = 20
# set/reset some variables
pPlot = pUnit.plot()
iSameTileHealFactor = 0
iAdjacentTileHealFactor = 0
iBuildingHealFactor = 0
iSelfHealFactor = 0
iPromotionHealFactor = 0
iTileHealFactor = 0
iActivePlayer = CyGame().getActivePlayer()
pActivePlayer = gc.getPlayer(iActivePlayer)
iActivePlayerTeam = pActivePlayer.getTeam()
eDomain = gc.getUnitInfo(pUnit.getUnitType()).getDomainType( )
# a sea or air unit in a city, behaves like a land unit
if pPlot.isCity():
eDomain = DomainTypes.DOMAIN_LAND
# calculate the adjacent-tile heal-factor caused by other units (only the unit with the highest factor counts)
for dx in range(-1, 2):
for dy in range(-1, 2):
# ignore same tile. Adjacent-tile healing does not work on the same tile.
if not (dx == 0 and dy == 0):
pLoopPlot = CyMap().plot(pPlot.getX()+dx, pPlot.getY()+dy)
# loop through all units on the plot
for i in range(pLoopPlot.getNumUnits()):
pLoopUnit = pLoopPlot.getUnit(i)
eLoopUnitDomain = gc.getUnitInfo(pLoopUnit.getUnitType()).getDomainT ype()
# a sea or air unit in a city, behaves like a land unit
if ((eDomain == DomainTypes.DOMAIN_SEA) or (eDomain == DomainTypes.DOMAIN_AIR)) and pLoopPlot.isCity():
eLoopUnitDomain = DomainTypes.DOMAIN_LAND
# adjacent-tile heal does only work if the units have the same domain type
if (eDomain == eLoopUnitDomain):
if (pLoopUnit.getTeam() == iActivePlayerTeam):
if (pLoopUnit.getAdjacentTileHeal() > iAdjacentTileHealFactor):
iAdjacentTileHealFactor = pLoopUnit.getAdjacentTileHeal()
# calculate the same-tile heal-factor caused by other or same unit (only the unit with the highest factor counts)
# the same-tile healing is also a kind of self-healing. Means : the promotion Medic I has also effect on the owner unit
for i in range(pPlot.getNumUnits()):
pLoopUnit = pPlot.getUnit(i)
eLoopUnitDomain = gc.getUnitInfo(pLoopUnit.getUnitType()).getDomainT ype()
# a sea or air unit in a city, behaves like a land unit
if pLoopPlot.isCity():
eLoopUnitDomain = DomainTypes.DOMAIN_LAND
# same tile heal does only work if the units are of the same domain type
if (eDomain == eLoopUnitDomain):
if (pLoopUnit.getTeam() == iActivePlayerTeam):
if (pLoopUnit.getSameTileHeal() > iSameTileHealFactor):
iSameTileHealFactor = pLoopUnit.getSameTileHeal()
# only the highest value counts
iTileHealFactor = max(iAdjacentTileHealFactor, iSameTileHealFactor)
# calculate the self heal factor by the location and promotion
iTeam = pPlot.getTeam()
pTeam = gc.getTeam(iTeam)
iSelfHealFactor = NEUTRAL_HEAL_RATE
iPromotionHealFactor = pUnit.getExtraNeutralHeal()
if pPlot.isCity():
iSelfHealFactor = CITY_HEAL_RATE
iPromotionHealFactor = pUnit.getExtraFriendlyHeal()
elif (iTeam == iActivePlayerTeam):
iSelfHealFactor = FRIENDLY_HEAL_RATE
iPromotionHealFactor = pUnit.getExtraFriendlyHeal()
elif (iTeam != TeamTypes.NO_TEAM):
if (pTeam.isAtWar(iActivePlayerTeam)):
iSelfHealFactor = ENEMY_HEAL_RATE
iPromotionHealFactor = pUnit.getExtraEnemyHeal()
# calculate the heal factor by city buildings
if pPlot.isCity():
if (pPlot.getTeam() == iActivePlayerTeam):
pCity = pPlot.getPlotCity()
# loop for all buldings
for iBuilding in range(gc.getNumBuildingClassInfos()):
# check if city has that building
if pCity.hasBuilding(iBuilding):
# sum up all heal rates
iBuildingHealFactor += gc.getBuildingInfo(iBuilding).getHealRateChange()
# return the sum of all heal factors
return iTileHealFactor + iBuildingHealFactor + iSelfHealFactor + iPromotionHealFactor
Lets step through the main parts :
first few lines are used to set some variables and constants. Nothing special here:
# heal rates for certain areas. They are usually stored in the GlobalDefines.XML but can't be read out with a standard API function.
# So I placed them here as constants.
ENEMY_HEAL_RATE = 5
NEUTRAL_HEAL_RATE = 10
FRIENDLY_HEAL_RATE = 15
CITY_HEAL_RATE = 20
# set/reset some variables
pPlot = pUnit.plot()
iSameTileHealFactor = 0
iAdjacentTileHealFactor = 0
iBuildingHealFactor = 0
iSelfHealFactor = 0
iPromotionHealFactor = 0
iTileHealFactor = 0
iActivePlayer = CyGame().getActivePlayer()
pActivePlayer = gc.getPlayer(iActivePlayer)
iActivePlayerTeam = pActivePlayer.getTeam()
eDomain = gc.getUnitInfo(pUnit.getUnitType()).getDomainType( )
This one is quite important to know. A sea or air unit can be healed by a land unit, but only when its in a city. This leads to 2 major conclusions :
- a sea unit with promotion Medic I (same tile heal +10%) located in a city can heal a land unit located in the city and can also heal a land unit with Medic II (adjacent tile heal + 10%) if the unit is on a cities adjacent tile.
- a sea unit on the coast with Medic II can't heal a land unit on an adjacent tile.
Due to that I set the units domain to DOMAIN_LAND when the units to be examined it is located in a city.
# a sea or air unit in a city, behaves like a land unit
if pPlot.isCity():
eDomain = DomainTypes.DOMAIN_LAND
Next the adjacent tile heal is watched. A loop though all adjacent tiles for a friendly (own or teammate) unit which has a protmotion with adjacent tile heal. I don't look for the promotion itself, but for its effects on the heal factor with the function CyUnit.getAdjacentTileHeal(). This makes the function mod friendly.
Two things to be mentioned :
- the same tile is ignored, because the adjacent tile heal has no effect on the same tile!!!
- if the adjacent plot is a city, also the air and sea units are considered.
# calculate the adjacent-tile heal-factor caused by other units (only the unit with the highest factor counts)
for dx in range(-1, 2):
for dy in range(-1, 2):
# ignore same tile. Adjacent-tile healing does not work on the same tile.
if not (dx == 0 and dy == 0):
pLoopPlot = CyMap().plot(pPlot.getX()+dx, pPlot.getY()+dy)
# loop through all units on the plot
for i in range(pLoopPlot.getNumUnits()):
pLoopUnit = pLoopPlot.getUnit(i)
eLoopUnitDomain = gc.getUnitInfo(pLoopUnit.getUnitType()).getDomainT ype()
# a sea or air unit in a city, behaves like a land unit
if pLoopPlot.isCity():
eLoopUnitDomain = DomainTypes.DOMAIN_LAND
# adjacent-tile heal does only work if the units have the same domain type
if (eDomain == eLoopUnitDomain):
if (pLoopUnit.getTeam() == iActivePlayerTeam):
if (pLoopUnit.getAdjacentTileHeal() > iAdjacentTileHealFactor):
iAdjacentTileHealFactor = pLoopUnit.getAdjacentTileHeal()
Now we examine the friendly units on the same plot. More or less the same stuff as before. One thing to be mentioned here :
- the same tile healing (as it is with the promotion Medic I) is also a self healing. Means : it has also effect on the promotions owner unit!!.
# calculate the same-tile heal-factor caused by other or same unit (only the unit with the highest factor counts)
# the same-tile healing is also a kind of self-healing. Means : the promotion Medic I has also effect on the owner unit
for i in range(pPlot.getNumUnits()):
pLoopUnit = pPlot.getUnit(i)
eLoopUnitDomain = gc.getUnitInfo(pLoopUnit.getUnitType()).getDomainT ype()
# a sea or air unit in a city, behaves like a land unit
if pLoopPlot.isCity():
eLoopUnitDomain = DomainTypes.DOMAIN_LAND
# same tile heal does only work if the units are of the same domain type
if (eDomain == eLoopUnitDomain):
if (pLoopUnit.getTeam() == iActivePlayerTeam):
if (pLoopUnit.getSameTileHeal() > iSameTileHealFactor):
iSameTileHealFactor = pLoopUnit.getSameTileHeal()
Because the two values adjacent tile heal factor and same tile heal factor are not summed up, we can only use the largest of both values.
# only the highest value counts
iTileHealFactor = max(iAdjacentTileHealFactor, iSameTileHealFactor)
Next we are looking on the self healing factor. It is influenced by two factors : the territory (friendly, neutral or enemy) the unit is located on and several promotions which could increase this territory factor. Which territory the unit is currently on is determined by the plot's owner. If it's the player or a teammate we have friendly territory, if it's somebody we have war with it's enemy territory. Anything else is neutral.
# calculate the self heal factor by the location and promotion
iTeam = pPlot.getTeam()
pTeam = gc.getTeam(iTeam)
iSelfHealFactor = NEUTRAL_HEAL_RATE
iPromotionHealFactor = pUnit.getExtraNeutralHeal()
if pPlot.isCity():
iSelfHealFactor = CITY_HEAL_RATE
iPromotionHealFactor = pUnit.getExtraFriendlyHeal()
elif (iTeam == iActivePlayerTeam):
iSelfHealFactor = FRIENDLY_HEAL_RATE
iPromotionHealFactor = pUnit.getExtraFriendlyHeal()
elif (iTeam != TeamTypes.NO_TEAM):
if (pTeam.isAtWar(iActivePlayerTeam)):
iSelfHealFactor = ENEMY_HEAL_RATE
iPromotionHealFactor = pUnit.getExtraEnemyHeal()
Finally we are looking at the buildings which may have influence to the healing. Of course, this is only done when we are in a players or teammates city. Then we loop through all buildings in the city looking for the buildings influence on healing. Currently there is only one standard building doing so : the hospital.
# calculate the heal factor by city buildings
if pPlot.isCity():
if (pPlot.getTeam() == iActivePlayerTeam):
pCity = pPlot.getPlotCity()
# loop for all buldings
for iBuilding in range(gc.getNumBuildingClassInfos()):
# check if city has that building
if pCity.hasBuilding(iBuilding):
# sum up all heal rates
iBuildingHealFactor += gc.getBuildingInfo(iBuilding).getHealRateChange()
Last thing is to sum up all the single heal factors and return the value.
# return the sum of all heal factors
return iTileHealFactor + iBuildingHealFactor + iSelfHealFactor + iPromotionHealFactor
The value returned by this function can now be used to calculate the number of turns a unit needs to be 100% healed. An implemenation may be as follows:
if (eUnitDomain == DomainTypes.DOMAIN_AIR):
fCurrStrength = float(pUnit.airCurrCombatStr()*0.01)
fMaxStrength = float(pUnit.airMaxCombatStr()*0.01)
else:
fCurrStrength = float(pUnit.baseCombatStr())*float(1.0-pUnit.getDamage()*0.01)
fMaxStrength = float(pUnit.baseCombatStr())
iTurnsToHeal = int((fMaxStrength-fCurrStrength)/float(fMaxStrength*float(mt.getPlotHealFactor(pUni t))*0.01)+0.999) # force to round up
The python used here is very likly not the best possible. As many others here, I learnt python with civ4, allthough I have some years of experience as professional programmer. But this is 10 years away now.
The knowledge about healing in civ4 I gained by try and error and lots of investigations and experiments with a random map and the world builder. There still may be some details about healing not covered by this function, but I think it offers a quite good overview about the civ4 healing details and some python basics.
Kael Mar 26, 2006, 01:51 PM Thats an amazing contribution 12monkeys! Not only excellent code but a great example of how to implement a whole concept in python. Thanks for sharing it!
IVZanIV Mar 26, 2006, 06:40 PM 12monkeys, if you set the domain of sea and air units to land while in a city, doesn't that mean that air units will not work as they were created ever again? Because air units by default stay in cities and bomb from there, but as land units, they can move, which is against the "city rule"... Sorry if that's wrong...
IVZanIV Mar 26, 2006, 06:47 PM Also, since it is here...
This piece of code is so simple that I'm not sure if it's even worthy of putting here, but it was my first successful venture into Python, and I'm actually kind of proud of it. :crazyeye:
def onLoadGame(self, argsList):
#Displays the "Welcome Back!" Message when a saved game loads.
CyInterface().addImmediateMessage("Welcome back!","")
#ENDLoadGame
return 0
What it does:
This code prints a simple message when the game is loaded, giving a "homely" feel. :D
Use:
Giving your code that extra touch of human emotion.
naf4ever Mar 27, 2006, 12:37 AM Also, since it is here...
This piece of code is so simple that I'm not sure if it's even worthy of putting here,,,,,
Trust me this is very useful. I started programming pretty much from scratch a month ago just so i could mod python civ files.. It took me a week just to figure out how to put up a message like yours. So hopefully you'll save someone else a week of time... heh.
Heres another useful message contribution that can be used at any interrupt point. IVe found it invaluable when learning python and trying to figure out if my programs are actually working or not:
myResult = "whatever the heck your trying to test"
CyInterface().addMessage(CyGame().getActivePlayer( ),True,10,"%s" %(myResult),'',
1,'',ColorTypes(8),-1,-1,True,True)
What it does: Prints a message thats based on a variable, in this case myResult.
Use: If you got some long complex function that gives no python errors yet doesnt seem to be working it can be frustrating debugging it. This allows you to test if the result your trying to achieve is actually happening in the first place and if so what its value is. Especially good if you got some function that involves lots of math or got some loop error you are trying to track down.
12monkeys Mar 27, 2006, 01:07 AM 12monkeys, if you set the domain of sea and air units to land while in a city, doesn't that mean that air units will not work as they were created ever again? Because air units by default stay in cities and bomb from there, but as land units, they can move, which is against the "city rule"... Sorry if that's wrong...
I don't change the unit itself. I just treat them in the code as land units, to simplify the handling. So there is no problem.
IVZanIV Mar 27, 2006, 01:27 PM I started programming pretty much from scratch a month ago just so i could mod python civ files...
Exact same thing for me, and I like it so much now, I don't feel like stopping. :mischief:
I don't change the unit itself. I just treat them in the code as land units, to simplify the handling. So there is no problem.
Ah, ok, that makes sense. Thanks.
mrkingkong Mar 31, 2006, 08:50 AM Hey, which file do you write these codes into for them to actually do anything? Do you put them in the CvEventManager.py file only?
This is very useful, iv learnt a lot so far but iv yet to program my first successful python event (mainly due to my fear it will all go badly wrong)
mrkingkong
TheLopez Mar 31, 2006, 09:04 AM Trust me this is very useful. I started programming pretty much from scratch a month ago just so i could mod python civ files.. It took me a week just to figure out how to put up a message like yours. So hopefully you'll save someone else a week of time... heh.
Heres another useful message contribution that can be used at any interrupt point. IVe found it invaluable when learning python and trying to figure out if my programs are actually working or not:
myResult = "whatever the heck your trying to test"
CyInterface().addMessage(CyGame().getActivePlayer( ),True,10,"%s" %(myResult),'',
1,'',ColorTypes(8),-1,-1,True,True)
What it does: Prints a message thats based on a variable, in this case myResult.
Use: If you got some long complex function that gives no python errors yet doesnt seem to be working it can be frustrating debugging it. This allows you to test if the result your trying to achieve is actually happening in the first place and if so what its value is. Especially good if you got some function that involves lots of math or got some loop error you are trying to track down.
naf4ever that is good but this gives you a lot more flexibility.
Basically this is a file almost completely gutted except for the important code and example. Lets call this file CvFoo.py.
# Import statements
# ...
# ...
# Global variables
# ...
# ...
# Change this to enable or disable debug messages
g_bDebug = true
LOG_ONLY = 1
SCREEN_ONLY = 2
LOG_AND_SCREEN = 3
# Change to redirect the debug messages to the logs, screen or both.
g_iDebugMessageTarget = LOG_ONLY
class CvFoo:
def fooBar(self):
# Debug code - Start
if(g_bDebug):
self.printDebugMessage("Hello, World!")
# Debug code - End
return iHangingBodyCount
# Prints the debug message to the logs, screen or both
def printDebugMessage(self, strMessage):
if(g_iDebugMessageTarget == LOG_ONLY):
CvUtil.pyPrint(strMessage)
elif(g_iDebugMessageTarget == SCREEN_ONLY):
CyInterface().addImmediateMessage(strMessage)
else:
CvUtil.pyPrint(strMessage)
CyInterface().addImmediateMessage(strMessage)
naf4ever Mar 31, 2006, 08:21 PM naf4ever that is good but this gives you a lot more flexibility.
Basically this is a file almost completely gutted except for the important code and example. Lets call this file CvFoo.py.
# Import statements
# ...
# ...
# Global variables
# ...
# ...
# Change this to enable or disable debug messages
g_bDebug = true
LOG_ONLY = 1
SCREEN_ONLY = 2
LOG_AND_SCREEN = 3
# Change to redirect the debug messages to the logs, screen or both.
g_iDebugMessageTarget = LOG_ONLY
class CvFoo:
def fooBar(self):
# Debug code - Start
if(g_bDebug):
self.printDebugMessage("Hello, World!")
# Debug code - End
return iHangingBodyCount
# Prints the debug message to the logs, screen or both
def printDebugMessage(self, strMessage):
if(g_iDebugMessageTarget == LOG_ONLY):
CvUtil.pyPrint(strMessage)
elif(g_iDebugMessageTarget == SCREEN_ONLY):
CyInterface().addImmediateMessage(strMessage)
else:
CvUtil.pyPrint(strMessage)
CyInterface().addImmediateMessage(strMessage)
Hmm... I guess i meant mine for use mainly for when there are no coding errors. When the code is correct but the problem is some loop or incorrect variable being called up, which happens to me a lot. This way i can print its value to see whats going.
Can you explain what yours here does more? How does it differ from the python popup message i get when im in toggle.gamedebugmode ?
TheLopez Mar 31, 2006, 10:19 PM Hmm... I guess i meant mine for use mainly for when there are no coding errors. When the code is correct but the problem is some loop or incorrect variable being called up, which happens to me a lot. This way i can print its value to see whats going.
Can you explain what yours here does more? How does it differ from the python popup message i get when im in toggle.gamedebugmode ?
Sure, it allows you to turn debug messages on/off and where they should be displayed/recorded.
adamamri Apr 01, 2006, 08:10 AM Hello, I do not know if I am on good forium but I have a question. Is you he possible with the Python to make so that the embraque computer of the ICBM in its under sailors. By knowing well on that I already modified code XML por that it is possible. (it makes some would be necessary to modify the IA of the plays)
Thank you
Kael Apr 01, 2006, 08:50 AM Hello, I do not know if I am on good forium but I have a question. Is you he possible with the Python to make so that the embraque computer of the ICBM in its under sailors. By knowing well on that I already modified code XML por that it is possible. (it makes some would be necessary to modify the IA of the plays)
Thank you
You may want to start a thread in the creation and customizaation forum to ask this question. This thread is just for modders to post working python code they have written.
naf4ever Apr 04, 2006, 02:36 AM This is from TGA's post on the first page of this thread: (pretend everything is tabbed)
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)):
Im unclear what the functions under "### Checks for tiles not in city radius" are doing. Im thinking they exclude the squares at the very corners so you only return back squares from the Fat Cross. Is this correct? Also what do the "\" at the end of the lines do?
The Great Apple Apr 04, 2006, 03:44 PM Im thinking they exclude the squares at the very corners so you only return back squares from the Fat Cross. Is this correct?Yup. There is probably a marginally better way involving finding the magnetude of the values, and if both have magnetude 2, avoiding the plot. This way it easier to read though.
Also what do the "\" at the end of the lines do?It signifies "jump to the next line". I'm not actually sure they are neccessary - I find it more readable though.
The Great Apple Apr 11, 2006, 04:12 PM # Finds plot - takes wrapping into account
def findPlot(self, plotX, plotY):
if CyMap().isWrapX():
if plotX >= CyMap().getGridWidth():
plotX -= CyMap().getGridWidth()
elif plotX < 0:
plotX += CyMap().getGridWidth()
if CyMap().isWrapY():
if plotY >= CyMap().getGridHeight():
plotY -= CyMap().getGridHeight()
elif plotY < 0:
plotY += CyMap().getGridHeight()
return CyMap().plot(plotX, plotY)
What it does: Returns the real plot from a pair of co-ordinates which may not necessarily be within the map bounds. It does this by checking the plot is in the bounds of the map, and if it is not, it will change the co-ordinate to the real co-ordinate.
NOTE: I've been a bit lazy and not included instances when there is no wrapping. This is because Civ will recognise an invalid plot, and not let you do anything on it anyway.
Uses: Whenever you want to find all the plots in a specific area around an object you should always check to see if the plot actually lies on the map... and if it does not fix it so that it does.
Kael Jun 12, 2006, 11:57 AM I was playign with the AI_unitUpdate() function in CvGameInterface.py and I was amazed at how flexible it is. Here is an example of being able to control the AI just from python:
def AI_unitUpdate(argsList):
'AI moves units - return 0 to let AI handle it, return 1 to say that the move is handled in python '
#CvUtil.pyPrint( "CvGameInterface.AI_unitUpdate" )
pUnit = argsList[0]
if pUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_GIANT_SPIDER'):
iX = pUnit.getX()
iY = pUnit.getY()
for iiX in range(iX-1, iX+2, 1):
for iiY in range(iY-1, iY+2, 1):
pPlot = CyMap().plot(iiX,iiY)
for i in range(pPlot.getNumUnits()):
if pPlot.getUnit(i).getOwner() != pUnit.getOwner():
return 0
return 1
return gameUtils().AI_unitUpdate(argsList)
What it does: If the unit being controlled is a Giant Spider it looks for units int he surrounding tiles that have a different owner than itself. If it finds one it is allowed to move (presumably to attack, but it is for whatever the AI wants to do with it). If there isn't a unit in the surrounding tile then the spider stays and waits.
Uses: We could have made the spider attack with this function, we could push missions to units, we could have the unit build a road, run away.
We could have a successful attack on an AI city cause an "alarm" building to be created. Then all units within x amount of tiles of a city with an "alarm" could run through some python logic to decide if they should stay where they are, or head to the city.
You could create an event in a scenerio that would cause the AI to all rush for and attack a specific city with this function, all without the SDK. There are a lot of possibilities.
There are similiar functions for AI_chooseTech, AI_chooseProduction, AI_doWar and AI_doDiplo.
Gerikes Jun 12, 2006, 09:50 PM I was playign with the AI_unitUpdate() function in CvGameInterface.py and I was amazed at how flexible it is.
While we're on the subject, I was just wondering if you had by any chance run into the instance where you want the AI_unitUpdate function to run the behaviors of units that are being automated. Typically, it works just as well for simple AI units, and for all human automated units, but if there's any time you want the unit to un-automate all by itself, you'll run into a problem.
The CvUnitAI::AI_update function in the SDK is called from the CvSelectionGroupAI::AI_update function. However, the selection group update does not call the unit update once during the time it's on the stack, since the unit might need to do multiple moves (the first call to CvUnitAI::AI_update will move the unit, the next will push a build mission, etc.). It's interesting code that will sometimes prove troublesome, and the hack to test for an infinite loop has saved my butt a few times :P
while (readyToMove())
{
pHeadUnit = getHeadUnit();
if (pHeadUnit == NULL)
{
break;
}
iTempHack++;
if (iTempHack > 100)
{
FAssert(false);
pHeadUnit->finishMoves();
break;
}
resetPath();
pHeadUnit->AI_update(); //Python function called in this function
if (doDelayedDeath())
{
bDead = true;
break;
}
}
As you can see, the python function might be called multiple times during a CvSelectionGroup::AI_update call. When I was writing that python function, instead of checking for the type of unit, I needed to check for the automate type. Since I'm not expecting that python function to be called with the automate type being NO_AUTOMATE, I didn't include that. Here's the code:
def AI_unitUpdate(self,argsList):
pUnit = argsList[0]
# Added by Gerikes for harvester and destroy automation
pPlot = pUnit.plot()
pGroup = pUnit.getGroup()
pPlayer = gc.getPlayer(pUnit.getOwner())
if (pGroup.getAutomateType() == AutomateTypes.AUTOMATE_HARVEST):
# Snipped code to handle harvester automation
return True # We handled the AI, so don't let the SDK try handling it
if (pGroup.getAutomateType() == AutomateTypes.AUTOMATE_DESTROY):
pTargetUnit = pGroup.getTargetUnit() # getTargetUnit is a function I added to the selection group class
# Something happened to the unit... perhaps someone killed it, who knows.
# All I know is that it's no longer valid, my work is done.
if (pTargetUnit is None):
pGroup.setAutomateType(AutomateTypes.NO_AUTOMATE)
return True # We handled the AI, so don't let the SDK try handling it
# Snipped code handling the "destroy" automation.
return False # Nothing we touched. Let the SDK handle it.
If during the first time the loop in CvSelectionGroupAI::AI_update() is called the python function makes the unit un-automated, the second time through the loop that python function falls through, returns False, and suddenly I have the actual CvUnitAI::AI_update() function running it's course. This is an undesired effect.
My current workaround is this: I've changed the above code in CvSelectionGroupAI as follows:
/*
* Modified by Gerikes to allow for a breakout in case the group needs to be
* un-automatized during their automatization. The added portion is exactly the same
* as AI_isAutomated, but I think an automated unit loses it's inheritance of the AI
* classes after it's automation is turned off.
*/
//while (readyToMove())
while (readyToMove() && (!isHuman() || isAutomated()))
/* End modified by Gerikes */
{
pHeadUnit = getHeadUnit();
if (pHeadUnit == NULL)
{
break;
}
iTempHack++;
if (iTempHack > 100)
{
FAssert(false);
pHeadUnit->finishMoves();
break;
}
resetPath();
pHeadUnit->AI_update();
if (doDelayedDeath())
{
bDead = true;
break;
}
}
/* Added by Gerikes to break out of this function if this unit is no longer automated */
if (isHuman() && !isAutomated() )
{
return false;
}
/* End Added by Gerikes to break out of this function if this unit is no longer automated */
As read in the comments, I tried to use AI_isAutomated, as that function is used in this very same function earlier, but I kept getting run-time errors where that function suddenly was not linked to that unit. Do you think that my assumption is correct that the AI_* functions are no longer linked to the unit after it's lost it's automatization, or is there something else I'm missing?
Gunner Jun 19, 2006, 01:56 PM First of all I'd like to wholeheartdly thank everyone that has contributed to this thread. It really is an amazing resource to an aspiring Civ4 python modder. Keep up the good work :goodjob:
Now for my question. I'm a bit confused about how to use gc.getActivePlayer() and gc.getPlayer(). Doesn't gc.getActivePlayer() just return the human player when in singleplayer? If thats the case then is the following piece of code that Aussie_Lurker correct?
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:
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.getInf oTypeForString("SPECIALIST_SLAVE"), 1)
else:
for i in range(player.getNumCities()):
player.getCity(i).setFreeSpecialistCount(gc.getInf oTypeForString("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.
I was thinking that instead of
player = gc.getActivePlayer()
it should say
player = gc.getPlayer(iPlayer)
I'm assuming that what he was trying to do is supposed to apply to every player, not just the human.
The Great Apple Jun 19, 2006, 02:01 PM You're right. That function was misinterpreted quite a bit when Civ first came out. Should be gc.getPlayer(iPlayer).
Gunner Jun 19, 2006, 02:07 PM Thanks for the response TGA. Is there a way we could change that post by Aussie Lurker on the first page then? I think I'll pm him and tell him to edit it if he can.
ocedius Jul 20, 2006, 08:21 PM more please.
Kael Aug 09, 2006, 07:06 AM I use this for a wonder called the Eyes and Ears Network that Bebematos submited as an entry in the Design a Wonder contest. The wonder grants the owning player any tech that is known by 3 or more player the owning player has open borders agreements with.
if pCity.isHasRealBuilding(gc.getInfoTypeForString('B UILDING_EYES_AND_EARS_NETWORK')):
eTeam = gc.getTeam(pPlayer.getTeam())
listTeams = []
for iPlayer2 in range(gc.getMAX_PLAYERS()):
pPlayer2 = gc.getPlayer(iPlayer2)
if (pPlayer2.isAlive() and iPlayer2 != iPlayer):
iTeam2 = pPlayer2.getTeam()
if eTeam.isOpenBorders(iTeam2):
listTeams.append(gc.getTeam(iTeam2))
if len(listTeams) >= 3:
for iTech in range(gc.getNumTechInfos()):
if eTeam.isHasTech(iTech) == False:
iCount = 0
for i in range(len(listTeams)):
if listTeams[i].isHasTech(iTech):
iCount = iCount + 1
if iCount >= 3:
eTeam.setHasTech(iTech, True, iPlayer, False, True)
CyInterface().addMessage(iPlayer,True,25,'You recived a free Tech from the Eyes and Ears Network.','AS2D_TECH_DING',1,'Art/Interface/Buttons/Buildings/Eyesandearsnetwork.dds',ColorTypes(8),pCity.getX() ,pCity.getY(),True,True)
What it Does: This code is in the onCityDoTurn event in CvEventManager.py. iPlayer, pPlayer and pCity are defined earlier in the file. If the city has the Eyes and Ears Network ("Eyes") then the game checks all of the players in the game. If any of those players has an open border agreement with the Eyes owner then that players team is added to the listTeams list.
The second part of the function only runs if their are more than 3 entries on the list (that part is just to minimize processing since I dont want to perform the tech searches if I wont get any positive hits anyway). Assuming there are 3 or more players that the Eyes owner has open borders agreements with the function goes through all of the techs. If the Eyes owner doesnt have the tech and 3 or more players he has open borders agreements with do have the tech, the Eyes owner gets the tech for free.
Uses: If you are particuarly old school, this function could be easily modified to make the Civ3 great library (you gain any tech if half the players in the game have it). You could also have it give some progress towards the techs instead of the whole tech.
I think the code is also a decent example of a way to build a list based on criteria and then pass the list through a function. I could have checked all players on all techs but this way is more efficient, I will only check the players that matter (those with open borders agreements) and I wont even run the tech checks at all if I have less than 3 open borders agreements.
The code to check and add techs is also useful if you haven't dealt with it before. Its a little confusing since techs are associated with teams, not players, but its not to bad.
Jeckel Aug 14, 2006, 10:43 PM Having things pemited to the screen is cool and printing it to the log is totally helpful, but I am not the quickest person some times and I don't always catch the words on the screen before they disappear and alt tabing to the logs constantly can get to be a hassle. So I use these to methods for my messages.
class Blah:
def alertPlayer(self, iPlayer, message, iColor = 7):
pPlayer = gc.getPlayer(iPlayer)
# Return immediately if the player passed in is invalid
if(pPlayer == None):
return None
# Return immediately if the player passed in is invalid
if(pPlayer.isNone()):
return None
# If the player isn't human then no need to send a message
if (not pPlayer.isHuman()):
return None
eventMessageTimeLong = gc.getDefineINT("EVENT_MESSAGE_TIME_LONG")
szIcon = None
iFlashX = 0
iFlashY = 0
szString = str(message)
CyInterface().addMessage(iPlayer, True, eventMessageTimeLong,
szString, None, 0, szIcon, ColorTypes(iColor),
iFlashX, iFlashY, True, True)
def alertIconPlayer(self, iPlayer, message, pObject, szIcon = None, iColor = 7):
pPlayer = gc.getPlayer(iPlayer)
# Return immediately if the player passed in is invalid
if(pPlayer == None):
return None
# Return immediately if the player passed in is invalid
if(pPlayer.isNone()):
return None
# If the player isn't human then no need to send a message
if (not pPlayer.isHuman()):
return None
eventMessageTimeLong = gc.getDefineINT("EVENT_MESSAGE_TIME_LONG")
strMessage = str(message)
CyInterface().addMessage(iPlayer, True, eventMessageTimeLong,
strMessage, None, 0, szIcon, ColorTypes(iColor),
pObject.getX(), pObject.getY(), True, True)
What it Does: These two methods to basicly the same thing with one small difference. The first method takes 'iPlayer' an int type of a player and verifies it is a valid human player. Second it takes the argument 'message' and turns it into a string. An optional third integer argument may be included to change the color of the text, if none is given the text will default to 7 which is red, I think -1 will make the text uncolored.
The text message is then displayed on the player's screen. In addition to displaying the text on the screen, it will also record the text in the ingame Event Log which is the most helpful part in my opinion.
The second method works just as the first except it takes two additional arguments, the one required and the other optional.
The third argument is required and is used to get the x and y that the icon should be pointed at and can be a pUnit, pCity, pPlot, or any obj that has the .getX() and .getY() methods.
The forth argument is optional and allows you to set which icon will be shown pointing at the before mentioned x y plot.
Uses: I use this to display debuging info when I'm writing code and to give players ingame messages when they are playing. Combine this with the code mentioned earlyer in this thread by TheLopez to turn debug pemiting on and off and add in the printing to txt logs and it gets alot easyer to see what is happening behind the scenes.
mrkingkong Sep 07, 2006, 10:53 AM Please keep contributing to this thread- to those new to the whole Python game (like me) it is invaluable.
On that note, here is my first ever working code which im kinda proud of, simple and in construction tho it is. Note that my added code is in bold:
def onCombatResult(self, argsList):
'Combat Result'
pWinner,pLoser = argsList
playerX = PyPlayer(pWinner.getOwner())
unitX = PyInfo.UnitInfo(pWinner.getUnitType())
playerY = PyPlayer(pLoser.getOwner())
unitY = PyInfo.UnitInfo(pLoser.getUnitType())
if pLoser.getUnitType() == gc.getInfoTypeForString('UNIT_KNIGHT'):
CyInterface().addImmediateMessage("Your knight has survived the fall, and gets to his feet", "")
deadUnitX = pLoser.getX()
deadUnitY = pLoser.getY()
newUnit = playerY.initUnit(gc.getInfoTypeForString('UNIT_SWO RDSMAN'), deadUnitX, deadUnitY, UnitAITypes.NO_UNITAI)
if (not self.__LOG_COMBAT):
return
if playerX and playerX and unitX and playerY:
CvUtil.pyPrint('Player %d Civilization %s Unit %s has defeated Player %d Civilization %s Unit %s'
%(playerX.getID(), playerX.getCivilizationName(), unitX.getDescription(),
playerY.getID(), playerY.getCivilizationName(), unitY.getDescription()))
What it does:
It is run in the onCombatResult event trigger. Checks to see if the losing unit is a Knight, and if it is then gets the Knight's plot and creates a Swordsman on it, along with displaying a corny "Your Knight survived the fall" message.
Use in my mod:
Will be used in my mod with a random number so that there is a random chance that it occurs. Is meant to simulate the Knight surviving his horse's death, and give him another bite at the cherry, as it were. NOTE: Currently the chance of this happening is 100%, as i havnt put in the random number bit yet.
Thanks to Kael and Gerikes for their help with getting this working. Still working on improving it but as its my first working Python thingumajig im kinda proud of it!
mrkingkong
Shqype Jun 02, 2007, 08:47 PM I would like this thread to be stickied as well. Woodelf?
And I encourage people to keep contributing to it! It helps so much and it's getting me back in the mood to work with python again.
Nercury Aug 12, 2007, 01:35 AM Multi-dimensional array. If there already exists something like this in python (built-in), please, let me know :)
class mdArray:
"multi dimensional array handler"
def __init__(self, dimensions, initial = None):
dimlen = len(dimensions)
self._dimensions = [0] * dimlen
self._factors = [0] * dimlen
product = 1
i = dimlen - 1
while i >= 0:
self._dimensions[i] = dimensions[i]
self._factors[i] = product
product *= self._dimensions[i]
i -= 1
self._data = [initial] * product
def getOffset(self, indices):
if len(indices) != len(self._dimensions):
raise IndexError
offset = 0
for i, dim in enumerate(self._dimensions):
if indices[i] < 0 or indices[i] >= dim:
raise IndexError
offset += self._factors[i] * indices[i]
return offset
def __getitem__(self, indices):
return self._data[self.getOffset(indices)]
def __setitem__(self, indices, value):
self._data[self.getOffset(indices)] = value
def get(self):
return self._data
def __len__():
return len(self._data)
Usage:
# to create array, use one of the following:
array2d = mdArray([5,9]) #creates 5x9 2-dimensional array
array3d = mdArray([5,9,2]) #creates 5x9x9 3-dimensional array
array3d = mdArray([5,9],30) #creates 5x9 array with all values equal to 30
#changind data in array:
array2d[1,0] = 12 #sets 1x0 item to 12
array3d[4,8,1] = -30 #sets 4,8,1 item to -30
#reading data:
print array2d[1,0] #prints value 1x0 form array
#getting internal array offset (this class is wrapper around simple 1 dimension array):
array2d.getOffset([1,2]) #returns internal array index
World array for storing tile plot types. Wraps in X automatically. Uses mdArray.
class worldArray(mdArray):
def __init__(self, dimensions, initial = None):
if len(dimensions) != 2: #only allow 2 dimensions
raise IndexError
dimensions_copy = [dimensions[1],dimensions[0]] #reverse dimensions (required to pass plot types corectly)
dimlen = len(dimensions_copy)
self._dimensions = [0] * dimlen
self._factors = [0] * dimlen
product = 1
i = dimlen - 1
while i >= 0:
self._dimensions[i] = dimensions_copy[i]
self._factors[i] = product
product *= self._dimensions[i]
i -= 1
self._data = [initial] * product
def getOffset(self, indices):
if len(indices) != 2: #only allow 2 indices
raise IndexError
indices_copy = [indices[1],indices[0]] #reverse indices (required to match dimensions)
if len(indices_copy) != len(self._dimensions) or len(indices_copy) < 2:
raise IndexError
# make sure array wraps if out of bounds
while indices_copy[0] < 0:
indices_copy[0] += self._dimensions[0]
while indices_copy[0] > self._dimensions[0] - 1:
indices_copy[0] -= self._dimensions[0]
while indices_copy[1] < 0:
indices_copy[1] += self._dimensions[1]
while indices_copy[1] > self._dimensions[1] - 1:
indices_copy[1] -= self._dimensions[1]
offset = 0
for i, dim in enumerate(self._dimensions):
if indices_copy[i] < 0 or indices_copy[i] >= dim:
raise IndexError
offset += self._factors[i] * indices_copy[i]
return offset
def getHeight(self):
return self._dimensions[0]
def getWidth(self):
return self._dimensions[1]
Usage:
#example map-script code:
def generatePlotTypes():
cgc = CyGlobalContext()
map = cgc.getMap()
iW = map.getGridWidth()
iH = map.getGridHeight()
#world is two-dimensional array which wraps in X automagically, so you may not worry about edges
world = worldArray([iW,iH],PlotTypes.PLOT_OCEAN)
#create two land tiles
world[5,5] = PlotTypes.PLOT_LAND
world[5,6] = PlotTypes.PLOT_LAND
# return our world in one-dimensional array to Firaxis to do the rest :)
return world.get()
Nercury Aug 12, 2007, 01:57 AM Array wrapper with binary search optimization:
class bArray:
def __init__(self):
self._data = []
def __setitem__(self, power, newValue):
index, value = self._search(power,0,len(self._data)-1)
if value == None:
self._data.insert(index, [power, newValue])
else:
self._data[index][1] = newValue
def __getitem__(self, power):
index, value = self._search(power,0,len(self._data)-1)
return value
def __len__(self):
return len(self._data)
# removes element by index
def pop(self, index = None):
if index == None:
return self._data.pop()
else:
return self._data.pop(index)
# removes element by key
def remove(self, power):
index, value = self._search(power,0,len(self._data)-1)
if value != None:
self._data.pop(index)
return True
else:
return False
# returns random element from array and removes it
def random(self, dice):
if len(self._data) == 0:
return None
else:
item = dice.get(len(self._data),"Python - random bArray item with removing.")
value = self._data[item][1]
self._data.pop(item)
return value
# returns random element from array
def randomItem(self, dice):
if len(self._data) == 0:
return None
else:
item = dice.get(len(self._data),"Python - random bArray item without removing.")
value = self._data[item][1]
return value
# internal function. searches for key value in array in specified limits
# if fails, returns None
def _search(self,value,fromI,toI):
left = fromI
right = toI
while left <= right:
mid = (right-left) / 2 + left
if value > self._data[mid][0]:
left = mid + 1
elif value < self._data[mid][0]:
right = mid-1
else:
return mid, self._data[mid][1]
return left, None
# iteration
def __iter__(self):
self.index = -1
return self
def next(self):
if self.index == len(self._data) - 1:
raise StopIteration
self.index += 1
return self._data[self.index][1]
Usage:
This array is useful when you have large amount of data indexed by keys (for example, in my case, continent tile list, indexed by world.getOffset()), and want to check if specific key exists in that list VERY often. Or, you want to be able to access data very quickly. Keys are unique.
# creating array:
a = bArray()
# adding data:
a[2] = "data1" #data with key 2
a[5] = "data2" #data with key 5
a[2] = "data3" #update item with key 2 to "data3"
#retrieving data:
print a[5]
#check if key exists
if a[2] != None:
print "exists"
#iterating data:
for item in a:
print item
#retrieving random item:
item = a.randomItem(dice)
#retrieving random item, and removing it at the same time:
item = a.random(dice)
Darque Sep 19, 2007, 02:06 AM I have one request for a small python script sample. I need a script to add a button to a unit, that when pressed, the button will fire off a custom event.
I can add the button to the unit, and I can set the graphics for it. But I cannot figure out how to assign it to fire off another python function.
dutchking Sep 26, 2007, 06:48 PM I'm trying to learn Python. It's all disorganized and crazy to me. :crazyeye: XML is a dream compared with Python. :D How can I embed a Leaderhead in an Advisor screen? *hint hint ;) Someone please PM me. Meanwhile I'll be messing around with Python files...:hmm:
Seven05 Oct 01, 2007, 08:38 AM Here's my simple contribution, similar to an example previously posted by Kael, in CvGameInterface.pv:
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 pCity.isCoastal(10):
#Default number of ships required (default = number of active civs in game)
#with a minimum value of 4 to make sure the AI can build enough ships in small
#games
iShipsRequired = min(4,CyGame().getNumCivPlayers())
if eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_WORKBOAT'):
iWorkboat = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_WORKBOAT')
iShipsRequired = min(3,pPlayer.getNumCities())
iShipsRequired -= pPlayer.getUnitClassCount(iWorkboat)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_GALLEY'):
iGalley = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_GALLEY')
iShipsRequired -= pPlayer.getUnitClassCount(iGalley)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_TRIREME'):
iGalley = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_GALLEY')
iTrireme = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_TRIREME')
iShipsRequired *= 2
iShipsRequired -= pPlayer.getUnitClassCount(iGalley)
iShipsRequired -= pPlayer.getUnitClassCount(iTrireme)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_CARAVEL'):
iCaravel = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_CARAVEL')
iShipsRequired = max(3,int(CyGame().getNumCivPlayers() / 2))
iShipsRequired -= pPlayer.getUnitClassCount(iCaravel)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_GALLEON'):
iGalleon = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_GALLEON')
if pPlayer.getUnitClassCount(iGalleon) > iShipsRequired:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_PRIVATEER'):
iPrivateer = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_PRIVATEER')
iShipsRequired *= 2
iShipsRequired -= pPlayer.getUnitClassCount(iPrivateer)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_FRIGATE'):
iShipOfTheLine = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_SHIP_OF_THE_LINE')
iFrigate = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_FRIGATE')
iShipsRequired = pPlayer.getNumCities() * 2
iShipsRequired -= pPlayer.getUnitClassCount(iShipOfTheLine)
iShipsRequired -= pPlayer.getUnitClassCount(iFrigate)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_SHIP_OF_THE_LIN E'):
iShipsRequired = pPlayer.getNumCities()
iIronclad = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_IRONCLAD')
iShipOfTheLine = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_SHIP_OF_THE_LINE')
iShipsRequired -= pPlayer.getUnitClassCount(iIronclad)
iShipsRequired -= pPlayer.getUnitClassCount(iShipOfTheLine)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_IRONCLAD'):
iIronclad = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_IRONCLAD')
iShipOfTheLine = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_SHIP_OF_THE_LINE')
iShipsRequired -= pPlayer.getUnitClassCount(iIronclad)
iShipsRequired -= pPlayer.getUnitClassCount(iShipOfTheLine)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_TRANSPORT'):
iTransport = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_TRANSPORT')
iGalleon = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_GALLEON')
iShipsRequired -= pPlayer.getUnitClassCount(iTransport)
iShipsRequired -= pPlayer.getUnitClassCount(iGalleon)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_DESTROYER'):
iDestroyer = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_DESTROYER')
iShipOfTheLine = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_SHIP_OF_THE_LINE')
iFrigate = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_FRIGATE')
iIronclad = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_IRONCLAD')
iShipsRequired = pPlayer.getNumCities() * 2
iShipsRequired -= pPlayer.getUnitClassCount(iShipOfTheLine)
iShipsRequired -= pPlayer.getUnitClassCount(iFrigate)
iShipsRequired -= pPlayer.getUnitClassCount(iIronclad)
iShipsRequired -= pPlayer.getUnitClassCount(iDestroyer)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_STEALTH_DESTROY ER'):
iStealthDestroyer = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_STEALTH_DESTROYER')
iDestroyer = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_DESTROYER')
iShipOfTheLine = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_SHIP_OF_THE_LINE')
iFrigate = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_FRIGATE')
iIronclad = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_IRONCLAD')
iShipsRequired = pPlayer.getNumCities() * 2
iShipsRequired -= pPlayer.getUnitClassCount(iShipOfTheLine)
iShipsRequired -= pPlayer.getUnitClassCount(iFrigate)
iShipsRequired -= pPlayer.getUnitClassCount(iIronclad)
iShipsRequired -= pPlayer.getUnitClassCount(iDestroyer)
iShipsRequired -= pPlayer.getUnitClassCount(iStealthDestroyer)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_BATTLESHIP'):
iBattleship = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_BATTLESHIP')
iShipsRequired -= pPlayer.getUnitClassCount(iBattleship)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_MISSILE_CRUISER '):
iMissileCruiser = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_MISSILE_CRUISER')
iShipsRequired -= pPlayer.getUnitClassCount(iMissileCruiser)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_SUBMARINE'):
iSubmarine = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_SUBMARINE')
iShipsRequired -= pPlayer.getUnitClassCount(iSubmarine)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_ATTACK_SUBMARIN E'):
iAttackSubmarine = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_ATTACK_SUBMARINE')
iShipsRequired -= pPlayer.getUnitClassCount(iAttackSubmarine)
if iShipsRequired < 1:
return True
elif eUnit.getUnitClassType() == gc.getInfoTypeForString('UNITCLASS_CARRIER'):
iCarrier = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_CARRIER')
iBattleship = CvUtil.findInfoTypeNum(gc.getUnitClassInfo, gc.getNumUnitClassInfos(), 'UNITCLASS_BATTLESHIP')
iShipsRequired = pPlayer.getUnitClassCount(iBattleship)
iShipsRequired -= pPlayer.getUnitClassCount(iCarrier)
if iShipsRequired < 1:
return True
return gameUtils().cannotTrain(argsList)
What does it do?
It's pretty simple, if an AI controlled coastal city is trying to train a new unit this evaluates their current navy and restricts the training of additional ships. First we make sure the player isn't human and the city is on the coast and then we iterate through the various ships by unitclass. Remember this is in CANNOT TRAIN, so returning True means they can't train the unit in question. At the end we return the default result to catch everything else.
Use:
The code is pretty straight forward. In my mod the AI had a tendancy to build a HUGE navy, large enough to cripple their ability to build a proper land army. So I added some basic rules per ship class to help them along, these rules are easily adjusted changing iShipsRequired as needed. This also all but eliminates the AI using workboats to explore the map, at least until they have no more coastal resources.
NOTE: isCoastal(x) should be adjusted so that x = maximum freshwater lake size + 1, the default is 9 so most people will want to use 10. That's the minimum water area size used to determine if the city is coastal in Warlords and BTS, Vanilla Civ4 accepts no args for isCoastal() so you need no value in the ()'s.
IdiotsOpposite Jan 15, 2008, 05:22 PM Ok, will do. I'll put up my cannotTrain function from the CvGameInterface.py file:
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('BUIL DING_BARRACKS'), False, False, False):
return True
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_ARCHERY_RANGE'), False, False, False):
return True
if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_SCOUT')):
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_HUNTING_LODGE'), False, False, False):
return True
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_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_KILMORP H'):
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_OVERLORD S'):
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('CIVICOP TION_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:
if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_WARRIOR')):
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_BARRACKS'), False, False, False):
return True
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_ARCHERY_RANGE'), False, False, False):
return True
The next does exactly the same thing for Scouts:
if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_SCOUT')):
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_HUNTING_LODGE'), False, False, False):
return True
if pPlayer.canConstruct(gc.getInfoTypeForString('BUIL DING_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).
if eUnit == gc.getInfoTypeForString('UNIT_EIDOLON'):
if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ORDER'):
return True
if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_RUNES_OF_KILMORP H'):
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.
if eUnit == gc.getInfoTypeForString('UNIT_PALADIN'):
if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ASHEN_VEIL') :
return True
if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_OCTOPUS_OVERLORD S'):
return True
A single block that keeps civs with the Ashen Veil state religion from building High Priests.
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.
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.
if eUnit == gc.getInfoTypeForString('UNIT_ROYAL_GUARD'):
if pPlayer.getCivics(gc.getInfoTypeForString('CIVICOP TION_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.
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!
This is good stuff for something I'm trying to do with python, but how would you apply it for civics? I'm trying to make it so that if Hinduism is not your state religion, you cannot run Caste System, and if it is, you are REQUIRED to run Caste System. How would you change "eUnit" to a labor civic?
moopoo Mar 24, 2008, 09:10 PM Hey I keep reading in the codes you put up, "arglist". What's an Arglist?
Aussie_Lurker Mar 24, 2008, 09:13 PM OK, now I could be wrong, but I believe it is short for "Argument List". As a non-professional Coder, I believe it lists the situations for which a line of code might be the case. A proper coder could tell you what it really means though.
moopoo Mar 25, 2008, 12:44 AM that would make sense. so are all these instances of arglist referring to the same list?
Fezzik May 27, 2008, 05:01 PM Hey I keep reading in the codes you put up, "arglist". What's an Arglist?
As Aussie said, it is an "argument list". It is similar to argument unpacking (http://docs.python.org/tut/node6.html#SECTION006740000000000000000), except what Firaxis did was define each function as accepting a single argument, a list, which contains all of the real arguments.
So your normal Python function might look like this:
def cannotTrain(pCity, eUnit, bContinue, bTestVisible):
# do something
return False
# And would call the function like this
if cannotTrain(pCity, eUnit, bContinue, bTestVisible):
pass
Whereas Firaxis did this:
def cannotTrain(argsList):
pCity = argsList[0]
eUnit = argsList[1]
bContinue = argsList[2]
bTestVisible = argsList[3]
# do something
return False
# And would call the function like this
# Pack the list first
argsList = (pCity, eUnit, bContinue, bTestVisible)
# Then call it
if cannotTrain(argsList):
pass
But both work essentially the same way. The first is cleaner, but perhaps they had their reasons (like integration with C++) for defining each function as accepting only a list. It also would have been more Pythonic to unpack it like this:
def cannotTrain(argsList):
pCity, eUnit, bContinue, bTestVisible = argsList
# do something
return False
EDIT: It looks like they did do that simplified unpacking in some places. I'm still trying to work out which stuff they put in C++ and which in Python, and why the Python API is the way it is.
Jeckel May 27, 2008, 10:40 PM In python if you define a function like this
def blah(*lArgs):
pass
then whatever arguments you give to the function will be put in that argument list.
For example blah(1, 9, 6) would pass the function a list like [1, 9, 6]. So you can pass 0 to N arguments without changing the function def line, which can be good for things with lots of args or with args that are changing alot.
moopoo May 28, 2008, 01:40 AM Thanks very much for explaining this stuff.
So if I get this right, argument lists are lists defined by the written function to store the variables for that function?
Colin Jun 05, 2008, 05:22 AM @mrkingkong
Thanks for your snippet. I have used it as a basis for my my first succesful foray into python - causing any unit defeated by a zombie to rise up as a zombie itself! Having clear digestible chunks of code here is a godsend, as previously I have found an enormous gulf between basic python tutorials and the vast array of code contained in a typical civ4 python file. This thread helps to bridge that gulf so I just want to express my appreciation to all contributors.
PS Any more FFH functions anybody wants to put up and explain would be most welcome (*Cough*Diseased units*Cough*)
|
|