Python Snippets

Don't change the files in Assets. It makes recovering from errors more difficult. Instead, create a copy of the file you want to change under

Code:
C:\Documentss and Settings\
  MyDocuments\
    My Games\
      Beyond the Sword\
        CustomAssets\

Put it in the same folder structure as it was in Assets. Any files in CustomAssets override those in Assets as long as you're not playing a mod. This is covered in more depth in one of the stickied basic's of modding threads.

Regarding your code, it looks like you have the incorrect indentation under the last "if". In Python, whitespace indentation is important. I see a space where you should have a tab. Below, "^" should be a tab.

Code:
if gc.getPlayer(ePlayer).getStateReligion() != gc.getInfoTypeForString(religion):
^return True

When the game cannot understand your Python, oftne the result is a blank interface (just a map, no buttons). You can enable logging (see the CivIV.ini file in "My Games\BtS") to have the problems in your code written to a file, PythonErr.log, in that same folder's "logs" folder.
 
Wow, I'm so used to C++/Java that I didn't know that python considered whitespace important. Thank you, I think I will go read some stuff on python and whitespace now.
 
Well, I fixed the earlier problem, but now a new one has arisen. The code doesn't do anything. I have it written exactly as it should, and I don't get any interface errors.

Code:
def cannotDoCivic(self,argsList):
		ePlayer = argsList[0]
		eCivic = argsList[1]

		if eCivic == gc.getInfoTypeForString("CIVIC_HEREDITARY_RULE"):							# The Civic That We Are Working With
			if gc.getPlayer(ePlayer).getStateReligion() !=   gc.getInfoTypeForString("RELIGION_JUDAISM"):		# The Religion That Is Required For The Civic
				return True

		return False

The whitespace is correct, but I can still switch to hereditary rule even if I am not jewish. What is wrong here?
 
Try outputting some debug info as you go using addImmediateMessage(message, sound). The second parameter I just leave blank (not sure if None works).

Code:
def debug(msg):
	"Print a debug message. Define this near the top (anywhere really) outside of any class definitions."
	CyInterface().addImmediateMessage(msg, "")

...

def cannotDoCivic(self,argsList):
	ePlayer = argsList[0]
	eCivic = argsList[1]

	debug("player = %d" % ePlayer)
	debug("civic = %d" % eCivic)

	if eCivic == gc.getInfoTypeForString("CIVIC_HEREDITARY_RULE"):
		debug("Hereditary Rule")
		if gc.getPlayer(ePlayer).getStateReligion() != gc.getInfoTypeForString("RELIGION_JUDAISM"):
			debug("Not Judaism")
			return True

	debug("Enjoy your new civic!")
	return False

The messages will display along with other game messages but do not show up in the message log (alt-tab). They appear for a few seconds and then vanish.

But looking at the C++ source,

Code:
	if([B]GC.getUSE_CANNOT_DO_CIVIC_CALLBACK()[/B])
	{
		CyArgsList argsList2; // XXX
		argsList2.add(getID());
		argsList2.add(eCivic);
		long lResult=0;
		gDLL->getPythonIFace()->callFunction(PYGameModule, "cannotDoCivic", argsList2.makeFunctionArgs(), &lResult);
		if (lResult == 1)
		{
			return false;
		}
	}

I realize why it doesn't work. With BtS Firaxis added flags to tell which Python callbacks like this should be called. They are in PythonCallbackDefines.xml:

Code:
	<Define>
		<DefineName>USE_CANNOT_DO_CIVIC_CALLBACK</DefineName>
		<iDefineIntVal>0</iDefineIntVal>
	</Define>

Change the 0 to a 1 and see if that works. Again, you should be modifying copies of these files in CustomAssets. If you aren't going to follow that advice, at least back up your Assets folder so you won't have to reinstall to fix a broken game. ;)
 
This is for a mod, so the assets folder is backed up in the normal BtS folder. Thank you for the advice.
 
It does, thank you very much. You may want to update this code and/or the instructions so it works with BtS. Specifically relating to above stated file.

Thanks again people.
 
Hey Zebra,

just want to say thanks for these snippets, they are great :D

but, a quick question,
with the leader specific units, how do i make it so that each and every leader has its own unit? im new to python, so please excuse me if this has a blatantly obvious answer :p
 
About the Thumbtack system. Alt-C is a pretty bad shortcut to use since Alt selects all units on the current tile, so if any units have moves left you will select the current stack instead of turning off (or on) the unit cycling.
 
