Introducing: PyScenario - easy-to-use scenario event scripting for RFC

The reason I wanted it is because I've given free cities to silly AI civs such as Japan who settle 7 cities on Honshu (for no reason).

... So I wanted a trigger that would only fire if Japan is a human civ, to spawn 1 settler (or however many until we have the same number of settlers as you get in a non-modded game) on the starting plot on turn 1.
Aha! Consider the check() method altered to suit your needs. :goodjob:

Would it be useful to have a "at game start" Condition? This would only be valid on initialization and could be used instead of modding the RiseAndFall module (to add starting units and Techs, to be more exact) for the four early Civs in each scenario. Or to make editing the WBS file superfluous. (Even if it would still be more efficient to edit the WBS directly, but anyways.)

Same for Arabia, same for Vikings, same for China :)
Actually not a bad idea, as an alternative to editing the RiseAndFall module by adding alternative code for human vs AI players.
 
I have some questions :)

- What happens when i want to flip a city but it has not been founded?
- Is this code right?
Code:
Trigger(27).year(-233).once().city(65, 43, "Skodra").valid((65,43),bAdiacent = True)
because now the scenario works only if i delete the "valid()" part
-there was a third but now i can't really remember @_@

oh and would it be possible to have a function that calls a specific "random event"? (I fear not, they aren't indexed anywhere in rhye's code, are they?)

EDIT: for the second question i've found the error i was writing Adiacent instead of Adjacent :p
 
- What happens when i want to flip a city but it has not been founded?
You mean that you define the flipping city by name only (the default way)? Then the code will look for a city with that name (eventually belonging to the player eOwner) - and fail. Nothing happens, even if the Trigger fires and thus expires.

But you can also define the target city by tile coordinate, in what case any city on that spot will flip (unless you also specify the target by name). The most sure way to flip things might be to define a target area (with the target() method) and flip everything inside that area. You can also define alternate city names with the find() method and have the code go through, say, "Rome", "Roma", "Rom" and so on.

- Is this code right?
By enabling exceptions you will know that something is off when you happen to misspell a Trigger. :D

The once() method does nothing for a Trigger that uses the city() Action, by the way. But it doesn't harm either, so you can safely use it any time you are unsure whether it is needed or not.

oh and would it be possible to have a function that calls a specific "random event"? (I fear not, they aren't indexed anywhere in rhye's code, are they?)
I wouldn't know right now, but that would probably involve adding replacement files for the random event module(s). Or something.

What sort of historical event were you considering?
 
I've renamed the flag() Condition triggers() and it can now be used to check whether or not several (actually any number of) Triggers have fired, making this feature somewhat more powerful.
No, scratch that. :crazyeye: The triggers() Condition is once again renamed - to fired() - and can only check one label - but it is of course possible to use several fired() Conditions in one Trigger.

Instead, there is an additional argument that determines whether the labeled Trigger should have fired - or not fired. This makes it possible to create on-off events. (Like one Trigger firing causes another to fire also, but yet another Trigger will stop the second Trigger from firing further. The second Trigger would then have one fired() Condition set to bFired=True and one set to bFired=False.)

If this doens't make any sense it will be covered in the documentation. :D
 
By enabling exceptions you will know that something is off when you happen to misspell a Trigger. :D
very useful thanks :goodjob:

What sort of historical event were you considering?

don't know exactly...something to let the player decide about something like: "We have discovered a method to improve our navy by adding a boarding device to our ships. Should we install it on our fleet?
-Yes! let's call it Corvus ---->gives a promotion to all triremes
-No we should care more about the training of new legionaries ----> you receive 3 legionaries

or something like this :hide:
(ummm but maybe something more tied to a specific year would be better)

and could there be a function that adds an amount of gold to the selected civ? (roman empire goes bankrupt very quickly :cringe:)

last question (for now) i promise :p
I tried the stability() function but it keeps giving me errors...is it possible? to subtract stability i suppose that i should insert a negative number am i right?
 
