Sample Python Code

adamamri said:
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.
 
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?
 
naf4ever said:
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.
naf4ever said:
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.
 
Code:
# 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.
 
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:

Code:
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.
 
Kael said:
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

Spoiler CvSelectionGroupAI::AI_update() :

Code:
	while (readyToMove())
	{
		pHeadUnit = getHeadUnit();

		if (pHeadUnit == NULL)
		{
			break;
		}

		iTempHack++;
		if (iTempHack > 100)
		{
			FAssert(false);
			pHeadUnit->finishMoves();
			break;
		}

		resetPath();

		pHeadUnit->AI_update(); [b]//Python function called in this function[/b]

		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:

Spoiler CvGameUtils.py, AI_unitUpdate() :

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() [b]# getTargetUnit is a function I added to the selection group class[/b]

                    # 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:

Spoiler CvSelectionGroupAI::AI_update() :

Code:
/*
	 * 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?
 
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?

Aussie_Lurker said:
Well, as many of you here will know, I have had my fair share of problems with the Python language. However I did get THIS one to work:

Code:
def onBeginPlayerTurn(self, argsList):
        'Called at the beginning of a players turn'
        iGameTurn, iPlayer = argsList

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

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

Yours,
Aussie_Lurker.
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.
 
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.

Code:
		if pCity.isHasRealBuilding(gc.getInfoTypeForString('BUILDING_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.
 
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.

Code:
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.
 
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:

Code:
	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())
[B]		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_SWORDSMAN'), deadUnitX, deadUnitY, UnitAITypes.NO_UNITAI)[/B]
		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
 
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.
 
Multi-dimensional array. If there already exists something like this in python (built-in), please, let me know :)

Code:
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:

Code:
# 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.
Code:
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:

Code:
#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()
 
Array wrapper with binary search optimization:

Code:
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.

Code:
# 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)
 
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.
 
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:
 
Here's my simple contribution, similar to an example previously posted by Kael, in CvGameInterface.pv:
Code:
def cannotTrain(argsList):
	pCity = argsList[0]
	eUnit = argsList[1]
	bContinue = argsList[2]
	bTestVisible = argsList[3]
	ePlayer = pCity.getOwner()
	pPlayer = gc.getPlayer(ePlayer)

	if not pPlayer.isHuman() and 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_LINE'):
			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_DESTROYER'):
			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_SUBMARINE'):
			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.
 
Ok, will do. I'll put up my cannotTrain function from the CvGameInterface.py file:

Code:
def cannotTrain(argsList):
	pCity = argsList[0]
	eUnit = argsList[1]
	bContinue = argsList[2]
	bTestVisible = argsList[3]
	ePlayer = pCity.getOwner()
	pPlayer = gc.getPlayer(ePlayer)

	if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_WARRIOR')):
		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_BARRACKS'), False, False, False):
			return True
		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_ARCHERY_RANGE'), False, False, False):
			return True

	if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_SCOUT')):
		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_HUNTING_LODGE'), False, False, False):
			return True
		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_STABLES'), False, False, False):
			return True

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

	if eUnit == gc.getInfoTypeForString('UNIT_PALADIN'):
		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ASHEN_VEIL'):
			return True
		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_OCTOPUS_OVERLORDS'):
			return True

	if eUnit == gc.getInfoTypeForString('UNIT_HIGH_PRIEST'):
		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ASHEN_VEIL'):
			return True

	if eUnit == gc.getInfoTypeForString('UNIT_DEMON_SUMMONER'):
		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ORDER'):
			return True

	if eUnit == gc.getInfoTypeForString('UNIT_ROYAL_GUARD'):
		if pPlayer.getCivics(gc.getInfoTypeForString('CIVICOPTION_GOVERNMENT')) != gc.getInfoTypeForString('CIVIC_HEREDITARY_RULE'):
			return True

	if eUnit == gc.getInfoTypeForString('UNIT_BEAST_OF_AGARES'):
		if pCity.getPopulation() <= 5:
			return True

	return False

What it does: cannotTrain is run through everytime the game gives you the option to build units. If the function returns True then you "cannotTrain" the given eUnit. Returning False means you can train the unit.

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

Use:

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

Code:
	if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_WARRIOR')):
		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_BARRACKS'), False, False, False):
			return True
		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_ARCHERY_RANGE'), False, False, False):
			return True

The next does exactly the same thing for Scouts:

Code:
	if (not pPlayer.isHuman() and eUnit == gc.getInfoTypeForString('UNIT_SCOUT')):
		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_HUNTING_LODGE'), False, False, False):
			return True
		if pPlayer.canConstruct(gc.getInfoTypeForString('BUILDING_STABLES'), False, False, False):
			return True

The following check doesn't allow the player to build Eidolon's if they have either The Order or Runes of Kilmorph as their state religion. Notice that you can already require a religion for a unit in the XML but by using this function you can get much more grnaular with it (in this case allowing the unit to be built by 3 of the 5 religions in the game).

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


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

Code:
	if eUnit == gc.getInfoTypeForString('UNIT_PALADIN'):
		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ASHEN_VEIL'):
			return True
		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_OCTOPUS_OVERLORDS'):
			return True

A single block that keeps civs with the Ashen Veil state religion from building High Priests.

Code:
	if eUnit == gc.getInfoTypeForString('UNIT_HIGH_PRIEST'):
		if pPlayer.getStateReligion() == gc.getInfoTypeForString('RELIGION_THE_ASHEN_VEIL'):
			return True

A single block that keeps civs with the Order state religion from building Demon Summoners.

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

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

Code:
	if eUnit == gc.getInfoTypeForString('UNIT_ROYAL_GUARD'):
		if pPlayer.getCivics(gc.getInfoTypeForString('CIVICOPTION_GOVERNMENT')) != gc.getInfoTypeForString('CIVIC_HEREDITARY_RULE'):
			return True

The last one just requires that the building city be a certain size before the unit is allowed to be built. I do this because when the unit comes into play it kills 4 population in the city.

Code:
	if eUnit == gc.getInfoTypeForString('UNIT_BEAST_OF_AGARES'):
		if pCity.getPopulation() <= 5:
			return True

Thats everything Im blocking on, but by no means is it the limit to which you can block on things. Have fun!

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?
 
Back
Top Bottom