Well if you want to do that you just need to duplicate the code as many times as you want. Example:
Code:
        leader = Leader Type        ## The Leader
        unit = Unit Type        ## The Unit

        ## If The Civ Doesn't Have The Correct Leader Tell the SDK Not To Allow The Player To Construct This Building
        if eUnit == gc.getInfoTypeForString(unit):
            leaderType = gc.getLeaderHeadInfo(gc.getPlayer(pCity.getOwner()).getLeaderType()).getType()
            if leaderType != gc.getInfoTypeForString(leader):
                return True

        leader = Leader Type        ## The Leader
        unit = Unit Type        ## The Unit

        ## If The Civ Doesn't Have The Correct Leader Tell the SDK Not To Allow The Player To Construct This Building
        if eUnit == gc.getInfoTypeForString(unit):
            leaderType = gc.getLeaderHeadInfo(gc.getPlayer(pCity.getOwner()).getLeaderType()).getType()
            if leaderType != gc.getInfoTypeForString(leader):
                return True


If you think of a better key combo let me know and I'll update the code.
To do it your self just change the bold code:
Code:
## This line checks to see if the ALT and C keys where pressed
			if theKey == InputTypes.KB_[font=arial][size=4][B]C[/B][/size][/font] and self.bAlt:
				## This turns the Unit Cycling on or off
				if gc.getActivePlayer().isOption(PlayerOptionTypes.PLAYEROPTION_NO_UNIT_CYCLING):
					gc.getActivePlayer().setOption(PlayerOptionTypes.PLAYEROPTION_NO_UNIT_CYCLING, False)
				else:
					gc.getActivePlayer().setOption(PlayerOptionTypes.PLAYEROPTION_NO_UNIT_CYCLING, True)
:thumbsup:
 
In making scenarios I've found a few Python snippets that are very useful. Most are reused from stock Civ 4 mods, like Desert War, but since this is a great reference thread they are worth repeating here. :D

One such snippet:

PERMANENT WAR OR PEACE

What it does: sets every nation to permanent war or peace. This is useful because you can specify in Python exactly when or where you want this to kick in, as opposed to the main menu option, which would run all the time.
Code:
for iPlayerLoop1 in range(gc.getMAX_PLAYERS()):
	if(gc.getPlayer(iPlayerLoop1).isAlive()):
		for iPlayerLoop2 in range(gc.getMAX_PLAYERS()):
			if(gc.getPlayer(iPlayerLoop2).isAlive()):
				gc.getTeam(gc.getPlayer(iPlayerLoop1).getTeam()).setPermanentWarPeace(iPlayerLoop2, true)

You might set this to run on the first game turn, or on a specific turn when you don't want civs prematurely ending a war that's already started. It all depends on however you want to trigger it.
 
That looks familiar. ;)

BTW, a version that allows turning it on or off:

Code:
def resetWarPeace(self, bValue):
	for iPlayerLoop1 in range(gc.getMAX_PLAYERS()):
		if(gc.getPlayer(iPlayerLoop1).isAlive()):
			for iPlayerLoop2 in range(gc.getMAX_PLAYERS()):
				if(gc.getPlayer(iPlayerLoop2).isAlive()):
					gc.getTeam(gc.getPlayer(iPlayerLoop1).getTeam()).setPermanentWarPeace(iPlayerLoop2, bValue)

Then all you need to call is resetWarPeace(true) or resetWarPeace(false). :)
 
Thanks guys, I try to keep adding more stuff but its not that easy as one person. :thumbsup:

I'm going to put these on the front page of the thread. :)
 
Hi, Zebra,

I have a question about a bit of the Desert War code, to which I'm so indebted for my crash-course in Python last May. :D

I noticed it uses the global script to store and retrieve truth values for whether a specific event occurred, such as:

Spoiler :