don't know exactly...something to let the player decide about something like: "We have discovered a method to improve our navy by adding a boarding device to our ships. Should we install it on our fleet?
-Yes! let's call it Corvus ---->gives a promotion to all triremes
-No we should care more about the training of new legionaries ----> you receive 3 legionaries
Ah, to create your own random events... Unfortunately Firaxis opted to make these a combination of C++, Python and XML so they aren't very accessible for most modders.

I don't think there will ever be support for anything like this with PyScenario, but perhaps in another application altogether? :king:

and could there be a function that adds an amount of gold to the selected civ? (roman empire goes bankrupt very quickly :cringe:)
There will be a gold() method in the next update, which I'm documenting right now. :goodjob:

I tried the stability() function but it keeps giving me errors...is it possible?
Could you please post any errors or exceptions in the beta-testing thread? See the first post for more details on bug reporting.

With any luck you've managed to find a bug and I think I need to postpone the new version until I have been able to look into it... :p Your cooperation is appreciated. :goodjob:

to subtract stability i suppose that i should insert a negative number am i right?
Yes, this is a correct assumption. (This will also apply to the gold() Action.) Maybe this needs to be documented better?
 
All the new additions discussed since the last update are now live! Download the latest version from the beta-testing thread. The documentation is also updated, so make sure to revisit the first few posts in this tread.

The most useful addition might be the new Trigger binding feature (see Tutorial for the specifics) but I believe that the degrade() method is still the coolest new feature. :king: I did some testing with a desertification script and the results were pretty awesome! :D I'll try to cook up a little PyScenario powered mod-mod this weekend to showcase how it can be used. :goodjob:

Other than that, next up is a couple of things I've been putting off since the inception of this project... Namely the automatic promotions and the in-game messengering features. These both require some work so I'll better get to it while I still have some free time to spare... :p
 
I had another idea for a PyScenario add-on: What if all the starting conditions for spawning Civs were to be scripted with PyScenario? There would of course have to be a modified version of the RiseAndFall module (with all the unit and Tech lines commented out) and the actual PyScenario script might be somewhat large (but would still contain less lines than the current code). How does that sound?

Because then it would be easy to edit, say, how many Settlers a given Civ spawns with. (I actually think there could be a special spawn() Condition...) Or to add/remove a starting Tech to any Civ.

I'd have to do some experimenting and also time the execution time (both on spawn and how much lag this would add to the mod in general, as there would be more Triggers to be checked). But there might also be a way to only replace the spawn code for one or a select few Civs, leaving the rest of Rhye's code to be activated as is. This would minimize the share number of Triggers added to the Scenario module by default.

Maybe this could be merged with the proposed Rhye's Spawns add-on?
 
In the "flip" trigger, is there a way so that the flip is actually a conquest? (i.e: city conquered rather than peacefully change hands)
What you could try out, with the latest version, is to put the treaty() Action (with bPeace=False) before the flip() Actions and have the flip be a hostile one. This will kill all the hostile units in the flipping city/area - and should also have an effect on Stability (as if the cities were conquered).

Please report back how it worked out. :goodjob: (Otherwise you could always resort to using the stability() Action to get the result you wanted.)
 
:D I'll try to cook up a little PyScenario powered mod-mod this weekend to showcase how it can be used.
And I did. :king:

It took about a workday to research, design, script, test and tweak. All that is needed now is some actual play test. :p It was actually the first time I used PyScenario myself for real, so it was fun! :goodjob:

Once I've completed all the features I plan on adding to the beta-version I can look forward to some scenario making of my own. Because now it will be easy to whip up almost anything. (And the good thing is - if I miss some feature I can always add it.)
 
Ah, to create your own random events... Unfortunately Firaxis opted to make these a combination of C++, Python and XML so they aren't very accessible for most modders.

not really, I intended a way to "call" that event in a specific year with a function

