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

Or, my take on scripting the fall of the Roman Empire would be to use the stability() method to give them stability penalties until the built-in secessions and finally a collapse takes care of it. But it is of course possible to mod how this is implemented in the mod, but that would involve editing the Stability and/or RiseAndFall modules.

But I realize of course the use for a stability check Condition. Right now I'm pondering how to best implement it though. (It would be a breeze to code something like that in Python. But making it a general PyScenario Condition requires some afterthought.)

Maybe have two methods for this? Like stable(iStability) and unstable(iStability) - the first one checks if Stability is equal or more than iStabilty, and the second one checks if Stability is less than iStability. Or there could just be a boolean argument as you suggested. (I think I included something like this in the found() Condition.)

Perhaps two booleans instead of the suggested one? bGreaterThan and bEqualTo - the first one will determine whether or not Stability needs to be more or less than iStability, and the second one will also accept the actual iStability value - regardless of the the previous boolean setting.

But all of this strikes me as very awkward, when all that really is needed is this code:
Code:
if utils.getStability(ePlayer) == iStability:
        ...
All that really needs to be done is change the == (equal to) to < (less than), <= (less than or equal to), > (greater than) or >= (equal to or greater than) to get whatever Condition you need. I'm almost tempted to make the user enter one of these comparison operators in the method (as a string). :lol: Or make my own custom comparison type with these five settings plus != (not equal to). Then the user would either enter a numerical value between 0 and 4 - or use the type as a string: "ComparisonType_EqualTo" :p
 
I want to connect my fall of the Empire with RFC system: if Rome has -10 or less stab => some barbarians spawn (and the situation repeats itself ~4 times). Then, if Rome has -10 or less stab and Greece is under Roman control => Eastern Empire secedes.
Every check method is convenient for me so it's up to you which one you choose.

It's hard to come back to custom methods after more than 2 weeks :) As far as I tested back then everything was OK but I didn't conduct real "tests" (just played for fun). Anyway, the more people test the bigger chance to find bugs (if there are any).

About tiles() I thought about some method to make "child" triggers to yield, etc. (like XP() is a child of units() ).

Anyway, I'm still to get back to modding (or cIV) after the holidays because great additions in Maple (MMORPG) makes me to catch up on there :)
 
I want to connect my fall of the Empire with RFC system: if Rome has -10 or less stab => some barbarians spawn (and the situation repeats itself ~4 times). Then, if Rome has -10 or less stab and Greece is under Roman control => Eastern Empire secedes.
Every check method is convenient for me so it's up to you which one you choose.
I might end up doing a fallOfRome() Condition for you if all else fails. It would check if stabilty is -10 or less and pass. :D

It's hard to come back to custom methods after more than 2 weeks :) As far as I tested back then everything was OK but I didn't conduct real "tests" (just played for fun). Anyway, the more people test the bigger chance to find bugs (if there are any).
I consider playing the "real" test of any mod or scenario. I only have time and energy to check whether or not everything fires as it should, but don't actually know what would happen in a real game situation.

About tiles() I thought about some method to make "child" triggers to yield, etc. (like XP() is a child of units() ).
No, map tiles and areas can always be defined with target(). The tiles() method is just another way of doing it, but it also allows for non-rectangular areas.

So there is no need to have any add-on methods for this. Or you could just use target() for that - if you really wanna do it that way:
Code:
Trigger().yields(2).target(45,54)
You should probably revisit section 2.3 in the documentation for more on this subject.
Spoiler :
2.3 Defining a target tile

The governing principle of PyScenario is that every Trigger is used with one Player and is aimed at one single map tile. There are options for defining multiple tiles though, and such a "plot list" will then override any single tile setting. But this would really be a special case and different types of Triggers will react differently to a plot list.

There are several special methods that can be used to define one or more tiles. They all take an arbitrary number of arguments, depending on what you want to achieve.

target( <arguments> )
Given a pair of integer values these numbers will correspond to the X and Y map coordinates respectively. But a single tuple argument containing two values is also valid, so you can use whatever option feels most comfortable.

The method can also be used for defining a rectangular map area. Then you'd have to supply two sets of coordinates - the first one is always the lower left tile and the second one is the upper right tile making up the rectangle. Then all the tiles within this area are added to the plot list.

Two sets of coordinates can either be entered as a pair of tuples - or as four separate integer values. So these are all valid examples of use:
Code:
target(34,56)
target((34,56))
target((33,55),(35,57))
target(33,55,35,57)
It could be noted however that the target() method isn't entirely necessary for defining single map tiles, as many other methods can take map coordinates as arguments. But its always a viable option if you feel more comfortable with using it.

tiles( <tuples> )
This method is used to define a plot list consisting of any specified map tiles. Each entry has to be a tuple consisting of a set of coordinate values, as described above.

find( <strings> )
This method is a special option as it can be used to search for cities by name on a turn to turn basis. It can take any number of strings as arguments - as long as each string is enclosed in quotations and separated from the next one by a comma.