Code:
	def setupScriptData( self ):
		"""Initialise the global script data dictionary for usage."""
		scriptDict =	{	'bKasserinePass': False,
					'bMarethLine': False,
					'bOperationBarbarossa': False,
					'bMiddleEast': False,
					'bStalingrad': False,
					'bParisLiberated': False,
					'bEnglandFallen': False,
					'bCapTimerOn': False,
					'bEndCapTimer': False,
					'bVictoryNextTurn': False,
					'bVictoryAchieved': False,
					'iExtraUnitCost': 0,
					'iParisLiberation': -1,
					'iEnglandFalls': -1,
					'iCapTimer': 0,
					'iVictoryTimer': 9,
					'tCapturedObjectivesList': []
				}
		gc.getPlayer(0).setScriptData( pickle.dumps(scriptDict) )

	def isOperationBarbarossa( self ):
		"""Returns if the Operation Barbarossa event already took place."""
		scriptDict = pickle.loads( gc.getPlayer(0).getScriptData() )
		return scriptDict['bOperationBarbarossa']

	def setOperationBarbarossa( self, bNewValue ):
		"""Sets that the Operation Barbarossa event took place."""
		scriptDict = pickle.loads( gc.getPlayer(0).getScriptData() )
		scriptDict['bOperationBarbarossa'] = bNewValue
		gc.getPlayer(0).setScriptData( pickle.dumps(scriptDict) )

	def getExtraUnitCost( self ):
		"""Returns the Extra Unit Cost that is the result of Operation Barbarossa."""
		scriptDict = pickle.loads( gc.getPlayer(0).getScriptData() )
		return scriptDict['iExtraUnitCost']

	def setExtraUnitCost( self, iNewValue ):
		"""Sets the Extra Unit Cost that is the result of Operation Barbarossa."""
		scriptDict = pickle.loads( gc.getPlayer(0).getScriptData() )
		scriptDict['iExtraUnitCost'] = iNewValue
		gc.getPlayer(0).setScriptData( pickle.dumps(scriptDict) )


................. [and many more functions such as these] ...................


Now, it seems at first glance that it'd be just as easy to accomplish the same thing by having dummy variables which get set to 1 or 0, depending on whether the event happened, instead of all those helper functions. So why is the script used instead here? It is because the script is saved with the game, yes? (As opposed to variables, which would get reinitialized upon reload.)
 
As one who worked with Locutus on these functions, I can confidently say, "Yes". :)

Hi, Zebra,

I have a question about a bit of the Desert War code, to which I'm so indebted for my crash-course in Python last May. :D

I noticed it uses the global script to store and retrieve truth values for whether a specific event occurred, such as:

Spoiler :

Code:
	def setupScriptData( self ):
		"""Initialise the global script data dictionary for usage."""
		scriptDict =	{	'bKasserinePass': False,
					'bMarethLine': False,
					'bOperationBarbarossa': False,
					'bMiddleEast': False,
					'bStalingrad': False,
					'bParisLiberated': False,
					'bEnglandFallen': False,
					'bCapTimerOn': False,
					'bEndCapTimer': False,
					'bVictoryNextTurn': False,
					'bVictoryAchieved': False,
					'iExtraUnitCost': 0,
					'iParisLiberation': -1,
					'iEnglandFalls': -1,
					'iCapTimer': 0,
					'iVictoryTimer': 9,
					'tCapturedObjectivesList': []
				}
		gc.getPlayer(0).setScriptData( pickle.dumps(scriptDict) )

	def isOperationBarbarossa( self ):
		"""Returns if the Operation Barbarossa event already took place."""
		scriptDict = pickle.loads( gc.getPlayer(0).getScriptData() )
		return scriptDict['bOperationBarbarossa']

	def setOperationBarbarossa( self, bNewValue ):
		"""Sets that the Operation Barbarossa event took place."""
		scriptDict = pickle.loads( gc.getPlayer(0).getScriptData() )
		scriptDict['bOperationBarbarossa'] = bNewValue
		gc.getPlayer(0).setScriptData( pickle.dumps(scriptDict) )

	def getExtraUnitCost( self ):
		"""Returns the Extra Unit Cost that is the result of Operation Barbarossa."""
		scriptDict = pickle.loads( gc.getPlayer(0).getScriptData() )
		return scriptDict['iExtraUnitCost']

	def setExtraUnitCost( self, iNewValue ):
		"""Sets the Extra Unit Cost that is the result of Operation Barbarossa."""
		scriptDict = pickle.loads( gc.getPlayer(0).getScriptData() )
		scriptDict['iExtraUnitCost'] = iNewValue
		gc.getPlayer(0).setScriptData( pickle.dumps(scriptDict) )


................. [and many more functions such as these] ...................


Now, it seems at first glance that it'd be just as easy to accomplish the same thing by having dummy variables which get set to 1 or 0, depending on whether the event happened, instead of all those helper functions. So why is the script used instead here? It is because the script is saved with the game, yes? (As opposed to variables, which would get reinitialized upon reload.)
 
lol in all truth, back then when those functions were written I didn't know a lot of python.

I asked him for something to track the events we were implementing, he wrote it, and I looked at it and said, "Yeah whatever, as long as it works". :lol:

It's a different story now though. ;)
 
I just wish the script data was multiplayer compat (or I knew a way to make MP). When I first got civ I didn't know what python was (other then a snake), all I knew was JavaScript and HTML.
 
Well, dang. I had hoped to make my scenarios using scripts multiplayer compatible. That's not good. :(
 
Top Bottom