All the new additions discussed since the last update are now live! Download the latest version from the beta-testing thread. The documentation is also updated, so make sure to revisit the first few posts in this tread.
:goodjob:

I tryed the startup() function but i don't know if i'm using it correctly...I wanted rome to not start at war with greece and carthage but something doesn't work (can't figure out if this sentence is correct, google translator doesn't help :sad:)
Code:
Trigger(7).startup().treaty(4)
Trigger(7).startup().treaty(6)

EDIT: i wanted to found christianity in jerusalem but with religions() i can only spread it...is there another way?
 
not really, I intended a way to "call" that event in a specific year with a function
Aha, I don't think that's possible. But I can't rule it out either, as I know next to nothing about stuff involving C++. :p

I tryed the startup() function but i don't know if i'm using it correctly...I wanted rome to not start at war with greece and carthage but something doesn't work (can't figure out if this sentence is correct, google translator doesn't help :sad:)
I believe it doesn't work because the startup() method will only work on initialization. Since Rome and Greece haven't spawned yet they are dead/inactive. And whatever you manage to set their diplomatic status to before they spawn, Rhye's code in the RiseAndFall module will change it once Rome spawns.

What you need to do is edit the Consts module instead. Look for the list lEnemyCivsOnSpawn and edit the Roman entry:
Code:
[iEgypt,iGreece,iGreece,iCarthage,iCarthage], #Rome
If you take away all instances of iGreece then they should never start at war - and if you add more of them, then the likelihood for war will increase even more.

But if you wanna keep you scenario/mod contained to your Scenario module, then you have to change their diplomatic status after Rome has spawned (on turn 90). It shouldn't affect play but can be a bit confusing.

DIT: i wanted to found christianity in jerusalem but with religions() i can only spread it...is there another way?
I believe you need to hack into the Religions module. :p This is something I haven't tried myself, but I know others have. So its quite doable and if you need help, just holler. :king:
 
Ok, done! :king: All features that were planned from the conception of this project are now officially in, along with a ton of stuff that "got in the way" during the process. :D Download the last "big" update from the beta-testing thread. (See the change log for all details.)

The last two major hurdles were the in-game messaging feature that I was actually planning to do well before I ever started work on PyScenario - and the fabled automatic promotions feature. The latter is a real vanity project of mine as I doubt anyone will even wanna use it. :lol: It involves a complicated script (see the last function in PyScenario.py) that will pre-set promotions to units depending on unit level, unit type and the technological level of the owner. I knew this was gonna be somewhat work intensive, so I saved it for last. :p

Now I will focus on testing, making improvements and correcting bugs. Any request for Trigger methods will of course be considered. I have some pretty nifty mod-scenario ideas of my own... :king:
 
awesome work! :D
i've tried the message() and popup() functions...very well done! ;)

now i wanted meplum to spawn a warrior and then a gallic warrior every turn while it's not under roman control so i wrote this
Code:
Trigger(30).year(-760,0).fired("RomeisPlayer").label("MeplumisGaul").owned((59,47))
Trigger(30).year(-760,-400).fired("RomeisPlayer").fired("MeplumisGaul").units(59,47,24,1).valid((59,47),bDomestic=True)
Trigger(30).year(-400,0).fired("RomeisPlayer").fired("MeplumisGaul").units(59,47,28,1).valid((59,47),bDomestic=True)
but it doesn't work...i don't get pyton errors so i think that i'm not using the right functions?

and could there be something like a random(iprobability) function that fires only with a certain probability? for example it could be used in my code to not spawn a gallic warrior every single turn...

and maybe a function that counts how many units of a certain type there are in a civilization? (if there are at all)
 