Note that only one city and one tile will be assigned as the target for the entire Trigger. So with multiple city name arguments the first one that is found when the Trigger is checked will be assigned. Also note that such a city target doesn't overwrite a target tile defined elsewhere!

This method should only be used with some afterthought as the exact setup of cities will vary from game session to game session.
I'm looking forward to you getting back into scripting/testing. :goodjob:
 
I've got an exception with ".output(0,2)" and my palace ended producing 2 culture instead of food.
First update PyScenario, then follow the instructions for reporting bugs found in the beta testing thread.

edit: I need to know what the exception is as it will tell me what is wrong - and the traceback will enable me to figure out where it was created. I also need your script (or at least the complete Trigger) in order to be able to recreate the exception on my machine. And finally we need to have the same version of PyScenario and use the same Custom modules. (I'm using the latest version - what version are you currently using?) A savegame from the previous turn might also be helpful.

If I define area and then create units somewhere else units appear in defined area not at X and Y defined in units().
This is a feature, but I agree that it is confusing. It is documented though, but I might change it in the future.

Also note that the latest version has a new method for spawning units with plot lists: spawn()
 
Ok, here's what I came up with:

solidity( iStabilit )
The Condition checks whether the Stability rating of the target player is less than iStability.

operator( eOperator )
This is a add-on method to solidity() - and potentially other methods like it - and is used to change the default operator (which is "less than" for the solidity() method) to another. It takes both indexed values (integers) and strings:
OperatorTypes:
-1 = NO_OPERATOR
0 = OPERATOR_LESS_THAN
1 = OPERATOR_GREATER_THAN
2 = OPERATOR_EQUAL_TO
3 = OPERATOR_NOT_EQUAL_TO
4 = OPERATOR_GREATER_OR_EQUAL
5 = OPERATOR_LESS_OR_EQUAL
I actually created the new OperatorTypes type myself, and the default way to use it would actually be OperatorTypes.NO_OPERATOR - which is the way the various CivIV types also are meant to be used. (I've made it so that only "NO_OPERATOR" is valid also.)

And here's how to use the attached Custom module:
Code:
from Custom import *
Trigger.solidity = solidity
Trigger.operator = operator
 

Attachments

I am requesting to add a function for checking diplomatic relations. Ex:

Code:
Trigger(iCiv1).operator().[b]Relation(iCiv2, iPoints)[/b]

I would like to create scenarios with Civ1 declaring war on Civ2 after having less than iPoints on their diplomatic modifiers or a DP with Civ2 after having more than iPoints.
 
I am requesting to add a function for checking diplomatic relations. Ex:

Code:
Trigger(iCiv1).operator().[b]Relation(iCiv2, iPoints)[/b]

I would like to create scenarios with Civ1 declaring war on Civ2 after having less than iPoints on their diplomatic modifiers or a DP with Civ2 after having more than iPoints.
Hmm... I'll try to conjure up something for you to test.
 
And here it is:

relation( eRival, eAttitude )
The Condition is cleared if the target player has an attitude that is equal to or less than the eAttitude value. The latter can either be defined as a numerical value or a string, like "ATTITUDE_CAUTIOUS". All the AttitudeTypes and their indexed values can be found here.

Included is also the operator() method from the earlier post and they are used like:
Code:
Trigger(0).relation(1,"ATTITUDE_CAUTIOUS").operator("OPERATOR_EQUAL_TO")...
But this is also valid:
Code:
Trigger(0).relation(1,2).operator(2)...
The relation() method of course also works without the operator() method. Use the add-on only if you wanna change the default OperatorTypes.OPERATOR_LESS_OR_EQUAL setting.

Add this to your Scenario module after the other import statements at the beginning:
Code:
from Custom import *
Trigger.relation = relation
Trigger.operator = operator
It should also be noted that the relation() Condition works even if the two Civs haven't actually met - or have lost contact. Therefore, if you wanna use the treaty() Action to cause a state of war, you should also use the contact() Condition to make sure that the two combatants are currently in contact.
 

Attachments

Here's another of LuKo's requests:

cities( iNum, ePlayer=None, bCitizenCount=False )
The Condition looks for the number of cities belonging to the ePlayer Civ - or the target player if ePlayer=None - and passes it matches iNum (or greater). The operator can be changed with the operator() method which is included. The bCitizenCount setting can be enabled to look for the sum of all cities' sizes instead of the number of cities (regardless of their size).
Code:
from Custom import *
Trigger.cities = cities
Trigger.operator = operator
 

Attachments

Could I request a player action to sign (or end) def. pacts with other civs? It would be nice to make wars more accurate, etc.
 
Could I request a player action to sign (or end) def. pacts with other civs? It would be nice to make wars more accurate, etc.
I'll look into it. :scan:
 
As a quick fix I added an argument to the treaty() method:

treaty( eCounterpart, bPeace=True, bOpenBorders=False, bDefensivePact=False, bOverride=True )
It works the same as before, now there is the option of enabling defensive pacts. Remember however that one of the parties need to have the Military Science Tech.

Unpack the attached Custom.py file into your \Rhye's and Fall of Civilization\Assets\Python\ folder and add these line to your Scenario module:
Code:
from Custom import *
Trigger.treaty = treaty
If you manage to test this before the next update the added functionality will be included. :goodjob:
 

Attachments

This is great. I don't fully understand the syntax yet, but I'll look at the examples in the subforum and try some things.

The most interesting feature is that this allows a common framework for a lot of variations on RFCE, simply changing or replacing the Scenario.py file.

To start, there's a little thing I'm not sure if it's doable. I usually spend a great deal of time restarting the game after autoplay when choosing a medium-late civ. I hate having no Jerusalem and/or Christian Holy City. Barbs raze them very often (if there are no wonders in the cities).

Is it possible to have a trigger to prevent civs from razing capitals or holy cities? I suppose it had to identify the "conquering a city" event and somehow disable razing when one of the two conditions applies.

And can the Conquerors event be converted to PyScenario? Or what would I need to do to disable the existing one? I always thought it had very little use for the AI.

The same applies for Congresses.

I have some ideas for a RFC:Eurocentric modmod. I could start a thread to discuss it if you were kind enough to help me a little. :rolleyes:

Only another quick question: I also would like to alter a lot the stability penalties on number of cities and having cities in certain areas. I suppose this can be achieved with PyScenario by counter-acting the default RFC penalties (that is, giving stability points each X turns for certain conditions).

But you can't also change the tech penalty rate for more than 10 cities, can you? :confused:
 
Most of the things you mention lie beyond the scope of PyScenario, as it really isn't meant to be used as a modding tool as much it is designed to be used for scenario events. So you pretty much need to learn Python. :p But if you do, there are few limits on what you can achieve, so I would strongly recommend it! :goodjob:

Now, don't feel intimidated by the whole programming thing. Its not that special and Python is actually fairly easy - and fun! - to learn. The language was designed by geeks many years ago and its all about having fun - geeky fun. :D (Did you realize that the name "Python" is a reference to Monty Python? :lol: This is why most of the examples in the official documentation revolves around old Monty Python sketches! :crazyeye:)

So you should totally post your ideas and I could get you started. You could be learning while doing, even if I strongly recommend that you read this textbook (available online for free). Trust me, you'll be Python modding in no time at all! :king:

And PyScenario will still be a convenient way of doing some basic scenario things.
 
Not even for Congresses?
I guess anything is possible, but just because it can be done doesn't mean that it should be included.

I plan on processing through much of the RFC Python code however (much like I'm doing with the City Name Manager, as discussed in another thread) in order to make things more easily accessible for modders. But you'd still need some basic Python knowledge to mod the mod, so there is no time like the present when it comes to taking those first fumbling steps.

And if you're gonna learn a little, you could just go all the way. Then you'd be able to redesign pretty much the whole mod. (Which I'm also planning on doing myself. :D) I don't know if you can believe this, but 7 months ago I knew absolutely nothing about Python. :eek: If I only had someone to help me get started and the textbook I mentioned before I would have been programming in a matter of days, not weeks or months. So even without all that I learned pretty much all the basics by trial and error in about a month or so. Just take the plunge. :D Take the blue pill. :scan:
 
I just got confirmation that the latest version of the RFC Epic/Marathon mod-mod is indeed the definitive release. This means that it would actually make sense to port PyScenario to RFC:E/M. Should I?

Since PyScenario is still only in beta testing, I think I could get away with abandoning the official version and continue development on this other version instead. This pretty much means that any future version of PyScenario wont be adapted to work with the regular version any more... This makes it harder to get people to test the application, as relatively few people would be running anything other than the official version (or even outdated official versions). Which beats the purpose of public beta-testing.

There are technical issues to look into however, before I can even decide. Like how the turn() method would work, because RFC:E/M throws any set game turn setup right out the window. It seems that the year() method would become the default game turn Condition instead. Which isn't optimal since game year intervals vary during a game.

It is also somewhat problematic that one of the core features of PyScenario is the ability to have Triggers fire on intervals/randomly. If you change the game speed, then the scenario gets thrown out of whack. And if the interval/probability scales with game speed - then you'd pretty much have to design your scenario with Normal speed turn intervals. This would make actual scenario making problematic, as I believe that most scenario makers would opt for a game speed other than Normal.

And making Epic/Marathon speed the default - and scaling down intervals for Normal speed - then there would be issues with rounding down short intervals. Some scenarios simply wouldn't work as intended on Norman speed, as the code would do unexpected things because of the scaling.

One possible solution would be to limit any Scenario.py file to one set game speed only. Either it wont initialize on another game speed - or it would alter the game speed after the fact (if that even is doable). But I just don't know...

Any thoughts on this? Should the next PyScenario version be 2.0 - for RFC:E/M only?
 
Back
Top Bottom