Ball Lightning
www.sporedum.net
Events in Python
Spoiler :
15.3 Python and Events
XML tags allow a good range of possibilities for modding events and their triggers, but some of the more complicated stuff you might want to do requires the use of Python. Python modding, of course, requires a working knowledge of Python, so if you do not have that, feel free to skip this section. This article won’t teach you Python or how to use Python with Civ4 general. The Python file for event functions is Assets\Python\EntryPoints\CvRandomEventInterface.py.
Let us first look at how Python can be used in conjuncton with the events described in Civ4EventInfos.xml. As mentioned, each event entry has four Python function tags. If these are filled, then the aproppriate Python functions are called and used in conjunction with whatever XML data has been specified. Here are, again, the tags.
PythonCallback- that’s the Python function which is called when the event actually happens. It’s used to give the event effects more complicated than are possible through raw XML. Quests are the best example of that, as their rewards are usually somewhat complicated.
Callback functions don’t need to return a value.
PythonExpireCheck- that’s the Python function which is called to see whether the event has expired. It will be called every turn for every event that has occurred. If the event has expired, it gets reset. The practical use of that is for quests. Events that set a quest should sent a Python expiration check function, which will determine the failing conditions of that quest.
Using Python expiration functions will not let you undo regular (non-quest) events. So if you have an event that, say, adds a promotion to all melee units, and add an expiration check to that event, it won’t remove the promotion when the expiration function returns true.
Expiration functions should return a true value for when the event has expired and a false value for when it hasn’t expired.
It’s important to note that expiration functions are only required if the expiration condition can not be described by XML alone. If the only expiration condition you want is, for example, a technology, then you can make the event expire by setting <ObsoleteTechs> in Civ4EventTriggerInfos.xml. That’s the general idea of event Python functions – they complement the XML when it can’t describe what you want.
PythonCanDo- that’s the function which gets called to see if the event can be applied/selected. Again, that is required for non-trivial conditions. If the XML specifies, for example, that the event will subtract 100 gold from the treasury, people won’t be able to choose it unless they have 100 gold. But sometimes you will want to have non-trivial codition checks.
If the event can happen, the Python function should return a true value, and a false value if it can’t happen.
PythonHelp- this function will generate the help text for the event, if required. That text is displayed in mouseovers. This is primarily required for events whose text can change somewhat, such as having a different number depending on the circumstances. For example, if your event gives the player a free unit per every city, then the event’s help text should be modified according to the number of cities the player has. The function should return a text string.
Now, let’s look at some examples of these Python functions in use. The examples are from events that ship with BtS. First, you’ll notice that functions begin with retrieving data from the argsList passed to them.
The first thing you should notice is the argument list that applies to event Python functions. For callback functions (<PythonCallback>), expiration (<PythonExpireCheck>) and help functions (<PythonHelp>) argsList[0] will always contain the event identifier number, and argsList[1] will have an object describing the trigger data. For condition functions (<PythonCanDo>) argsList[0] will be the trigger object data. The type of that object is EventTriggeredData and it contains information about the data that the event was triggered with. You can get the affected player, the other player, the picked city and other things from the object.Code:def doWeddingFeud2(argsList): iEvent = argsList[0] kTriggeredData = argsList[1]
15.4 EventTriggeredData
Here’s what you can do with the object in Python. Assume you have an assignment like
Code:kTriggeredData = argsList[0]
Now, there’s a bunch of useful stuff to do with the object through its member variables.
kTriggeredData.ePlayer- will return the player ID for whom the event was triggered. To get a CyPlayer object out of it in Python,
Code:player = gc.getPlayer(kTriggeredData.ePlayer)
kTriggeredData.eTrigger– will return the trigger type ID of the trigger for this event. That corresponds to an entry in Civ4EventTriggerInfos.xml
will return a trigger info object if you need one.Code:trigger = gc.getEventTriggerInfo(kTriggeredData.eTrigger)
kTriggeredData.iTurn- will return the turn number on which the event was triggered. Useful for expiration checks – you can, for example, easily check how many turns it’s been since the event was triggered.
kTriggeredData.iCityId– will return the ID of the city that’s been picked by the event. Remember that city IDs are per-player and not global. Therefore, to get a CyCity object in Python, you would
Code:player = gc.getPlayer(kTriggeredData.ePlayer) city = player.getCity(kTriggeredData.iCityId)
kTriggeredData.iPlotX– will return the X coordinate of the plot picked by the event.
kTriggeredData.iPlotY– will return the Y coordinate of the plot picked by the event.
If you want to access a CyPlot object for the event’s plot, do so by
Code:plot = gc.getMap().plot(kTriggeredData.iPlotX, kTriggeredData.iPlotY)
kTriggeredData.iUnitId– will return the ID of the unit that’s been picked by the event. Just like city IDs, unit IDs are per-player and not global. So, if you want to get a CyUnit object corresponding to the picked unit, do:
Code:player = gc.getPlayer(kTriggeredData.ePlayer) unit = player.getUnit(kTriggeredData.iUnitId)
kTriggeredData.eOtherPlayer– returns the ID of the other player for events that pick two player. Thus:
will make player the affected player and otherPlayer the other player, ready for manipulation through Python.Code:player = gc.getPlayer(kTriggeredData.ePlayer) otherPlayer = gc.getPlayer(kTriggeredData.eOtherPlayer)
kTriggeredData.iOtherPlayerCityId– will return the ID of the other player’s city that’s been picked by the event. To get a CyCity object:
Code:otherPlayer = gc.getPlayer(kTriggeredData.eOtherPlayer) otherCity = otherPlayer.getCity(kTriggeredData.iOtherPlayerCityId)
kTriggeredData.eReligion– returns the ID of the religion picked by the event.
kTriggeredData.eCorporation– returns the ID of the corporation that has been picked by the event.
kTriggeredData.eBuilding– returns the ID of the building that has been picked. A CyBuildingInfo object can be obtained:
Code:buildingInfo = gc.getBuildingInfo(kTriggeredData.eBuilding)
The use of these properties enables you to access the information you need about the event and the way it’s been triggered. Now, a full example of a Python function that executes an event:
This is a fairly straightforward function. It iterates through all cities of the affected player and gives temporary happiness to them if they have the religion that’s been picked by the event trigger. The effect is not particularly complex, but it is something that could not be accomplished purely through XML, so it’s a good example of why Python is useful in event modding.Code:def doWeddingFeud2(argsList): iEvent = argsList[0] kTriggeredData = argsList[1] player = gc.getPlayer(kTriggeredData.ePlayer) (loopCity, iter) = player.firstCity(false) while(loopCity): if loopCity.isHasReligion(kTriggeredData.eReligion): loopCity.changeHappinessTimer(30) (loopCity, iter) = player.nextCity(iter, false) return 1
Here’s an example of a PythonCanDo function, which checks whether the event can be selected.
It checks whether the affected player has at least 10 gold per each city he owns. If not, the function returns false and the event can’t happen, otherwise it returns true and allows the event.Code:def canDoWeddingFeud3(argsList): iEvent = argsList[0] kTriggeredData = argsList[1] player = gc.getPlayer(kTriggeredData.ePlayer) if player.getGold() - 10 * player.getNumCities() < 0: return false return true
A good example of the help-text creating functions comes with the Horse Whispering quest. One of the possible rewards gives you free Horse Archers. Their number is variable, though, meaning it couldn’t exist in the help text. Let’s look at the help text string in BtS text files:
As you can see, there’s a %d1 in the text, which will be substituted by the number. That’s what the getHelpHorseWhisperingDone1 function does in Python.Code:<Tag>TXT_KEY_EVENT_HORSE_WHISPERING_DONE_HELP_1</Tag> <English>[ICON_BULLET]Receive %d1 [COLOR_UNIT_TEXT]Horse Archers[COLOR_REVERT].</English>
Here, iNumUnits is set to be the default number of players for the current map size and then the number is inserted into the help text string.Code:def getHelpHorseWhisperingDone1(argsList): iEvent = argsList[0] kTriggeredData = argsList[1] map = gc.getMap() iNumUnits = gc.getWorldInfo(map.getWorldSize()).getDefaultPlayers() szHelp = localText.getText("TXT_KEY_EVENT_HORSE_WHISPERING_DONE_HELP_1", (iNumUnits, )) return szHelp
Now, an example of an expiration function. Let’s look at the function which checks whether the Master Blacksmith quest has expired. The quest picks a city and expires (becomes impossible to complete) if the city which was picked changes hands or is destroyed.
Remember, kTriggeredData.ePlayer will always contain the player for whom the event starting the quest was triggered – and kTriggeredData.iCityId will always contain the city ID. The function checks if the city is None (which happens if it’s destroyed) or if the city’s owner isn’t the player to whom the quest was given. In these cases, the function returns true, expiring the event.Code:def expireMasterBlacksmith1(argsList): iEvent = argsList[0] kTriggeredData = argsList[1] player = gc.getPlayer(kTriggeredData.ePlayer) city = player.getCity(kTriggeredData.iCityId) if city == None or city.getOwner() != kTriggeredData.ePlayer: return true return false
Finally, about Python functions that pertain to triggers. Civ4EventTriggers.xml also has Python function tags.
PythonCanDo- acts just like the same tag for events. Checks whether the trigger can be activated.
PythonCanDoCity- checks whether a city is eligible to be picked by the trigger.
PythonCanDoUnit- checks whether a unit is eligible to be picked by the trigger.
PythonCallback- acts just like the same tag for events. Gets called if the trigger is fired.
For PythonCanDoCity functions, argsList[0] is the trigger ID, argsList[1] is the player and argsList[2] is the city ID. For PythonCanDoUnit functions, it’s the same with the exception that argsList[2] is the unit ID. Here’s an example of a PythonCanDoUnit function for the Champion Unit trigger.
After finding the unit, the function checks if it’s damaged or has less than 3 experience – in these cases it returns false. If the unit had the Leadership promotion, it also returns false. If the function returns false, then the unit is not eligible to be picked by the trigger and another one will be found.Code:def canTriggerChampionUnit(argsList): eTrigger = argsList[0] ePlayer = argsList[1] iUnit = argsList[2] player = gc.getPlayer(ePlayer) unit = player.getUnit(iUnit) if unit.isNone(): return false if unit.getDamage() > 0: return false if unit.getExperience() < 3: return false iLeadership = CvUtil.findInfoTypeNum(gc.getPromotionInfo,gc.getNumPromotionInfos(),'PROMOTION_LEADERSHIP') if unit.isHasPromotion(iLeadership): return false return true
That summarizes how events in BtS interact with Python and what the different function types are. Following are examples of some events you can create via modding.