now i wanted meplum to spawn a warrior and then a gallic warrior every turn while it's not under roman control so i wrote this
Code:
Trigger(30).year(-760,0).fired("RomeisPlayer").label("MeplumisGaul").owned((59,47))
Trigger(30).year(-760,-400).fired("RomeisPlayer").fired("MeplumisGaul").units(59,47,24,1).[COLOR="Red"]valid((59,47),bDomestic=True)[/COLOR]
Trigger(30).year(-400,0).fired("RomeisPlayer").fired("MeplumisGaul").units(59,47,28,1).[COLOR="Red"]valid((59,47)[/COLOR],bDomestic=True)
This is actually my doing... While I recommend always using a valid() Condition for spawning, it actually prevents units from spawning in cities. (As a city tile is always invalid.) So if you use valid() and wanna spawn in city tiles, use the garrison() Action instead of units(). This is documented in the API.

Also, its not entirely necessary to use the tCoords argument with valid() if the target plot is defined elsewhere in the Trigger. Like you've done with the units() Action.

Note however that once the Trigger "MeplumisGaul" has fired the first time, then the fired() Condition will always be cleared for the following Triggers. So there is only a "on" switch but no "off". (Although you could add one... But then these Triggers would never fire again once that Condition has passed.)

You could just add the owned() method to both of the spawning Triggers.

Also note that both spawning Triggers will fire on the year 400 BC. Also, I don't believe there is a thing as year zero. Try defining the second as year(-401,1).

and could there be something like a random(iprobability) function that fires only with a certain probability? for example it could be used in my code to not spawn a gallic warrior every single turn...
If you substitute the year() Condition for turn() you get this functionality built-in. See the API - holler if you can't figure it out. (Its actually an advanced feature and could potentially be confusing.) Year waypoints for different game turns can be found in the Consts module and are used in Rhye's code as con.i400BC (or you could just use the integer value directly, like 114).
Spoiler :
Code:
# date waypoints

i3000BC = 0
i2250BC = 25
i2085BC = 31
i2000BC = 34
i1800BC = 42
i1600BC = 50
i1000BC = 74
i850BC = 84
i700BC = 94
i650BC = 97
i600BC = 101
i483BC = 108
i479BC = 109
[COLOR="Red"]i400BC = 114[/COLOR] #new timeline
i350BC = 117
i250BC = 124 #new timeline
i210BC = 127
i110BC = 133
i100BC = 134 #new timeline
i10BC = 140
i33AD = 143
i50AD = 144
i140AD = 150 #new timeline
i170AD = 152
i190AD = 153
i200AD = 154 #new timeline
i250AD = 157
i300AD = 161
i350AD = 164
i450AD = 171
i470AD = 172 #new timeline
i476AD = 172
i500AD = 174 #new timeline
i550AD = 177
i600AD = 181
i622AD = 183
i690AD = 190
i700AD = 191
i860AD = 207
i900AD = 211
i920AD = 213
i1000AD = 221
i1100AD = 231
i1140AD = 235
i1190AD = 240
i1200AD = 241
i1250AD = 246
i1300AD = 251
i1350AD = 256
i1400AD = 261
i1450AD = 271
i1500AD = 281
i1600AD = 301
i1607AD = 302
i1650AD = 311
i1700AD = 321
i1715AD = 326
i1730AD = 331
i1745AD = 336
i1760AD = 341
i1775AD = 346
i1800AD = 355
i1820AD = 361
i1850AD = 372
i1860AD = 377
i1870AD = 382
i1880AD = 387
i1900AD = 397
i1910AD = 402
i1918AD = 406
i1930AD = 412
i1940AD = 420
i1950AD = 430
i2000AD = 480
and maybe a function that counts how many units of a certain type there are in a civilization? (if there are at all)
You mean like a army() or horde() or whatever Condition that takes eType and iNum as arguments? If the iNum value is equal or greater than the number of eType units for the target player, then the Condition has passed.

Could you specify a use for this methods?
 
You could just add the owned() method to both of the spawning Triggers.
you are right XD
You mean like a army() or horde() or whatever Condition that takes eType and iNum as arguments? If the iNum value is equal or greater than the number of eType units for the target player, then the Condition has passed.

