HOW TO: BTS non-random Events

Fierabras

Emperor
Joined
Dec 26, 2006
Messages
1,120
Introduction:

While playing around with BTS Events, I wondered if it was possible to make non-random timed events. After some experimentation I found that it was indeed possible. In this tutorial I will show an example of how it can be achieved.

Explanation:

As I see it (and correct me if I'm wrong) there are 2 tags in the event trigger that determine the randomness of an event being triggered:

<iPercentGamesActive>
<iWeight>

The first determines the percentage that an event is included in a game and the second sets the change of the event occurring in the game. By setting these to the following, I made them non-random.

Code:
<iPercentGamesActive>100</iPercentGamesActive>
<iWeight>-1</iWeight>

This means the event is always included (100%) and is always triggered (-1), providing all the requirements are met.

By setting the <bRecurring> tag to 1, the event will be triggered more than once.

Code:
<bRecurring>1</bRecurring>

The problem now is, that when the requirements are met, the event will trigger each turn. This is where some Python is needed to add a new requirement that isn't possible with the available XML tags.

Let's say you want the event to occur each 10 turns. In the XML for each event trigger there is a tag named <PythonCanDo>. It's this tag you can use to hook in a Python function. In the example below I used:

Code:
<PythonCanDo>canTriggerZeusSpawn</PythonCanDo>

This function called 'canTriggerZeusSpawn' does not exist yet, so you have to add it to CvRandomEventInterface.py

(create a folder Assets/Python/EntryPoints in your mod and copy the default BTS file CvRandomEventInterface.py to that folder)

The function reads as follows:

Code:
def canTriggerZeusSpawn(argsList):
	kTriggeredData = argsList[0]
	turn = kTriggeredData.iTurn
					
	if (turn % 10 == 0):
		return true
	return false

For the mathematically challenged :) , this uses the modulus operator:

18 % 10 = 8
19 % 10 = 9
20 % 10 = 0
21 % 10 = 1
etc.

So, this function will return true every tenth turn and this return value let's the trigger know the event gets fired.

Example:

Some people might remember the Statue of Zeus from CIV3 that gave free units at certain intervals. With the help of what I have explained above, I have recreated this functionality.

First, set the event trigger's requirement:

Code:
<BuildingsRequired>
	<BuildingClass>BUILDINGCLASS_STATUE_OF_ZEUS</BuildingClass>
</BuildingsRequired>
<iNumBuildings>1</iNumBuildings>

This means the event gets triggered for the civ that owns the Statue of Zeus world wonder (only 1 in the game).

Second, set the event

Code:
<UnitClass>UNITCLASS_AXEMAN</UnitClass>
<iNumFreeUnits>4</iNumFreeUnits>

Pretty self-explanatory, but notice that it uses UNITCLASS and not UNIT, which means that civs who have a UU that replaces the axeman, will get 4 of those UU's. For example: if the Greek own the Statue of Zeus, the event will gift 4 phalanxes ("Madness!? This is Sparta!")

Below is a screenshot of all of this in action:



I have added this example as an attachment.
 

Attachments

Almost anything that is possible in Python, can be 'hooked' through BTS events. Religions are a bit tricky (what happens if you remove a tech requirement from a religion, auto-founding?) and there are no tags in the event-XML for 'gifting' religions, but you can use the <PythonCallback> tag to execute your Python that would found a religion.

Anyway, for this you would use some more Python and maybe to a point that you start to wonder if you still need a BTS event. I have been down that road before...