Could you specify a use for this methods?
yeah like an "army(eType=None, iNum=1,tCoords=None)" function that fires if the number of specified units is equal or greater than the number of eType units. If eType is None then it will control if any unit is on that plot and if the coords are left to None then it will search in all the specified civ...

i would use it in my quests so you could be asked to build 3 galleys or to bring 3 galleys in a plot...but i don't know if it's possible @_@
 
yeah like an "army(eType=None, iNum=1,tCoords=None)" function that fires if the number of specified units is equal or greater than the number of eType units. If eType is None then it will control if any unit is on that plot and if the coords are left to None then it will search in all the specified civ...

i would use it in my quests so you could be asked to build 3 galleys or to bring 3 galleys in a plot...but i don't know if it's possible @_@
Ok, consider it done. :goodjob: I might get back to you for more on your requirements though, but I'll do this shortly. It could be like a count() method - for counting units by number and/or type and/or tile and/or Civ. I actually think its a good idea, so thanks for the idea. :king:

Almost anything is possible. :D
Spoiler :
Just say the word if you need, say, the Greeks to respawn as the Byzantine. Because I should rework the respawn code in RiseAndFall anyway, so that it can be useful for modding/scripting. (You could probably change the dynamic name for medieval Greece to Byzantine Empire in the XML, by the way. It would also be possible to make Justianius a Greek leader by editing the Consts module, although I haven't tried this myself...) Then you'd just have to script a Greek collapse before Byzantium "spawns".
 
Continued from the beta-testing thread:
A migrate() Action, or something like it, would actually be helpful. It could be used to add or subtract population in a city.
What about having ethnicity as a optional setting?

Like when adding one population point to a Chinese city, that population could be Japanese. Then the cultural makeup of that city would change to reflect that population segment. Or when the Action reduces population and the people leaving could be set to, say, Japanese. This would also change the Japanese cultural influence in the city.

But we're talking about ethnic cleansing now, right? Is it really such a good idea? :p

Another idea could be to transfer citizens between cities. Like when you add a point in city A, you could automatically take away a point in city B. I don't know how to implement this yet, though.

Any thoughts on this?
 
Ok, this is the new deal: New methods (like requested ones) will be posted as add-ons before they appear in the next PyScenario version. This involves a Custom module being downloaded and installed alongside the other PyScenario modules.

The first Custom module (download link below) contains Bonci's proposed unit counter method, dubbed the heads() Action:

heads( iNum=1, eType=None, tCoords=None )
Read Bonci's description for usage. :king:

All you have to do, except install the module, is to add these lines into you Script module (preferably at the beginning):
Code:
from Custom import *
Trigger.heads = heads
This will add the new method to PyScenario - modularly. You use it just like any other method in your script and the application won't be able to tell the difference.

If you have several Custom modules you can either rename them whatever you want and add a similar set of code for each in the Scenario module - or you can simply copy-paste the code from one Custom module into the other. (Just make sure all import and assignment statements are included at the beginning.) It would also be possible to make you own methods and add them in a Custom module.
 

Attachments

  • Custom_heads.zip
    902 bytes · Views: 143
I am (player) the receiver and (one) city gets auto-razed. Still, I don't know when in-game city gets auto-razed on conquer (when it never reached 2 pop? Is it why Melpum starts with 2 pop and starve to 1 at next turn?).
Yeah, I think so... What cities are you trying to flip, by the way? Did you spawn them yourself originally? In that case you can actually set the city size with the city() Action. (Refer to the API.)

Anyway, migration() would solve the rpoblem (all cities in the area gets +1pop-> flip(area)).
It could, but Civs might still opt to raze whatever the size. But I could probably have a look to see if it can't be prevented with an additional setting, somehow...

What other functionality would be in a migrate() method? Look at my post above for some ideas...
 
Top Bottom