In the example I used the requirement of a building for the event to trigger, but you can also use the requirement of a unit (let's say...some sort of camp unit ;) ) to let the event trigger.
 
Awesome, honestly, the Events are the only XML files I havent "gone through" to see what they offer.

quick question, if the Building were to become obsolete, in the example Statue of Zeus at Chemistry, would the spawning stop?

@Ekmek, I seem to remember Zebra made an enslavement mod via Python, I am not sure if it works with BTS though. I do remember playing around with it in Warlords though.
 
Yes, you can even make any event go obsolete after discovery of another tech (before the building itself goes obsolete). I suggest you look into Solver's excellent Guide to event modding, to see what all the tags do.
 
Next step could be event trigger at a fixed historical date. Is it doable? Is the game calendar addressable to trigger a specific historical event (instead of # of turns)?
Example:

Triggers:
Year 2000 BC (approx)
Egyptans are in play and alive
No one built Pyramids yet

Effect:
Egyptians build Pyramids (or receive a huge bonus to do that)
 
It's very doable IMO. It will be the topic of my follow-up tutorial, so, stay tuned...

Edit: might as well give some code to show part of it

Code:
def canTriggerPyramidsBuild(argsList):
        kTriggeredData = argsList[0]
	player = gc.getPlayer(kTriggeredData.ePlayer)
	turn = kTriggeredData.iTurn

	if (player.getCivilizationType() == gc.getInfoTypeForString("CIVILIZATION_EGYPT")):

		game = gc.getGame()
						
		if (game.getTurnYear(turn) == -2000):
			return true
		return false

	return false

One important thing to notice is that not all gamespeeds have a turn where the date is exactly 2000 BC, so a better approach would be to check for in-between dates. For the example for the Pyramids this PythonCanDo function needs to be expanded to also check if the Pyramids have been built already.
 
ooh ooh ooh. I'm a budding python coder who's struggling to take his first steps. Someone please help me in this.

In This:

def canTriggerZeusSpawn(argsList):
kTriggeredData = argsList[0]
turn = kTriggeredData.iTurn

if (turn % 10 == 0):
return true
return false

What is argsList, what does it refer to, why is it referred to, and what does it do here?

Also, why 0?
 
argList is arguments list. It contains data about the event. What it does here, is retrieving the game turn the event is triggered at. Why 0? 0 is the first key in any array (not just Python), but it doesn't always have the same value in BTS events. From Solver's guide:

Now, let&#8217;s look at some examples of these Python functions in use. The examples are from events that ship with BtS. First, you&#8217;ll notice that functions begin with retrieving data from the argsList passed to them.
Code:

Code:
def doWeddingFeud2(argsList): 
   iEvent = argsList[0] 
   kTriggeredData = argsList[1]

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.
 
I'm planning on doing a couple of these tutorials on BTS events. I was about to write another when I got side-tracked by a interesting request. For those who are interested in making BTS events more visual with artwork, check out this thread:

http://forums.civfanatics.com/showthread.php?t=267326

Some teaser screenshots:



The next tutorial will be titled: BTS historical events
I touched upon it in this thread, but posted more extensively in another thread, due to a post by my old pal, Ambreville:

http://forums.civfanatics.com/showthread.php?t=269280
 
Good work Fireabras. This is indeed fairly convenient generally, although personally, if I had to do a lot of non-random events, I wouldn't use the random event system but rather script them purely in Python, which is what most mods do for historical events. See Road to War for a very easy to understand (in my opinion) example of doing historical events.
 
Convenient, yes, absolutely, but what about modularity? The plus-side of using BTS events is that you could setup modular events XML to script your historical events.

Let's say I create a generic "Punic Wars" event with modular XML. That way it could be used in several scenarios. Of course copy-pasting parts out of a pure Python mod is easy as well, but dragging and dropping folders from Custom Events might be a bit easier and more practical.
 
Depends on what you prefer. I don't do modules myself. One other possibility that comes to mind is just creating a list of events in XML, along with a pure Python implementation, where XML would be confined to specifying whether each event is enabled or disabled. It would also allow you to tweak at ease.
 
Are we talking about extending PythonCallbackDefines.xml ? If so, we have just taken a leap to the end point of what I assume to be possible.
 
Yes, that. Or you could just add stuff to GlobalDefines.xml and check via Python whether to run your events based on that - the advantage being how easy GlobalDefines is to access via Python.
 
Thanks for these excellent tut's (both on random and non random), I'm right on the verge of "getting it" I think...so far I've managed to make A=B stuff...so I'm confident I can figure it out eventually....

My questions is with changing plots on cue (like a worker job): I'd like the ability to change a plot (specifically land into ocean/coast but I'm unsure of the method of triggering it...short of running 15 workers & and a spy and Great_prophet over the plot to trigger it that way (a little cumbersome)

Also can you insert rivers in anyway (I guess they are a feature?)
and how would you remove a peak, since you can't "get on" the plot the peak is on?
 
Back
Top Bottom