How to make a Python mod

Python modding is actually not the most important stuff to know when it comes to CivIV modding. Knowing your WBS and XML is probably much more important - and the SDK is where the real power is at. But Python is still handy to know.

This is the revenge of the developers of CIV! It was so cool to use 3 serial instances of rules.txt for CIV2 in order to have 9 additional custom units alltogether ... But, we all begged for it: give _more_ power to the people, it would be so great to ...
And now we have it: You nearly are better a software engineer for modding CIV.:goodjob::lol:I know C and try to learn the ideas (ie. the syntax :p) of C++ & Python ... (it was a long way already, my first programs I did in FORTRAN, input on punch cards, output the next day on a paperlisting, no joke) ... and then I do the things behind the abbreviations :D

I'm not sure how your complete code would look like, but it would probably be something like this [...] (The %s bit adds the integer value into the string after conversion to string type.)
If anybody is remotely interested, I'll post my complete code, when it is ready. For the part you provided, it will exactly look so.:)
***
Firstly the global variable iturn_counter won't be recognized inside checkTurn() as a global variable because you're assigning values to it (with the -= operator). This is why you need to use the global command to alias the local variable with the global variable before you assign or reference it inside the function
Haha, great opportunity for a tenacious bug: If anyone don't know that (and "one" variable is treaded as two ...)
Good to know! Thinking about it, I don't want to use it in another file/module, so I do not need really a global variable here. Can I use something like
Code:
static iturn_counter
inside the function in order to keep the value from one turn to the next? How will it be initialized, if I don't assign a value? Zero, random??

Just to be sure: the following lines [store a custom value inside the game so what it will be saved - and loaded] shall be in the rebel.py, not added to the CvEventManager!?!
Code:
onPreSave(argsList):
[...]
onLoad(argsList):
[...]
***
Code:
eHuman = cyGame.getActivePlayer()
if ePlayer == eHuman:
yes, yes, yes, that is exactly what I need! (Just for curiousity - in multiplayer I would check another variable too? Or would the player ID be evaluated, eg. above & below a fix number? I guess, eHuman is possibly a list, array of variables?)
***
How can a promotion like the Guerilla (I & with lower probability the higher) be simply realized? Just the key function call, please.
***
cyCity.getMilitaryHappinessUnits() ... isn't this limited to 2 or 3?? Or do I mix up with the older versions of CIV? In the moment I am toying around with
iNumRebels = max(1, cyCity.getCulturePercentAnger() - cyCity.getMilitaryHappinessUnits() )
(Illustration: Rebels are the foreign people which are not "contented" by military means.)

Again, thank You very much for your help. How goes the saying?
We can only look so far, because we are standing on the shoulders of giants!;)
 
This is the revenge of the developers of CIV! It was so cool to use 3 serial instances of rules.txt for CIV2 in order to have 9 additional custom units alltogether ... But, we all begged for it: give _more_ power to the people, it would be so great to ...
Yeah, I was doing all that Civ2 modding also...

And now we have it: You nearly are better a software engineer for modding CIV.:goodjob::lol:I know C and try to learn the ideas (ie. the syntax :p) of C++ & Python ... (it was a long way already, my first programs I did in FORTRAN, input on punch cards, output the next day on a paperlisting, no joke) ... and then I do the things behind the abbreviations :D
I don't know if you know any Object-Oriented Programming as part of your C++ know-how. It should be pointed out that the CivIV Python API is based upon the principle of OOP however. So there is Python syntax and then there is Object-Oriented Python (using classes).

If anybody is remotely interested, I'll post my complete code, when it is ready. For the part you provided, it will exactly look so.:)
Sure, I'd like to have a look. If just to be able to give you a pointer or two... :mischief:

Haha, great opportunity for a tenacious bug: If anyone don't know that (and "one" variable is treaded as two ...)
Good to know! Thinking about it, I don't want to use it in another file/module, so I do not need really a global variable here. Can I use something like
Code:
static iturn_counter
inside the function in order to keep the value from one turn to the next? How will it be initialized, if I don't assign a value? Zero, random??
No, the difference isn't between having the variable in the Event Manager or in the custom module. The distinction here is between local and global variables. The global variables are shared by the entire module (like CvEventManagar or Rebel) - but not by all modules. Local variables only live inside a function and are reset every time they are referenced.

Just to be sure: the following lines [store a custom value inside the game so what it will be saved - and loaded] shall be in the rebel.py, not added to the CvEventManager!?!
The onPreSave() and onLoad() methods are found in CvEventManager. (Look them up for yourself.) But you would need to create corresponding functions in your own module (the module where the variable you are storing/retrieving lives). These function can of course be named whatever, like save() and load() or storeData() and retrieveData(), and you make the call just as you would with the checkTurn() function.

Lets see if you can't figure it out before I give you the whole code for this.

yes, yes, yes, that is exactly what I need! (Just for curiousity - in multiplayer I would check another variable too? Or would the player ID be evaluated, eg. above & below a fix number? I guess, eHuman is possibly a list, array of variables?)
The eHuman variable is basically just a integer value - a player ID. Because each CvPlayer instance gets enumerated from zero and up by the C++. These index values are used to identify the various players and are also valid for identifying and fetching CyPlayer references with Python.

The "active player" is probably the current human player. So if there are several human players (multiplayer) then the player who is active would change during one game turn. But I'm not entirely sure about this.

You might wanna move the function call to checkTurn() from onBeginGameTurn() to onBeginPlayerTurn() by the way.

How can a promotion like the Guerilla (I & with lower probability the higher) be simply realized? Just the key function call, please.
Promotions are coded in the SDK (C++) but the actual settings are found in the XML. But this is also something I would know little to nothing about.

cyCity.getMilitaryHappinessUnits() ... isn't this limited to 2 or 3?? Or do I mix up with the older versions of CIV? In the moment I am toying around with
I would think that any such limit is dependent on civics settings. But I was under the impression that the getMilitaryHappinessUnits() method was counting the number of units capable of maintaining order.
 
I don't know if you know any Object-Oriented Programming as part of your C++ know-how.
I spend a few man years on programming in C, know about the purpose and concepts of OOP in theory, but did in C++ (or any other OOP language) just study some examples. So reading code I understand most of it, but writing is :crazyeye: Of course this will become better with time (especially when I come around to (re)install the game and can feel the interpreter throwing exceptions at me ... actually modifying IT seems right now more interesting than playing IT, does anybody know what that means:confused:
I heard some talking about a "burn-down-syndrome" (or something alike). Probably playing the "All barbarian" scenario in BtS will help!!
Sure, I'd like to have a look. If just to be able to give you a pointer or two.
Great! I am sure there will be something which is useful to be pointed out.:eek:
***
The global variables are shared by the entire module but not by all modules.
Are global variables visible in modules, which import the module defining the variable?
ie. would this {global iturn_counter} defined in rebels.py be visible in subrebels.py if this imports rebels.py?
Local variables only live inside a function and are reset every time they are referenced.
Ok, so I define the global variable outside the function and do the 'aliasing' thing inside ...
Code:
global iturn_counter = 15 + cyGame.getSorenRandNum(10, "rebels tci")
def checkTurn():
  global iturn_counter
  if iturn_counter > 0:		# no rebelion this turn
    iturn_counter -= 1
***
"""onPreSave() and onLoad() shall be in the rebel.py, not added to the CvEventManager!?!"""
The onPreSave() and onLoad() methods are found in CvEventManager, but you would need to create corresponding functions in your own module ...
So the exclusiveOR in my question was wrong, uhhmmm, I change that into a inclusiveOR;)
Now all make much more sense to me (hopefully not using heavily woodpointers right now:D)
Code:
added to the CvEventManager:::

def onPreSave(self, argsList):
		"called before a game is actually saved"
		CvUtil.pyPrint('OnPreSave')
		# Rebels mod
		Rebels.storeData()

def onLoadGame(self, argsList):
		# Rebels mod
		Rebels.retrieveData()
		return 0

defined in rebel.py:::

def storeData(argsList):
    CyGame().setScriptData(str(iturn_counter))

def retrieveData(argsList):
    global iturn_counter
    iturn_counter = int(CyGame().getScriptData())
***
You might wanna move the function call to checkTurn() from onBeginGameTurn() to onBeginPlayerTurn() by the way.
I suppose this avoids in multiplayer always the same (first) human player being punished. But with only 1 human player it is better not to change it (performancewise).
***
"""How can a promotion like the Guerilla (I & with lower probability the higher) be simply realized?"""
Promotions are coded in the SDK (C++) but the actual settings are found in the XML. But this is also something I would know little to nothing about.
In the CyUnit object I found this:
# VOID promote(PromotionType ePromotion, INT iLeaderUnitId)
Might be used for Promotion of existing units, but the LeaderUnitId confuses me.

Then in the CyPlayer object:
# CyUnit initUnit (UnitType iIndex, INT iX, INT iY, UnitAIType eUnitAI, DirectionType eFacingDirection) // NOTE: Always use UnitAITypes.NO_UNITAI
Well, wouldn't something like UNITAI_ATTACK not be more that what is intended with the rebels??!!

And wouldn't it make sense to promote the unit already on creation? But if the function doesn't accept a iGuerilla1 promotion in the arglist:sad:
***
"""cyCity.getMilitaryHappinessUnits() ... isn't this limited to 2 or 3?"""
I would think that any such limit is dependent on civics settings. But I was under the impression that the getMilitaryHappinessUnits() method was counting the number of units capable of maintaining order.
You are very probably right with both statements. Let me see, on game start: 2 people are content anyway, 2 by military means, 2 by the temple, 3 by the cathedral ... no, collosea are too expensive for me right now, let alone commerce into luxury ...
I have to install the beast next weekend! I'll be back.
 
I spend a few man years on programming in C, know about the purpose and concepts of OOP in theory, but did in C++ (or any other OOP language) just study some examples. So reading code I understand most of it, but writing is :crazyeye:
Knowing how to make your own classes and use them is optional for this sort of Python modding. But it helps that you know about classes, because then the API will make that much more sense to you. (The fact that you need a class instance to invoke the class methods on.)

Of course this will become better with time (especially when I come around to (re)install the game and can feel the interpreter throwing exceptions at me ... actually modifying IT seems right now more interesting than playing IT, does anybody know what that means:confused:
I heard some talking about a "burn-down-syndrome" (or something alike). Probably playing the "All barbarian" scenario in BtS will help!!
Personally I haven't played one single game since I started modding. Mostly because I have to prioritize my free time, and modding (programming) comes first. But running hundreds of hours of autoplay to watch the effects of my modding does nothing for making me wanna play what I created... :rolleyes:

Are global variables visible in modules, which import the module defining the variable?
ie. would this {global iturn_counter} defined in rebels.py be visible in subrebels.py if this imports rebels.py?
It depends. By default you'd have to reference the varable with rebels.iturn_counter but you can import the value to the name space of the subrebels module with:
Code:
from rebels import iturn_counter
But I'm not even sure if changing the value of the variable in the sub module will change it in the main module...

Ok, so I define the global variable outside the function and do the 'aliasing' thing inside ...
No, the global statement is separate from the assignment statement. And I don't think you need the two global statements either:
Code:
iturn_counter = 15 + cyGame.getSorenRandNum(10, "rebels tci")
def checkTurn():
  global iturn_counter
  if iturn_counter > 0:		# no rebelion this turn
    iturn_counter -= 1
In the CyUnit object I found this:
# VOID promote(PromotionType ePromotion, INT iLeaderUnitId)
Might be used for Promotion of existing units, but the LeaderUnitId confuses me.
It is confusing but I don't think that this is the way to give a unit a promotion. With Python you'd use CyUnit.setHasPromotion().

Then in the CyPlayer object:
# CyUnit initUnit (UnitType iIndex, INT iX, INT iY, UnitAIType eUnitAI, DirectionType eFacingDirection) // NOTE: Always use UnitAITypes.NO_UNITAI
Well, wouldn't something like UNITAI_ATTACK not be more that what is intended with the rebels??!!
Yeah, you would be correct on this. And I've never experienced any issues with using other than the default unit AI setting.

Note however that I simplified the method invocation in my tutorial code by utilizing the PyHelpers module. Because there you can use the PyPlayer class method initUnit() which is a wrapper for the CyPlayer method with the same name. Instead of having to type the unit AI and direction values you can add a optional number of units parameter. Making the PyPlayer method easier to use for most purposes. A seasoned Python modder can of course opt to use the CyPlayer.initUnit() method instead in order to set any unit AI desired.

And wouldn't it make sense to promote the unit already on creation? But if the function doesn't accept a iGuerilla1 promotion in the arglist:sad:
This is in fact something I suggested in my last "lesson" and the way to do it would be:
Code:
eGuerilla = gc.getInfoTypeForString("PROMOTION_GUERILLA1")
...
    pUnit = pPlayer.initUnit(eUnitType, iX, iY, UnitAITypes.UNITAI_ATTACK, DirectionTypes.NO_DIRECTION)
    pUnit.setHasPromotion(eGuerilla)
 
hundreds of hours of autoplay to watch the effects of my modding does nothing for making me wanna play what I created...
Don't worry, probably it is just the next level of gaming experience ...:D
playing CIV: build an empire, rule the world, stand the test of time ... (under given rules)
modding CIV: create the world, make the rules, be GOD ... (watching the AIPlayers build their empires)
With Python you'd use CyUnit.setHasPromotion().
Aha, now as I see it, the name makes sense, but somehow I missed to look for that.:blush:
Code:
VOID setHasPromotion (PromotionType eIndex, BOOL bNewValue)
With {pUnit.setHasPromotion(eGuerilla)} you skipped the bNewValue, but what does this influence?
And in general, skipping arguments means the program assumes always the same (a default value, eg. TRUE) or is the result random? (Don't tell me, that it is just in the definition, but actually the code says:"don't care" ...:lol:)
+++
Hey, one moment, do you use a 'wrapper' here and nothing is 'skipped'??
pUnit = pPlayer.initUnit(eUnitType, iX, iY, UnitAITypes.UNITAI_ATTACK, DirectionTypes.NO_DIRECTION)
pPlayer uses all arguments for the initUnit method, so this seems to indicate that it is a CyPlayer instance ...
with the same naming convention pUnit would be a instance of the CyUnit class
# constants
pPlayer = CyPlayer(:eek:
pUnit = :crazyeye:
eGuerilla = gc.getInfoTypeForString("PROMOTION_GUERILLA1")
pPlayer is an instance of what? And pUnit?? Please help and make this both lines complete.
***
To really playbalance the amount & strength of generated rebels a lot of real playing is necessary. This 'magic' constants can be changed easily later. Also for now I'll just make a test bed without actually checking the requirements for a rebellion.

So I want to add one new aspect to "my" first python module, which can be simply tested & experienced:
At the beginning of a normal game count the yields of the tiles the settler steps onto or stays on. After collecting eg. 20 commerce he develops the 'agriculture' tech, which allows him to settle down.
This means the following new features:
A find the current tile of the settler
B find out the yield (per turn) of this tile
***
in CvPlot.cpp is defined:
Code:
int CvPlot::getYield(YieldTypes eIndex) const
{
...
	return m_aiYield[eIndex];
...
}
Python sees this as CyPlot Object:
Code:
INT getYield (YieldType eIndex)
with eIndex € of {YIELD_FOOD, YIELD_PRODUCTION, YIELD_COMMERCE}
Code:
currentPlot = plot()                                  ###
iyieldAmounts[NUM_YIELD_TYPES]
...
for iYieldType in range(0, NUM_YIELD_TYPES):
  iyieldThisTurn = currentPlot.getYield(iYieldType)   ###
  iyieldAmounts[iYieldType] = iyieldThisTurn
...
I'm uncertain with the marked lines and how to find the starting settler unit - surely it is only one at this point of time.

I'll be back next weekend.
 
Oops! :blush: I guess I forgot the second argument/parameter in my example...

The CivIV Python API doesn't use any "default" parameters though. But you most definitely can in your own Python code! A "wrapper" for the method in question could look like this:
Code:
def addPromotion(pUnit, ePromotion, bAdd=True):
    pUnit.setHasPromotion(ePromotion, bAdd)
So you could both use addPromotion(pUnit, ePromotion) and addPromotion(pUnit, ePromotion, False) - the latter example will of course turn the function into its opposite.

Regarding variable naming conventions within a CivIV Python context there are a couple of things you should know. While the various enumerated types used for identifying various CivIV components are basically integer values (at least in Python terms) - but not all of them! - it is customary to differentiate iVariables from eVariables. A counter variable (integer) would be named iCounter while a promotion ID variable (integer) would be called ePromotion. But often they are both given the i prefix, which is confusing.

The p prefix is used for Cy class instances, like CyPlayer, CyUnit and CyCity. A CyPlayer instance is fetched with the CyGlobalContext class (usually assigned to the name gc) using the player ID (integer) as the identifier:
Code:
pPlayer = gc.getPlayer(ePlayer)
Fetching CyCity instances is mostly done via CyPlot instances, with CyPlot.getPlotCity() while CyUnit instances are more tricky to get hold of. (You basically need to know what tile the unit is on and what player it belongs to. But you might still end up with several candidates...)

In my tutorial I'm utilizing the PyHelpers module however, so its actually a PyPlayer instance and not a CyPlayer instance. And a PyPlayer instance is basically just a wrapper for a CyPlayer instance - but with its own API. (The most important features of the PyPlayer class are the methods getCityList() and getUnitList(). But initUnit() is also useful, as we demonstrated earlier.)

Regarding your own mod idea, you basically need to figure out how to get the CyUnit instance of the initial Settler unit. It could be as easy as invoking CyPlayer.getUnit(0) - which would fetch the first CyUnit instance of the CyPlayer in question. But that might not be completely accurate on every occasion, with Scout units and what not in circulation. So you could actually make use of the PyHelpers module and invoke PyPlayer.getUnitList() and traverse the units in the list returned. Then catch the CyUnit instance of the first Settler unit with CyUnit.getUnitType() or CyUnit.getUnitClass(), depending on how fool proof you wanna make your mod...

So far so good. Next you need to get the CyPlot instance, which is done with CyUnit.plot(). And then CyPlot.getYield(). You might wanna store the accumulated yields directly in the CyUnit instande with CyUnit.setScriptData() so that the count will also be saved along with the game. Instead of using a regular counter variable you access the stored value with CyUnit.getScriptData() but remember to change the value to string type and back into a integer value when you do so. A couple of helper functions could make things easier for you:
Code:
def getYields(pUnit):
    scriptData = pUnit.getScriptData()
    iValue = int(scriptData)
    return iValue

def setYields(pUnit, iValue):
    iOldValue = getYields(pUnit)
    iNewValue = iOldValue + iValue
    scriptData = str(iNewValue)
    pUnit.setScriptData(scriptData)
Granting the tech is done with CyTeam.setHasTech() but for that you of course need a CyTeam instance. It might not be obvious how this is done, but its CyGlobalContext.getTeam(). Note however that you need a team ID (integer) for this, which you can get with CyPlayer.getTeam(). Check the API for reference and it should make sense.
 
So you could both use addPromotion(pUnit, ePromotion) and addPromotion(pUnit, ePromotion, False)
Hey, this wrapping is cool! In this special case I would even prefer an extra {def subPromotion(pUnit, ePromotion, bAdd=False): } ... But is this demotion anywhere used in the original game already??
The p prefix is used for Cy class instances, like CyPlayer, CyUnit and CyCity.
p for pointer? Pointer to the implicit declared & created (==memoryallocated) object used in the visible assign statement?

(pPlayer for instances of the CyPlayer class ... I would like to propose the c prefix, no, not for constants. The c prefix would be topcool for instances of the PyXXX classes ... just kidding. Is there a convention for those instances? Sort of, l, if you only use the list returning methods. ;) )
I love your convention used in the example of the thread!
cyXXX = CyXXX() & pyXXX = PyXXX()

Is there anywhere something for the PyXXX classes what the CivIV Python API is for the CyXXX classes?
Spoiler :
Code:
def checkTurn():
   """Checks all players once per turn and executes only the (single) human."""
   eHuman = cyGame.getActivePlayer()
   iPlayersAlive = cyGame.countCivPlayersAlive()   # const=16?! TBD
   for iPlayer in range(0, iPlayersAlive):
      if iPlayer == eHuman:
         do_nomad(eHuman)
         do_rebel(eHuman)
         return

#---- nomad subroutines ----#

def do_nomad(eHuman):
   """Finds the plots of both human start units and accumulates their yields. Generates messages or units when applicable."""
   cyPlayer = gc.getPlayer(eHuman)
   cyUnit = cyPlayer.getUnit(0)
   cyPlot = cyUnit.plot()
   for eYieldType in range(0, eNUM_YIELD_TYPES):
      iyieldAmounts[eYieldType] = cyPlot.getYield(eYieldType)    # get yield triplet of UNIT0 this turn

# ===============================>>>> this is kind of wrong
# ArgumentError: Python argument types in
#    CyPlot.getYield(CyPlot, int)
# did not match C++ signature:
#    getYield(class CyPlot {lvalue}, enum YieldTypes)

   print ("do_nomad")
   print eHuman

   yieldPopup = PyPopup()
   yieldPopup.setHeaderString("turnYield")
   yieldPopup.setBodyString("** %s ** 12 ** 7 **" % eHuman)
   yieldPopup.launch()
...

#---- rebel subroutines ----#

def do_rebel(eHuman):
   """Checks all human cities and executes the rebels event when applicable."""
   cyPlayer = gc.getPlayer(eHuman)the PyXXX classes

   pyPlayer = cyPlayer.getPy()

## ===============================>>>> this is what I _want to do_, I know that it does not work (ref. CyAPI)
## wouldn't be elegant anyway ...

   for pyCity in pyPlayer.getCityList():
      cyCity = pyCity.GetCy()
      if checkCity(cyCity):
         spawnRebels(cyCity, getPlotList(cyCity))
...
I installed & got all running & hoped to finish it, but have now 2 problems left: I have an error which I cannot figure out:
ArgumentError: Python argument types in CyPlot.getYield(CyPlot, int) did not match C++ signature: getYield(class CyPlot {lvalue}, enum YieldTypes)
... and need a transformation from Cy to Py class:
pyPlayer = cyPlayer.getPy() is wrong, but what would be right?
You might wanna store the accumulated yields directly in the CyUnit instande with CyUnit.setScriptData() so that the count will also be saved along with the game.
Yields is a triplet (array) of integers. Will the serial access take care of the elements automatically (ie. just fetch scriptData 3 times) or have I to tell it WHAT to deliever (int)?
***
Thank You :king: SO MUCH for your broad help! I want to ask for one last new aspect:
Which is the method (of the CyPlot class I suspect) to make a tile visible to a player / team?
 
Hey, this wrapping is cool! In this special case I would even prefer an extra {def subPromotion(pUnit, ePromotion, bAdd=False): } ... But is this demotion anywhere used in the original game already??
Not in the CivIV Python API at least. A fellow modder showed me this "trick", actually. :D My own application relies heavily on it, by the way.

p for pointer? Pointer to the implicit declared & created (==memoryallocated) object used in the visible assign statement?

(pPlayer for instances of the CyPlayer class ... I would like to propose the c prefix, no, not for constants. The c prefix would be topcool for instances of the PyXXX classes ... just kidding. Is there a convention for those instances? Sort of, l, if you only use the list returning methods. ;) )
Yeah, I guess the p is for pointer but then again all variables seem to be pointers with Python... The nice thing about programming however is that you get to make your own conventions. Looking at other peoples Python mods this is indeed what they are doing...

Is there anywhere something for the PyXXX classes what the CivIV Python API is for the CyXXX classes?
Not that I'm aware of, at least. Just hunt down the module and read the documentation supplied with the code.

Code:
def checkTurn():
   """Checks all players once per turn and executes only the (single) human."""
   eHuman = cyGame.getActivePlayer()
   iPlayersAlive = cyGame.countCivPlayersAlive()   # const=16?! TBD
   for iPlayer in range(0, iPlayersAlive):
      if iPlayer == eHuman:
         do_nomad(eHuman)
         do_rebel(eHuman)
         return
This doesn't seem to work. Try this instead:
Code:
def checkTurn():
   """Checks all players once per turn and executes only the (single) human."""
   eHuman = cyGame.getActivePlayer()
   do_nomad(eHuman)
   do_rebel(eHuman)
Because countCivPlayersAlive() will only give you a integer value, not an array of active players. So if a game has 16 players of which 10 are alive, you would only be looping through players 0-9 regardless if any of the players 10-15 are alive or not.

Code:
def do_nomad(eHuman):
   """Finds the plots of both human start units and accumulates their yields. Generates messages or units when applicable."""
   cyPlayer = gc.getPlayer(eHuman)
   cyUnit = cyPlayer.getUnit(0)
   cyPlot = cyUnit.plot()
   for eYieldType in range(0, eNUM_YIELD_TYPES):
      iyieldAmounts[eYieldType] = cyPlot.getYield(eYieldType)    # get yield triplet of UNIT0 this turn
You have defined the name eNUM_YIELD_TYPES as a constant, right? And iyieldAmounts is defined as a empty dictionary? (I'd suggest you drop the i prefix for a dictionary. Perhaps call it yieldAmountsDict or something?)
# ArgumentError: Python argument types in
# CyPlot.getYield(CyPlot, int)
# did not match C++ signature:
# getYield(class CyPlot {lvalue}, enum YieldTypes)
Ok, this is an instance where a method isn't accepting an integer value in the place of the enumerated type. Very frustrating! So you basically have to find a way to pass he getYield() method a YieldTypes.YIELD_FOOD, YieldTypes.YIELD_PRODUCTION and YieldTypes.YIELD_COMMERCE respectively. You could either do this on three separate lines or store the references in an array of some sort and loop through them. Or you could use the built-in YieldTypes() class to create your own types on the fly with:
Code:
yieldsDict = dict()
for iType in range(YieldTypes.NUM_YIELD_TYPES):
   eType = YieldTypes(iYield)
   yieldsDict[eYieldType] = cyPlot.getYield(eType)
... and need a transformation from Cy to Py class:
pyPlayer = cyPlayer.getPy() is wrong, but what would be right?
You create a PyPlayer instance with PyPlayer(ePlayer), right? So then you need the PlayerTypes equivalent of the CyPlayer instance, and this is done with CyPlayer.getID(). So the whole thing would then be:
Code:
ePlayer = cyPlayer.getID()
pyPlayer = PyPlayer(ePlayer)
Of course you could make a helper function for the switch:
Code:
def switchCyPy(instance):
   if isinstance(instance, CyPlayer):
      return gc.getPlayer(instance.CyGet())
   else:
      return PyPlayer(instance.getID())
I haven't tried it myself but the you get the general idea. You'd be able to use the same function to switch from Cy to Py, and back from Py to Cy.

Yields is a triplet (array) of integers. Will the serial access take care of the elements automatically (ie. just fetch scriptData 3 times) or have I to tell it WHAT to deliever (int)?
I'm not sure what you mean. But you would basically have to transform the 3 integer values into one single string type, like scriptData = str(iFood) + str(iProduction) + str(iCommerce) and use that instead. Then upon fetching the scriptData you would have to separate the three characters of the returned string and turn those into integer values again. A very nice way of doing this is using a generator expression: iFood, iProduction, iCommerce = list(int(char) for char in scriptData) :D

Which is the method (of the CyPlot class I suspect) to make a tile visible to a player / team?
CyPlot.setRevealed() but it can be tricky to figure out what all those parameters are really supposed to be. I guess you could try something like:
Code:
pPlot.setRevealed(pPlayer.getTeam(), True, False, -1)
 
Thank GOD its Friday - again. I had to learn a lot. So the popup message takes only 1 line and don't accept &
Also I couldn't find out how to pass more than 1 variable to its body or header ...

Some methods need _exactly_ the argument (in this case TEAM) they expect [INT calculateNatureYield (YieldType eIndex, TeamType eTeam, BOOL bIgnoreFeature)], others are more forgiving [BonusType getBonusType (TeamType eTeam)] ... the latter worked with the teamID I got from [iTeam = cyPlayer.getTeam()], the other not. I stopped to ask why.

It is FAR better to change to windowed mode, for quicker task switches.
my DEBUGGING changes to CivilizationIV.ini:
(first I thought, no need to change that for a bit coding, but after a while "load mod, unload mod etc." I REGRETED it deeply to have not done it as the first action)
Spoiler :

; Specify whether to play in fullscreen mode 0/1/ask
FullScreen = 0
;-----------------------------
; Set to 1 for no tech splash screens
NoTechSplash = 1

; Set to 1 for no intro movie
NoIntroMovie = 1

; Set to 1 for no python exception popups
HidePythonExceptions = 0

;-----------------------------

; Move along
CheatCode = chipotle

;-----------------------------

; Custom Screen Height, minimum: 768 - Normal resolutions can be set in-game and will be used when set to 0
ScreenHeight = 900

; Custom Screen Width, minimum: 1024 - Normal resolutions can be set in-game and will be used when set to 0
ScreenWidth = 1200
; at1280x1024 desktop, windowed better then alt-tab & wait

;-----------------------------

; NEW !!!
ShowPythonDebugMsgs = 1

; Enable the logging system
LoggingEnabled = 1

; Enable synchronization logging
SynchLog = 1

; Overwrite old network and message logs
OverwriteLogs = 1

; Enable rand event logging
RandLog = 1

; Enable message logging
MessageLog = 1
Sorry, this is not exactly RebelsMod, but it is that, what I should have known when beginning to develop / change a python mod.

The program is almost ready. :D One thing I would like to improve is save & load of global variables. In general this is essential, because without saving the mod variables, if you save & load a game say at turn 22 and the mod part still behaves like starting at turn 1 again ... it is uhhm not satisfying.

Spoiler :
Code:
def storeData():
   'called before a game is actually saved FROM onPreSave(self, argsList)'
   """Save the global variables eg. if the game is paused"""
   global iturnCounterFLAG
   global ifoodBox
   global iproductionBox
   global icommerceBox
   scriptData = str(iturnCounterFLAG) + str(ifoodBox) + str(iproductionBox) + str(icommerceBox)
   print scriptData 
   cyGame.setScriptData(scriptData)

def retrieveData():
   'called FROM onLoadGame(self, argsList)'
   """Load the global variables with the saved values"""
   global iturnCounterFLAG
   global ifoodBox
   global iproductionBox
   global icommerceBox
   scriptData = cyGame.getScriptData()
   iturnCounterFLAG, ifoodBox, iproductionBox, icommerceBox = list(int(char) for char in scriptData)

The very last statement of the implemented code looks very cute and lean, but works only with single-digit values ...
otherwise this error occurs:
File "nomads", line 54, in retrieveData
ValueError: too many values to unpack
ERR: Python function onLoad failed, module CvAppInterface
Because this way no delimiter or additional info is stored:

via PRINT:
doNomadTCFLAG: 960
doNomadTRIPLET: 4, 2, 1

PY:OnPreSave
960421

which probably gives the list 9, 6, 0, 4, 2, 1

Can this cute line of code be repaired while remaining cute??? :confused: I have a workaround, but that looks cloggy :p


The other (and very last!) thing I want to ask, is that now every turn a popup window gives the current values of resource boxes (and needs a OK click for continuation) ... is it possible to have such a (tiny) window PERMANENT open, with updated information every turn?
 
Some methods need _exactly_ the argument (in this case TEAM) they expect [INT calculateNatureYield (YieldType eIndex, TeamType eTeam, BOOL bIgnoreFeature)], others are more forgiving [BonusType getBonusType (TeamType eTeam)] ... the latter worked with the teamID I got from [iTeam = cyPlayer.getTeam()], the other not. I stopped to ask why.
Yeah, this phenomenon is a real pain... :p

One thing I would like to improve is save & load of global variables. (...) Can this cute line of code be repaired while remaining cute??? :confused: I have a workaround, but that looks cloggy :p
Ok, time for the advanced course. (I've been holding out on you - intentionally.)

Since the scriptData must be saved in string type you need to convert your integer values to a valid string. The most convenient way to do this is to pickle the information, for example an array:
Code:
lGlobalSettings = [ iturnCounterFLAG, ifoodBox, iproductionBox, icommerceBox ]
Note that you can use any kind of values in a list (or any other data structure for that matter) - not just integer values.

By importing the pickle module you get access to the dumps() and loads() functions. The first one converts anything - a value, an array, an object - into a valid string type. (Cy class objects cannot be pickled though, because they aren't really defined in Python.) And the second function encrypts a pickled string value into whatever it used to be before pickling - it unpickles it.
Spoiler :
Code:
import pickle

...

def storeData():
   lGlobalSettings = [ iturnCounterFLAG, ifoodBox, iproductionBox, icommerceBox ]
   scriptData = pickle.dumps(lGlobalSettings)
   cyGame.setScriptData(scriptData)

def retrieveData():
   scriptData = cyGame.getScriptData()
   lGlobalSettings = pickle.loads(scriptData)
   global iturnCounterFLAG, ifoodBox, iproductionBox, icommerceBox
   iturnCounterFLAG, ifoodBox, iproductionBox, icommerceBox = lGlobalSettings
Note what I did - didn't do - with the global statement(s)! You could actually just have the lGlobalSettings as a one global value and index it for whatever you need. Or use a dictionary with string keys for convenience.


The other (and very last!) thing I want to ask, is that now every turn a popup window gives the current values of resource boxes (and needs a OK click for continuation) ... is it possible to have such a (tiny) window PERMANENT open, with updated information every turn?
I actually have no idea - interface modding is something I've yet to get into myself for real. But I guess you could just add the information to the main game interface instead. It should basically be a matter of modding some of the interface modules, but they are supposed to be somewhat of a mess... :p
 
""I have a workaround, but that looks cloggy""
Ok, time for the advanced course. (I've been holding out on you - intentionally.)
Really??? :eek: Yes, reading dialogs of good pupils asking and good teachers explaining is very effective (and sometimes more entertaining than just the info). [An great example of such dialogs wrote Galileo Galilei, in which of course the "church's view" representative wins the discussion, but who reads with an open mind detects that the other one has and presents in detail the better arguments ... Galilei told what he had to say without being tortured or even burned ...] Isn't it great when religions become grown-up and matured? Of course not mentioning one, which is in the 17th century of its own year counting ....
Uhm, counting, yes - I packed the iturnCounterFLAG and the yieldsTRIPLET into one integer (multiplying by 1000 inbetween), saving this one variable as shown and %dividing (modulo1000) to separate the four again when retrieving, but good code looks elegant ...
Note that you can use any kind of values in a list (or any other data structure for that matter) - not just integer values.
Assigning a pointer to an array of int (structures, functions ... whatever). May the data types be mixed? "pointer to an array of different objects"
... my internet PC & gaming PC are more than 60km distant ... I'll check it out later.
import pickle
does the sequence of the modules matter when importing? ("always #include stdio & stdlib FIRST!")
lGlobalSettings = [ iturnCounterFLAG, ifoodBox, iproductionBox, icommerceBox ]
scriptData = pickle.dumps(lGlobalSettings)
...
lGlobalSettings = pickle.loads(scriptData)
iturnCounterFLAG, ifoodBox, iproductionBox, icommerceBox = lGlobalSettings
YES!:goodjob: this is the elegance, I was looking for. Thank You once again!

I don't need this functionality urgently, but it is good to know how it works and I will definitely put it in, kind of ice cream on the py ...

[[[
What I don't love much in the best PC game ever is the opening window: "Since immemorial times your people have been wandering around as nomads and are now ready to settle down ..." - yeah, since generations and they nevertheless have no idea what lies behind the next forest?! Ok, enough ranting. :)

The nomad units defined here are only for the human players ...
(HINT: look for "may seem wasteful, but settlers confuse the AI" in CvUnitAI.cpp of the original game, let alone special features of settlers or radical new units)

... so the extra nomadic prelude is for the pleasure looking around without the urge (and possibility:D) to settle down ASAP - and a little advantage for the AIPlayers in development.
>>>>>>> with my nomads mod you have no chance to get one of the first 2, 3 religions, which is good in my opinion!
]]]

I think, more than a few dozens of turns are not playable with the AIPlayers behaving normal, meaning there is (nearly) no need to save within the first 10-20 minutes of a fresh game ...

but they are supposed to be somewhat of a mess...
read: YOU HAVE BE WARNED:lol:
 
Spoiler :
Code:

'''nomads.py''' 
'Imported IN CvEventManager.py' 

from CvPythonExtensions import * 
from PyHelpers import * 
from Popup import PyPopup 

popupHeader = "nomadic Rebels mod" 
popupMessage = "This Python mod includes Nomads and Rebels. Your starting WORKER units are Nomads with the \ 
ability to harvest the resources of their tiles. While researching the 'settle down' technologies they can \ 
build a few units. *** *** It also spawns barbarian units around cities with foreign citizens that \ 
are currently in disorder. The unit type, number and strength of Rebel units adapts to the makeup \ 
of the city garrison and the outcome will vary with the circumstances. You have been warned!\n" 

iturnCounterFLAG = 961    # central variable: Nomad state machine, Popup & Rebel counter 
ifoodBox = 0 
iproductionBox = 0        # triplet, holds the harvested yields 
icommerceBox = 0 

gc = CyGlobalContext() 
cyGame = CyGame() 
cyMap = CyMap() 


def showPopup(): 
   'Called at the start of the game FROM onGameStart(self, argsList)' 
   """Display the welcome message on game start, replace starting settler with 2 Nomads & reveal parts of the map""" 

   modPopup = PyPopup() 
   modPopup.setHeaderString(popupHeader) 
   modPopup.setBodyString(popupMessage) 
   modPopup.launch() 

   eHuman = cyGame.getActivePlayer() 
   cyPlayer = gc.getPlayer(eHuman) 
   iTeam = cyPlayer.getTeam() 
   eWorker = gc.getInfoTypeForString("UNIT_WORKER") 
   eMedic = gc.getInfoTypeForString("PROMOTION_MEDIC1") 

   cyUnit = cyPlayer.getUnit(0)            # settler 
   iUnitX, iUnitY = cyUnit.getX(), cyUnit.getY() 
   revealPlots(iUnitX, iUnitY, iTeam) 

   cyPlayer.initUnit(eWorker, iUnitX, iUnitY, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION) 
   cyPlayer.initUnit(eWorker, iUnitX, iUnitY, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION) 
   cyUnit.kill(True, eHuman) 

   cyUnit = cyPlayer.getUnit(1)            # warrior or scout 
   iUnitX, iUnitY = cyUnit.getX(), cyUnit.getY() 
   revealPlots(iUnitX, iUnitY, iTeam) 

   cyUnit = cyPlayer.getUnit(2)            # worker 1 
   cyUnit.setHasPromotion(eMedic, True) 
   cyUnit = cyPlayer.getUnit(3)            # worker 2 
   cyUnit.setHasPromotion(eMedic, True) 


def revealPlots(iUnitX, iUnitY, iTeam): 
   """Reveal Forests & Rivers in distance 2, Hills & Lakes in distance 3 and Mountains & Oases in distance 4""" 

   eForest = gc.getInfoTypeForString("FEATURE_FOREST") 
   eOasis = gc.getInfoTypeForString("FEATURE_OASIS") 

   for iX in range(iUnitX - 2, iUnitX + 3):   # Forests & Rivers 
      for iY in range(iUnitY - 2, iUnitY + 3): 
         cyPlot = cyMap.plot(iX, iY) 
         if cyPlot.isRiver() or (cyPlot.getFeatureType() == eForest): 
            cyPlot.setRevealed(iTeam, True, False, -1) 

   for iX in range(iUnitX - 3, iUnitX + 4):   # Hills & Lakes 
      for iY in range(iUnitY - 3, iUnitY + 4): 
         cyPlot = cyMap.plot(iX, iY) 
         if cyPlot.isHills() or cyPlot.isLake(): 
            cyPlot.setRevealed(iTeam, True, False, -1) 

   for iX in range(iUnitX - 4, iUnitX + 5):   # Mountains & Oases 
      for iY in range(iUnitY - 4, iUnitY + 5): 
         cyPlot = cyMap.plot(iX, iY) 
         if cyPlot.isPeak() or (cyPlot.getFeatureType() == eOasis): 
            cyPlot.setRevealed(iTeam, True, False, -1) 


#---------------------------# 


def checkTurn(): 
   'Called at the beginning of the end of each turn FROM onBeginGameTurn(self, argsList)' 
   """Check players once per turn and execute only the (single) human""" 

   global iturnCounterFLAG 
   eHuman = cyGame.getActivePlayer() 

   iturnCounterFLAG -= 1 
   if iturnCounterFLAG > 60: 
      doNomad(eHuman) 
   else: 
      if iturnCounterFLAG == 0:         # rebellion this turn !!! 
         doRebel(eHuman) 

         iturnCounterFLAG = 1 + cyGame.getSorenRandNum(5, "rEBEL!") 
         # punishment in 1 out of 3 turns (chance altogether: 22.2% per vacant City & Turn) 


#---- nomad subroutines ----# 


def doNomad(eHuman): 
   """Harvest yields. Generate messages or units when applicable""" 

   global iturnCounterFLAG 
   global ifoodBox 
   global iproductionBox 
   global icommerceBox 

   harvestYields(eHuman) 

   print ("doNomadTCFLAG: %s" % iturnCounterFLAG) 
   print ("doNomadTRIPLET:", ifoodBox, iproductionBox, icommerceBox) 

   itriplet = ifoodBox*1000000 + iproductionBox*1000 + icommerceBox 
   yieldPopup = PyPopup() 
   yieldPopup.setHeaderString("FPC %s" % itriplet) 

   if iturnCounterFLAG > 900:         # producing Warrior 
      if iproductionBox > 14:         # ready? 
         iproductionBox -= 15 
         iturnCounterFLAG = 841 

         eWarrior = gc.getInfoTypeForString("UNIT_WARRIOR") 
         cyPlayer = gc.getPlayer(eHuman) 
         cyUnit = cyPlayer.getUnit(2) 
         iUnitX, iUnitY = cyUnit.getX(), cyUnit.getY() 
         cyPlayer.initUnit(eWarrior, iUnitX, iUnitY, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION) 

         yieldPopup.setBodyString("Your nomads raised a Warrior unit") 
         yieldPopup.launch() 
         return 
      else: 
         yieldPopup.setBodyString("currently training a Warrior unit (%s/15P)" % iproductionBox) 

   elif iturnCounterFLAG > 800:       # researching Settle Basics I 
      if icommerceBox > 9:            # ready? 
         icommerceBox -= 10 
         iturnCounterFLAG = 781 

         yieldPopup.setBodyString("Your nomads researched Settle Basics I. Complicated stuff, more to learn ...") 
         yieldPopup.launch() 
         return 
      else: 
         yieldPopup.setBodyString("currently researching Settle Basics I (%s/10C)" % icommerceBox) 

   elif iturnCounterFLAG > 700:       # producing Scout 
      if iproductionBox > 14:         # ready? 
         iproductionBox -= 15 
         iturnCounterFLAG = 661 

         eScout = gc.getInfoTypeForString("UNIT_SCOUT") 
         cyPlayer = gc.getPlayer(eHuman) 
         cyUnit = cyPlayer.getUnit(3) 
         iUnitX, iUnitY = cyUnit.getX(), cyUnit.getY() 
         cyPlayer.initUnit(eScout, iUnitX, iUnitY, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION) 

         yieldPopup.setBodyString("Your nomads raised a Scout unit") 
         yieldPopup.launch() 
         return 
      else: 
         yieldPopup.setBodyString("currently training a Scout unit (%s/15P)" % iproductionBox) 

   elif iturnCounterFLAG > 600:       # researching Settle Basics II 
      if icommerceBox > 9:            # ready? 
         icommerceBox -= 10 
         iturnCounterFLAG = 541 

         yieldPopup.setBodyString("Your nomads researched Settle Basics II. We are now ready to settle down") 
         yieldPopup.launch() 
         return 
      else: 
         yieldPopup.setBodyString("currently researching Settle Basics II (%s/10C)" % icommerceBox) 

   elif iturnCounterFLAG > 400:       # producing Settler 1 
      if ifoodBox > 79:               # ready? 
         ifoodBox -= 80 
         iturnCounterFLAG = 361 

         eSettler = gc.getInfoTypeForString("UNIT_SETTLER") 
         cyPlayer = gc.getPlayer(eHuman) 
         cyUnit = cyPlayer.getUnit(2) 
         iUnitX, iUnitY = cyUnit.getX(), cyUnit.getY() 
         cyPlayer.initUnit(eSettler, iUnitX, iUnitY, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION) 

         yieldPopup.setBodyString("Your nomads raised a Settler unit") 
         yieldPopup.launch() 
         return 
      else: 
         yieldPopup.setBodyString("currently training a Settler unit (%s/80F)" % ifoodBox) 

   else:                              # producing Settler 2  / ready? 
      if (ifoodBox + iproductionBox + icommerceBox) > 99: 
         iturnCounterFLAG = 21 

         eSettler = gc.getInfoTypeForString("UNIT_SETTLER") 
         cyPlayer = gc.getPlayer(eHuman) 
         cyUnit = cyPlayer.getUnit(3) 
         iUnitX, iUnitY = cyUnit.getX(), cyUnit.getY() 
         cyPlayer.initUnit(eSettler, iUnitX, iUnitY, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION) 

         eMedic = gc.getInfoTypeForString("PROMOTION_MEDIC1") 
         cyUnit = cyPlayer.getUnit(2)            # demote worker 1 
         cyUnit.setHasPromotion(eMedic, False) 
         cyUnit = cyPlayer.getUnit(3)            # demote worker 2 
         cyUnit.setHasPromotion(eMedic, False) 

         yieldPopup.setBodyString("Your nomads raised again a Settler unit ... No way, you have to start 'a normal life' now!") 
         yieldPopup.launch() 
         return 
      else: 
         yieldPopup.setBodyString("currently training a Settler unit (sum: %s/100FPC)" % (ifoodBox+iproductionBox+icommerceBox)) 

   if (iturnCounterFLAG % 2 == 0):    # start values modulo 60, so popup every DIVISOR turn: 1, 2, 3, 4, 5 & 6 are allowed 
      yieldPopup.launch() 


def harvestYields(eHuman): 
   """Find the plots of the nomads and accumulate their yields""" 

   global ifoodBox 
   global iproductionBox 
   global icommerceBox 

   eBanana = gc.getInfoTypeForString("BONUS_BANANA") 
   eCorn = gc.getInfoTypeForString("BONUS_CORN") 
   eCow = gc.getInfoTypeForString("BONUS_COW") 
   eDeer = gc.getInfoTypeForString("BONUS_DEER") 
   ePig = gc.getInfoTypeForString("BONUS_PIG")           # other food boni inaccessable on coast or ocean 
   eRice = gc.getInfoTypeForString("BONUS_RICE") 
   eSheep = gc.getInfoTypeForString("BONUS_SHEEP") 
   eSugar = gc.getInfoTypeForString("BONUS_SUGAR") 
   eWheat = gc.getInfoTypeForString("BONUS_WHEAT") 

   eIvory = gc.getInfoTypeForString("BONUS_IVORY") 
   eMarble = gc.getInfoTypeForString("BONUS_MARBLE")     # other (strategic) production boni invisible 
   eStone = gc.getInfoTypeForString("BONUS_STONE") 

   eDye = gc.getInfoTypeForString("BONUS_DYE") 
   eFur = gc.getInfoTypeForString("BONUS_FUR") 
   eGems = gc.getInfoTypeForString("BONUS_GEMS") 
   eGold = gc.getInfoTypeForString("BONUS_GOLD") 
   eIncense = gc.getInfoTypeForString("BONUS_INCENSE") 
   eSilk = gc.getInfoTypeForString("BONUS_SILK") 
   eSilver = gc.getInfoTypeForString("BONUS_SILVER") 
   eSpices = gc.getInfoTypeForString("BONUS_SPICES") 
   eWine = gc.getInfoTypeForString("BONUS_WINE") 

   cyPlayer = gc.getPlayer(eHuman) 
   cyUnit = cyPlayer.getUnit(2)            # worker 1, warning msg if lost 
   cyPlot = cyUnit.plot() 

   ifoodBox += cyPlot.getYield(YieldTypes.YIELD_FOOD)   # base + features 
   iproductionBox += cyPlot.getYield(YieldTypes.YIELD_PRODUCTION) 
   icommerceBox += cyPlot.getYield(YieldTypes.YIELD_COMMERCE) 

   iTeam = cyPlayer.getTeam()                           # boni 
   eBT = cyPlot.getBonusType(iTeam) 
   print ("harvestYieldsBT1: %s" % eBT) 

   if eBT==eBanana or eBT==eCorn or eBT==eCow or eBT==eDeer or eBT==ePig or eBT==eRice or eBT==eSheep or eBT==eSugar or eBT==eWheat: 
      ifoodBox += 1 
      print ("doFood++") 
   if eBT==eIvory or eBT==eMarble or eBT==eStone: 
      iproductionBox += 1 
      print ("doProduction++") 
   if eBT==eDye or eBT==eFur or eBT==eGems or eBT==eGold or eBT==eIncense or eBT==eSilk or eBT==eSilver or eBT==eSpices or eBT==eWine: 
      icommerceBox += 1 
      print ("doCommerce++") 

   cyUnit = cyPlayer.getUnit(3)            # worker 2, warning msg if lost 
   cyPlot = cyUnit.plot() 

   ifoodBox += cyPlot.getYield(YieldTypes.YIELD_FOOD)   # base + features 
   iproductionBox += cyPlot.getYield(YieldTypes.YIELD_PRODUCTION) 
   icommerceBox += cyPlot.getYield(YieldTypes.YIELD_COMMERCE) 

   iTeam = cyPlayer.getTeam()                           # boni 
   eBT = cyPlot.getBonusType(iTeam) 
   print ("harvestYieldsBT2: %s" % eBT) 

   if eBT==eBanana or eBT==eCorn or eBT==eCow or eBT==eDeer or eBT==ePig or eBT==eRice or eBT==eSheep or eBT==eSugar or eBT==eWheat: 
      ifoodBox += 1 
      print ("doFood++") 
   if eBT==eIvory or eBT==eMarble or eBT==eStone: 
      iproductionBox += 1 
      print ("doProduction++") 
   if eBT==eDye or eBT==eFur or eBT==eGems or eBT==eGold or eBT==eIncense or eBT==eSilk or eBT==eSilver or eBT==eSpices or eBT==eWine: 
      icommerceBox += 1 
      print ("doCommerce++") 


#---- rebel subroutines ----# 


def doRebel(eHuman): 
   """Check all human cities and execute the rebellion event when applicable""" 

   cyPlayer = gc.getPlayer(eHuman) 
   pyPlayer = PyPlayer(cyPlayer.getID()) 
   for pyCity in pyPlayer.getCityList(): 
      cyCity = pyCity.GetCy() 
      if checkCity(cyCity): 
         spawnRebels(cyCity, getPlotList(cyCity)) 


def checkCity(cyCity): 
   """Check if the CyCity instance is valid for a rebellion""" 

   if cyCity.isDisorder() and cyCity.getCulturePercentAnger() and not cyCity.isNeverLost(): 
      return (cyGame.getSorenRandNum(100, "rebels") < 67)     # punishment in 2 out of 3 vacant cities 
   return False 


def getPlotList(cyCity): 
   """Check all adjacent plots and return a list of CyPlot instances""" 

   lPlots = list() 
   iCityX, iCityY = cyCity.getX(), cyCity.getY() 
   for iX in range(iCityX - 1, iCityX + 2): 
      for iY in range(iCityY - 1, iCityY + 2): 
         cyPlot = cyMap.plot(iX, iY) 
         if cyPlot.isWater() or cyPlot.isPeak() or cyPlot.isCity(): 
            continue 
         lPlots.append(cyPlot) 
   return lPlots 


def spawnRebels(cyCity, lPlots): 
   """Spawn rebel units on surrounding random plots""" 

   iNumPlots = len(lPlots) 
   if iNumPlots: 
      iNumRebels = max(2, cyCity.getCulturePercentAnger() - cyCity.getMilitaryHappinessUnits()) 
      # Illustration: Rebels are the foreign people which are not "contented" by military means 

      pyBarbarian = PyPlayer(gc.getBARBARIAN_PLAYER()) 
      eUnitType = cyCity.getConscriptUnit() 
      eGuerilla = gc.getInfoTypeForString("PROMOTION_GUERILLA1") 
      while iNumRebels: 
         iNumRebels -= 1 
         cyPlot = lPlots[cyGame.getSorenRandNum(iNumPlots, "spawn")] 
         pyRebel = pyBarbarian.initUnit(eUnitType, cyPlot.getX(), cyPlot.getY()) 
         if iNumRebels % 2: 
            pyRebel.setHasPromotion(eGuerilla, True)
Well, this is it. The save & load part is missing, because I still have to implement the latest version & test it.
 
Wow, thats a hefty module right there! You might wanna try importing the Nomads code in a module separately from the Rebels module. You could either include the central checkTurn() function code directly into CvEventHandler and direct the calls from there - or you could have one or the other be the parent module and the other one the child.

Other than that there is some repetitiveness and lots and lots of assignment going on here. You could just define your constants at the __main__ or module level (indentation level zero, just below the import statements) and be done with it. Especially the constants referring to the the human player. And if you include a pyHuman = PyPlayer(eHuman) reference you can use PyPlayer.initUnit() instead of the CyPlayer variety.

You could also do big things with data structures. Firstly you could put all the yield boxes into one array and index it with easily intelligible names:
Code:
iFood, iProduction, iCommerce = range(3) # assigns integer values 0-2
lYieldBox = [0] * 3 # assigns the list [0, 0, 0] as a default
Now you can assign values to the tuple with:
Code:
lYieldBox[iFood] = 1
lYieldBox[iProduction] += 1
lYieldBox[iCommerce] -= 1
And you index it the same way:
Code:
iTempFood = lYieldBox[iFood]
Next up you could put the bonus types into data structures defined as global constants (see above):
Code:
eIvory = gc.getInfoTypeForString("BONUS_IVORY") 
eMarble = gc.getInfoTypeForString("BONUS_MARBLE")
eStone = gc.getInfoTypeForString("BONUS_STONE") 
lProductionBonuses = [ eIvory, eMarble, eStone ]
Or more elegantly:
Code:
tProductionBonuses = ( "IVORY", "MARBLE", "STONE" )
lProductionBonuses = list(gc.getInfoTypeForsString("BONUS_" + bonus) for bonus in tProductionBonuses)
And since you're gonna repeat this for all the bonus categories you might as well have a helper function for it:
Code:
def getEnum(category, entry):
   string = category + "_" + entry
   eNum = gc.getInfoTypeForString(string)
   return eNum
This of course takes care of all your eNums fetched with CyGlobalContext.getInfoTypeForString(). :D Just make sure this function goes above the lines referencing it! And make sure to import your module(s) from the onBeginGame() method in the CvEventManager module - rather from __main__:
Spoiler :
Code:
	onBegingGame(self, argsList):
		global Rebels, Nomads
		import Rebels, Nomads
Now you can access the modules with their names from the entire Event Manager. :king:

What we have now is this:
Code:
tProductionBonuses = ( "IVORY", "MARBLE", "STONE" )
lProductionBonuses = list(getEnum("BONUS", bonus) for bonus in tProductionBonuses)

The you can easily index it with:
Code:
if eBT in lProductionBonuses:
But we can, of course, take this even further:
Code:
tBonuses = (
( "BANANA", "CORN", "COW", "DEER", "PIG", "RICE", "SHEEP", "SUGAR", "WHEAT" ), # food bonuses
( "IVORY", "MARBLE", "STONE" ), # production bonuses
( "DYE", "FUR", "GEMS", "GOLD", "INCENSE", "SILK", "SILVER", "SPICES", "WINE" ) #commerce bonuses
 )
So this is basically three tuples inside another tuple. Then we turn this into a list of tuples with eNums:
Code:
lBonuses = list(tuple(getEnum("BONUS", bonus) for bonus in tBonus) for tBonus in tBonuses)
Because it was elegance you wanted, right? ;)

And now you can do this:
Code:
for eYield in range(YieldTypes.NUM_YIELDS):
   if eBT in lBonuses(eYield):
      lYieldBox[eYield] += 1
Next up you can store the pesky YieldTypes values as global constants, or better yet as an array (also at module level):
Code:
tYieldTypes = ( YieldTypes.YIELD_FOOD, YieldTypes.YIELD_PRODUCTION, YieldTypes.YIELD_COMMERCE )
Now you can do this:
Code:
for iYield in range(YieldTypes.NUM_YIELDS):
   if eBT in lBonuses(iYield):
      lYieldBox[iYield] += 1
   lYieldBox[iYield] += cyPlot.getYield(tYieldTypes[iYield])
And there really is no reason to copy-paste the same code twice just because you're using the code for two units, is there? So this is what the entire harvestYields() function could look like:
Code:
def harvestYields():
   global lYieldBox
   for iUnit in [2, 3]:
      cyPlot = cyHuman.getUnit(iUnit).plot()
      for iYield in range(YieldTypes.NUM_YIELDS):
         if cyPlot.getBonusType() in lBonuses(iYield):
            lYieldBox[iYield] += 1
         lYieldBox[iYield] += cyPlot.getYield(tYieldTypes[iYield])
Oh how I love programming. :D (The lBonuses array was defined outside of the function as above, like was the cyHuman constant. Note that the function needs no argument.)

Now, lets see if we can't do something about revealPlots() also. First off, we could use the getPlotList() function from the Rebels code. It works because both CyPlot and CyCity instances have the same getX() and getY() methods. But we could do something else entirely:
Code:
#constants @ __main__
lRange = range(-4, -1) + range(3, 5) # creates the list [-4, -3, -2, 2, 3, 4]
ehumanTeam = cyHuman.getTeam()
eOasis = getEnum("FEATURE", "OASIS")
eForest = getEnum("FEATURE", "FOREST")
...

def revealPlots(iUnitX, iUnitY):
   for iX in lRange:
      for iY in lRange:
         cyPlot = cyMap.plot(iUnitX + iX, iUnitY + iY)
         if cyPlot.isRevealed(eHumanTeam, False):
            break
         iDistance = max(abs(iX), abs(iY))
         if checkPlot(cyPlot, iDistance):
            cyPlot.setRevealed(eHumanTeam, True, False, -1)

def checkPlot(cyPlot, iDistance)
   eFeature = cyPlot.getFeatureType()
   if iDistance > 3 and (cyPlot.isPeak() or eFeature == eOasis):
      return True
   if iDistance > 2 and (cyPlot.isHills() or cyPlot.isLake()):
      return True
   if cyPlot.isRiver() or eFeature == eForest:
      return True
   return False
Well, thats all for now. Its a nice little mod you made and your code is looking very valid. :goodjob: I think that the important thing is that you're having fun with the code - because I know I am. :D (Programming is a sport, after all. :lol:)
 
You might wanna try importing the Nomads code in a module separately from the Rebels module.
So they can be independently loaded?
In a "normal sized" project I would do that, so that every entity has its own file (altogether maybe several dozens in a 'library'). But in this little exercise, I think, the overall view is better with only 1 file (and minimal changes to EventMgr).
there is some repetitiveness and lots and lots of assignment going on here. You could just define your constants at the __main__ or module level
Yeah, you are right, I wanted to, but stupid me didn't find out how to make it run that way.
make sure to import your module(s) from the onBeginGame() method
Hey, you take away all the excitement! Some funny exceptions, warnings or simply not working code, the asking "Hell, why ...", no more looking around, working around, being forced to have definitions where none wants them ... you are SO EVIL!!!
Seriously, THANK YOU, this one did the trick!!!!!!!!!!!!!!!!!!!!!! (Despite I'm uncertain whether you meant onBeginGameTurn() or onGameStart(), supposed the latter) Now my definitions at module level are no longer ignored, this feels like a dream ...
Now it 'works as designed'. [I liked to respond 'works as implemented' instead of 'its no bug, its a feature'] ...

Still there is a tiny problem: after pause (new start of CIV4 & load savegame) the global declaration seems forgotten, so it has to be in onGameStart & onLoadGame (!?!) ...
More general: Have I introduced the problem by using "real code" in onGameStart() instead of just popping up a simple message?? Would it work then with just the 'normal' import at the beginning of EventMgr?
Because it was elegance you wanted, right?
Yes, I LOVE cute code, it is a pleasure to read it. I'm even tempted to use all of it, but know I'll might have problems to change it later :crazyeye:, when I have new ideas - you know, some programs tend to never become ready.:D
Unfortunately my last programming experience was deeply in the last century :cool:, so a lot is rusted in and out ... :(
lRange = range(-4, -1) + range(3, 5) # creates the list [-4, -3, -2, 2, 3, 4]
My belly would say "range(-4, -2)", but I do not understand _how_ the creation of the list goes ... I would combine (additiv) every element of the first range with every of the second, but with different results ... please explain.
Another point is that by using the intended list for the loops only the corner blocks of the 'Sudoku' will be accessed. The central block is missing on purpose, but the blocks on the edges?
Code:
         if cyPlot.isRevealed(eHumanTeam, False):
            break
         iDistance = max(abs(iX), abs(iY))
         if checkPlot(cyPlot, iDistance):
            cyPlot.setRevealed(eHumanTeam, True, False, -1)
Elegant! Shows the incarnation of the pure thought. I would like to compare at Run-Time how much slower my more primitive code is ...
Code:
   if iDistance > 3 and (cyPlot.isPeak() or eFeature == eOasis):
      return True
   if iDistance > 2 and (cyPlot.isHills() or cyPlot.isLake()):
      return True
   if cyPlot.isRiver() or eFeature == eForest:
just looking at it, methinks the > should be < and applied bottom up (inner rings) instead top down (outer rings) [in order to give the desired results on the map, of course you can redefine them too]. Or have I to look close again? that's not easy, cute code makes me dizzy.
Last night I had the tangent idea, usually you write the correct code and add then some mistakes, so you can see, whether the peoples just copy&paste around with (nearly) random results? No, YOU would never do that, at least not admit, wouldn't you? :D
&#65279;
So now the current code looks like this:
Spoiler :
Code:
&#65279;&#65279;&#65279;&#65279;&#65279;'''nomads.py'''
'Imported IN [CvEventManager.py] @ onGameStart(self, argsList) & onLoadGame(self, argsList)'

from CvPythonExtensions import *
from PyHelpers import *
from Popup import PyPopup
import pickle


popupHeader = "nomadic Rebels mod"
popupMessage = "This Python mod includes Nomads and Rebels. Your starting WORKER units are Nomads with the \
ability to harvest the resources of their tiles. While researching the 'settle down' technologies they can \
build a few units. *** *** It also spawns barbarian units around cities with foreign citizens that \
are currently in disorder. The unit type, number and strength of Rebel units adapts to the makeup \
of the city garrison and the outcome will vary with the circumstances. You have been warned!\n"

gc = CyGlobalContext()

eWorker = gc.getInfoTypeForString("UNIT_WORKER")
eWolf = gc.getInfoTypeForString("UNIT_WOLF")
eWarrior = gc.getInfoTypeForString("UNIT_WARRIOR")
eScout = gc.getInfoTypeForString("UNIT_SCOUT")
eSettler = gc.getInfoTypeForString("UNIT_SETTLER")

eMedic = gc.getInfoTypeForString("PROMOTION_MEDIC1")
eGuerilla = gc.getInfoTypeForString("PROMOTION_GUERILLA1")

eForest = gc.getInfoTypeForString("FEATURE_FOREST")
eOasis = gc.getInfoTypeForString("FEATURE_OASIS")

eBanana = gc.getInfoTypeForString("BONUS_BANANA")
eCorn = gc.getInfoTypeForString("BONUS_CORN")
eCow = gc.getInfoTypeForString("BONUS_COW")
eDeer = gc.getInfoTypeForString("BONUS_DEER")
ePig = gc.getInfoTypeForString("BONUS_PIG")           # other food boni inaccessable on coast or ocean
eRice = gc.getInfoTypeForString("BONUS_RICE")
eSheep = gc.getInfoTypeForString("BONUS_SHEEP")
eSugar = gc.getInfoTypeForString("BONUS_SUGAR")
eWheat = gc.getInfoTypeForString("BONUS_WHEAT")

eIvory = gc.getInfoTypeForString("BONUS_IVORY")
eMarble = gc.getInfoTypeForString("BONUS_MARBLE")     # other (strategic) production boni invisible
eStone = gc.getInfoTypeForString("BONUS_STONE")

eDye = gc.getInfoTypeForString("BONUS_DYE")
eFur = gc.getInfoTypeForString("BONUS_FUR")
eGems = gc.getInfoTypeForString("BONUS_GEMS")
eGold = gc.getInfoTypeForString("BONUS_GOLD")
eIncense = gc.getInfoTypeForString("BONUS_INCENSE")
eSilk = gc.getInfoTypeForString("BONUS_SILK")
eSilver = gc.getInfoTypeForString("BONUS_SILVER")
eSpices = gc.getInfoTypeForString("BONUS_SPICES")
eWine = gc.getInfoTypeForString("BONUS_WINE")

pyBarbarian = PyPlayer(gc.getBARBARIAN_PLAYER())

cyMap = CyMap()
cyGame = CyGame()

eHuman = cyGame.getActivePlayer()
cyPlayer = gc.getPlayer(eHuman)       # should be named cyPlayerHuman?
iTeam = cyPlayer.getTeam()            # return value might be eTeam, but is int

#pyPlayer = PyPlayer(eHuman)          # should be named pyPlayerHuman?
#pyHuman = PyPlayer(eHuman)           #

iturnCounterFLAG = 961    # (16*60+1) central variable: Nomad state machine, yieldPopup & Rebellion counter
ifoodBox = 0
iproductionBox = 0        # triplet, holds the harvested yields
icommerceBox = 0


def storeData():
   'called before a game is actually saved FROM onPreSave(self, argsList)'
   """Save the global variables eg. if the game is paused"""

   lGlobalSettings = [ iturnCounterFLAG, ifoodBox, iproductionBox, icommerceBox ]
   scriptData = pickle.dumps(lGlobalSettings)
   cyGame.setScriptData(scriptData)


def retrieveData():
   'called FROM onLoadGame(self, argsList)'
   """Load the global variables with the saved values"""

   global iturnCounterFLAG, ifoodBox, iproductionBox, icommerceBox

   scriptData = cyGame.getScriptData()
   lGlobalSettings = pickle.loads(scriptData)
   iturnCounterFLAG, ifoodBox, iproductionBox, icommerceBox = lGlobalSettings

   itriplet = ifoodBox*1000000 + iproductionBox*1000 + icommerceBox
   yieldPopup = PyPopup()
   yieldPopup.setHeaderString("FPC %s" % itriplet)
   yieldPopup.setBodyString("loaded TC-FLAG = %s" % iturnCounterFLAG)
   yieldPopup.launch()


#---------------------------#


def init():
   'Called at the start of the game FROM onGameStart(self, argsList)'
   """Display the welcome message on game start, replace starting settler with 2 Nomads & reveal parts of the map"""

   modPopup = PyPopup()
   modPopup.setHeaderString(popupHeader)
   modPopup.setBodyString(popupMessage)
   modPopup.launch()

   cyUnit = cyPlayer.getUnit(0)                      # settler
   iUnitX, iUnitY = cyUnit.getX(), cyUnit.getY()
   revealPlots(iUnitX, iUnitY)

   for i in [1, 2]:
      cyPlayer.initUnit(eWorker, iUnitX, iUnitY, UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION)
   cyUnit.kill(True, eHuman)

   for iX in [iUnitX - 5, iUnitX + 5]:
      for iY in [iUnitY - 5, iUnitY + 5]:
         if cyGame.getSorenRandNum(100, "Wolf") < 50:
            pyBarbarian.initUnit(eWolf, iX, iY)

   cyUnit = cyPlayer.getUnit(1)                      # warrior or scout
   revealPlots(cyUnit.getX(), cyUnit.getY())

   for iUnit in [2, 3]:                              # worker 1 | worker 2
      cyUnit = cyPlayer.getUnit(iUnit)
      cyUnit.setHasPromotion(eMedic, True)


def revealPlots(iUnitX, iUnitY):
   """Reveal Forests & Rivers in distance 2, Hills & Lakes in distance 3 and Mountains & Oases in distance 4"""

   for iX in range(iUnitX - 4, iUnitX + 5):
      for iY in range(iUnitY - 4, iUnitY + 5):
         cyPlot = cyMap.plot(iX, iY)
         if cyPlot.isRevealed(iTeam, False):
            continue
         if (cyPlot.isRiver() or (cyPlot.getFeatureType() == eForest)) and (max(abs(iX-iUnitX), abs(iY-iUnitY)) < 3):    # Forests & Rivers
            cyPlot.setRevealed(iTeam, True, False, -1)
         elif (cyPlot.isHills() or cyPlot.isLake()) and (max(abs(iX-iUnitX), abs(iY-iUnitY)) < 4):                       # Hills & Lakes
            cyPlot.setRevealed(iTeam, True, False, -1)
         elif cyPlot.isPeak() or (cyPlot.getFeatureType() == eOasis):                                                    # Mountains & Oases
            cyPlot.setRevealed(iTeam, True, False, -1)


#---------------------------#


def checkTurn():
   'Called at the beginning of the end of each turn FROM onBeginGameTurn(self, argsList)'
   """Check players once per turn and execute only the (single) human"""

   global iturnCounterFLAG

   print ("checkTurnTCFLAG: %s" % iturnCounterFLAG)
   iturnCounterFLAG -= 1
   if iturnCounterFLAG > 99:
      doNomad()
   else:
      if iturnCounterFLAG == 0:         # rebellion this turn !!!
         iturnCounterFLAG = 1 + cyGame.getSorenRandNum(5, "rEBEL!")
         "punishment in 1 out of 3 turns (chance altogether: 22.2% per vacant City & Turn)"
         doRebel()


#---- nomad subroutines ----#


def doNomad():
   """Harvest yields. Generate messages or units when applicable"""

   global iturnCounterFLAG, ifoodBox, iproductionBox, icommerceBox

   harvestYields()
   print ("doNomadTRIPLET:", ifoodBox, iproductionBox, icommerceBox)

   itriplet = ifoodBox*1000000 + iproductionBox*1000 + icommerceBox
   yieldPopup = PyPopup()
   yieldPopup.setHeaderString("FPC %s" % itriplet)

   if iturnCounterFLAG > 900:         # producing Warrior
      if iproductionBox > 14:         # ready?
         iproductionBox -= 15
         iturnCounterFLAG = 840

         cyUnit = cyPlayer.getUnit(2)
         cyPlayer.initUnit(eWarrior, cyUnit.getX(), cyUnit.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION)

         yieldPopup.setBodyString("Your nomads raised a Warrior unit")
      else:
         yieldPopup.setBodyString("currently training a Warrior unit (%s/15P)" % iproductionBox)

   elif iturnCounterFLAG > 800:       # researching Settle Basics I
      if icommerceBox > 9:            # ready?
         icommerceBox -= 10
         iturnCounterFLAG = 780

         yieldPopup.setBodyString("Your nomads researched Settle Basics I. Complicated stuff, more to learn ...")
      else:
         yieldPopup.setBodyString("currently researching Settle Basics I (%s/10C)" % icommerceBox)

   elif iturnCounterFLAG > 700:       # producing Scout
      if iproductionBox > 14:         # ready?
         iproductionBox -= 15
         iturnCounterFLAG = 660

         cyUnit = cyPlayer.getUnit(3)
         cyPlayer.initUnit(eScout, cyUnit.getX(), cyUnit.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION)

         yieldPopup.setBodyString("Your nomads raised a Scout unit")
      else:
         yieldPopup.setBodyString("currently training a Scout unit (%s/15P)" % iproductionBox)

   elif iturnCounterFLAG > 600:       # researching Settle Basics II
      if icommerceBox > 9:            # ready?
         icommerceBox -= 10
         iturnCounterFLAG = 540

         yieldPopup.setBodyString("Your nomads researched Settle Basics II. We are now ready to settle down")
      else:
         yieldPopup.setBodyString("currently researching Settle Basics II (%s/10C)" % icommerceBox)

   elif iturnCounterFLAG > 400:       # producing Settler 1
      if ifoodBox > 79:               # ready?
         ifoodBox -= 80
         iturnCounterFLAG = 360

         cyUnit = cyPlayer.getUnit(2)
         cyPlayer.initUnit(eSettler, cyUnit.getX(), cyUnit.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION)

         yieldPopup.setBodyString("Your nomads raised a Settler unit")
      else:
         yieldPopup.setBodyString("currently training a Settler unit (%s/80F)" % ifoodBox)

   else:                              # producing Settler 2  / ready?
      if (ifoodBox + iproductionBox + icommerceBox) > 99:
         iturnCounterFLAG = 60

         cyUnit = cyPlayer.getUnit(3)
         cyPlayer.initUnit(eSettler, cyUnit.getX(), cyUnit.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION)

         cyUnit = cyPlayer.getUnit(2)            # demote worker 1
         cyUnit.setHasPromotion(eMedic, False)
         cyUnit = cyPlayer.getUnit(3)            # demote worker 2
         cyUnit.setHasPromotion(eMedic, False)

         yieldPopup.setBodyString("Your nomads raised again a Settler unit ... No way, you have to start 'a normal life' now!")
      else:
         yieldPopup.setBodyString("currently training a Settler unit (sum: %s/100FPC)" % (ifoodBox+iproductionBox+icommerceBox))

   if (iturnCounterFLAG % 2 == 0):    # start values modulo 60, so popup every DIVISOR turn: 1, 2, 3, 4, 5 & 6 are allowed
      yieldPopup.launch()


def harvestYields():
   """Find the plots of the nomads and accumulate their yields"""

   global ifoodBox, iproductionBox, icommerceBox

   for iUnit in [2, 3]:                         # worker 1 | worker 2, warning msg if lost
      cyPlot = cyPlayer.getUnit(iUnit).plot()

      ifoodBox += cyPlot.getYield(YieldTypes.YIELD_FOOD)   # base + features
      iproductionBox += cyPlot.getYield(YieldTypes.YIELD_PRODUCTION)
      icommerceBox += cyPlot.getYield(YieldTypes.YIELD_COMMERCE)

      eBT = cyPlot.getBonusType(iTeam)                     # boni
      if eBT==eBanana or eBT==eCorn or eBT==eCow or eBT==eDeer or eBT==ePig or eBT==eRice or eBT==eSheep or eBT==eSugar or eBT==eWheat:
         ifoodBox += 1
      if eBT==eIvory or eBT==eMarble or eBT==eStone:
         iproductionBox += 1
      if eBT==eDye or eBT==eFur or eBT==eGems or eBT==eGold or eBT==eIncense or eBT==eSilk or eBT==eSilver or eBT==eSpices or eBT==eWine:
         icommerceBox += 1


#---- rebel subroutines ----#


def doRebel():
   """Check all human cities and execute the rebellion event when applicable"""

   pyPlayer = PyPlayer(cyPlayer.getID())
   for pyCity in pyPlayer.getCityList():
      cyCity = pyCity.GetCy()
      if checkCity(cyCity):
         rebelPopup = PyPopup()
         rebelPopup.setHeaderString("#grr$'zAck}*.@.:Bu'hao:@@")
         rebelPopup.setBodyString("You are again the victim of a rebellion!")
         rebelPopup.launch()
         spawnRebels(cyCity, getPlotList(cyCity))


def checkCity(cyCity):
   """Check if the CyCity instance is valid for a rebellion"""

   if cyCity.isDisorder() and cyCity.getCulturePercentAnger() and not cyCity.isNeverLost(): 
      return (cyGame.getSorenRandNum(100, "rebels") < 67)     # punishment in 2 out of 3 vacant cities
   return False


def getPlotList(cyCity):
   """Check all adjacent plots and return a list of CyPlot instances"""

   lPlots = list()
   iCityX, iCityY = cyCity.getX(), cyCity.getY()
   for iX in range(iCityX - 1, iCityX + 2):
      for iY in range(iCityY - 1, iCityY + 2):
         cyPlot = cyMap.plot(iX, iY)
         if cyPlot.isWater() or cyPlot.isPeak() or cyPlot.isCity():
            continue
         lPlots.append(cyPlot)
   return lPlots


def spawnRebels(cyCity, lPlots):
   """Spawn rebel units on surrounding random plots"""

   iNumPlots = len(lPlots)
   if iNumPlots:
      iNumRebels = max(2, cyCity.getCulturePercentAnger() - cyCity.getMilitaryHappinessUnits())
      # Illustration: Rebels are those foreign people which are not "contented" by military means

      eUnitType = cyCity.getConscriptUnit()
      while iNumRebels:
         iNumRebels -= 1
         cyPlot = lPlots[cyGame.getSorenRandNum(iNumPlots, "spawn")]
         pyRebel = pyBarbarian.initUnit(eUnitType, cyPlot.getX(), cyPlot.getY())
         if iNumRebels % 2:
            pyRebel.setHasPromotion(eGuerilla, True)
 
So they can be independently loaded?
Sure. It works like this:

Anything you import to a module is callable from the imported module with the module name. You can also only import selected portions of a module with from. And you can import everything with * so its available in the importing module directly, without calling on the name. This is in fact how the boost python stuff works:
Code:
from CvPythonExtensions import *
Otherwise you'd have to use the CvPythonExtensions name any time you wanna access the Python API.

Still there is a tiny problem: after pause (new start of CIV4 & load savegame) the global declaration seems forgotten, so it has to be in onGameStart & onLoadGame (!?!) ...
Yeah, I solved this myself with a function definition:
Code:
def importCustomModules():
   if CyGame().isFinalInitialized():
      global MyModule
      import MyModule
I just added this at the bottom of CvEventManager and added function calls to onGameStart() and onLoadGame(). But I also put a function call at the very bottom of CvEventManager, because then it also works whenever Python is reloaded during game-play. (So that you don't have to reload the game every time.) :D

More general: Have I introduced the problem by using "real code" in onGameStart() instead of just popping up a simple message?? Would it work then with just the 'normal' import at the beginning of EventMgr?
This is what I've managed to gather myself:

When the game initializes it firstly reads the C++ code, then it imports the Python, and only then does it parse the XML. CvEventManager is thus imported before any XML is read, so any method invocation of CyGlobalContext.getInfoTypeForString() will return -1/None. Because there basically is no info types to match with the string supplied.

The problem with importing your own modules at the __main__ level of CvEventManager is precisely that the all code at module level will be executed when the module is imported. Its no problem with assignment of values, but you can't refer to any XML settings.

If you already know the integer values representing all the stuff in your mod you don't actually need to refer to any XML. Then you can just define any constants as integers.

My belly would say "range(-4, -2)", but I do not understand _how_ the creation of the list goes ... I would combine (additiv) every element of the first range with every of the second, but with different results ... please explain.
It turns out I did it wrong - it should be:
Code:
range(-4, -1) + range(2, 5)
The first range() results in the list [-4, -3, -2] and the second one [2, 3, 4]. This because enumeration is always done from zero and up. It takes awhile to become accustomed to it though. So this creates the list [0-2]:
Code:
range(3)
Because it will enumerate from zero to the third entry. This creates the list [1-3]:
Code:
range(1,4)
Because the enumeration starts with 1 and goes to the fourth entry - counted from zero.

But as I said - its not always easy to figure these things out. I thought I tested my range() in IDLE (the default Python GUI) but I guess not. :rolleyes:

Another point is that by using the intended list for the loops only the corner blocks of the 'Sudoku' will be accessed. The central block is missing on purpose, but the blocks on the edges?
Why not add:
Code:
def revealPlots(iUnitX, iUnitY):
   for iX in lRange:
      for iY in lRange:
[B]         if abs(iX) == 4 == abs(iY):
            break[/B]
         cyPlot = cyMap.plot(iUnitX + iX, iUnitY + iY)
         if cyPlot.isRevealed(eHumanTeam, False):
            break
         iDistance = max(abs(iX), abs(iY))
         if checkPlot(cyPlot, iDistance):
            cyPlot.setRevealed(eHumanTeam, True, False, -1)
(If this is valid I actually think I've cracked a nut I've been pondering some time myself. :king:)
I would like to compare at Run-Time how much slower my more primitive code is ...
There is a way to time this, of course, but I wouldn't be at all surprised if it turned out that the "primitive" code was faster. Sure, the code itself takes up more place in memory, but there are less calculations to be made, right? But I'm no computer scientist. I just try to avoid 1. repetition of code and 2. unnecessary looping of code when possible. I often find myself avoiding 1 by risking 2... :rolleyes:
Code:
   if iDistance > 3 and (cyPlot.isPeak() or eFeature == eOasis):
      return True
   if iDistance > 2 and (cyPlot.isHills() or cyPlot.isLake()):
      return True
   if cyPlot.isRiver() or eFeature == eForest:
just looking at it, methinks the > should be < and applied bottom up (inner rings) instead top down (outer rings) [in order to give the desired results on the map, of course you can redefine them too]. Or have I to look close again? that's not easy, cute code makes me dizzy.
I also think I messed it up. :D But I guess the values 3 and 2 should really be 2 and 1. Or something, logic makes me dizzy. :crazyeye:

Last night I had the tangent idea, usually you write the correct code and add then some mistakes, so you can see, whether the peoples just copy&paste around with (nearly) random results? No, YOU would never do that, at least not admit, wouldn't you? :D
:lol: I post untested sample code for others when knowing better... :p
&#65279;
So now the current code looks like this:
I'll get back to you with a analysis later. :)
 
Ok, I looked at your code.
Code:
eHuman = cyGame.getActivePlayer()
cyPlayer = gc.getPlayer(eHuman)       # should be named cyPlayerHuman?
iTeam = cyPlayer.getTeam()            # return value might be eTeam, but is int
The i and e variable name prefixes are basically interchangeable. Use whatever you like, because most of them are integers anyway. But its still good to know why you are doing something, like when you made the conscious decision to opted for the cy prefix in favor of p for class instances.

Instead of using pop-ups for your player messages you might consider using in-game text messages. Look into the CyInterface class for this. (Tip: you can get away with using empty strings for skipping any sound effects and the like.)

It turns out that I messed up the reveal map code for you. :p This is instead what I did to create a city radius with a custom variant of the Rebels mod-comp:
Spoiler :
Code:
lBFC = list()
for iX in range(-2, 3):
  for iY in range(-2, 3):
    if abs(iX) == 2 == abs(iY) or iX == 0 == iY: continue
    lBFC.append((iX, iY))

...

def getPlotList(iX, iY):
  lPlots = list()
  for pPlot in list(Map.plot(iX + tCoords[0], iY + tCoords[1]) for tCoords in lBFC):
    if ( pPlot.isWater()
         or pPlot.isPeak()
         or pPlot.isCity()
         or not pPlot.isOwned() ):
      continue
    print (pPlot.getX(), pPlot.getY())
    lPlots.append(pPlot)
  return lPlots
You could probably adapt this for your own needs.

This is by the way what the custom Rebels module looks like:
Spoiler :
Code:
### Rebels mod component adapted to Jamie's Rome Mod, by Baldyr

from CivPlayer import *
from Popup import PyPopup

# constants

pHumanCiv = instance(Game.getActivePlayer())
pBarbarianCiv = CivPlayer(gc.getBARBARIAN_PLAYER())
iRandomSeed = iNumPlayers + Game.getSorenRandNum(iNumPlayers, "seed")

# settings

tCivilized = ( "Carthage", "Greece", "Rome", "Egypt" )
rebellionMessage = "A %s rebellion has broken out in the %s city of %s!"
popupHeader = "Jamie's Rome Scenario"
popupMessage = "This mod includes the Rebels Python mod component. It spawns barbarian units around \
cities with foreign citizens that are currently in disorder. The unit type and the number of units \
adapts to the makeup of the city garrison and the outcome will vary with the circumstances.\n\n\
You have been warned!"

# setup arrays

lCivilized = list()
for pCivPlayer in CivPlayer.Civilizations.itervalues():
  if pCivPlayer.get(CyPlayer).getCivilizationShortDescription(0) in tCivilized:
    lCivilized.append(pCivPlayer)
    
lBFC = list()
for iX in range(-2, 3):
  for iY in range(-2, 3):
    if abs(iX) == 2 == abs(iY) or iX == 0 == iY: continue
    lBFC.append((iX, iY))
      
# functions

def showPopup():
  modPopup = PyPopup()
  modPopup.setHeaderString(popupHeader)
  modPopup.setBodyString(popupMessage)
  modPopup.launch()

def initEvent(iTurn):
  pCurrentPlayer = instance((iTurn + iRandomSeed) % iNumPlayers)
  if not pCurrentPlayer.get(CyPlayer).isAlive(): return
  for pCity in getCities(pCurrentPlayer):
    pOriginalOwner = instance(pCity.getOriginalOwner())
    iForeigners = max(1, pCity.getCulturePercentAnger() / 100)
    if checkCity(pCity, pOriginalOwner, pCurrentPlayer, iForeigners):
      pRebelCiv = getRebelCiv(pOriginalOwner, pCurrentPlayer)
      spawnRebels(pCity, pOriginalOwner, pCurrentPlayer, pRebelCiv, iForeigners)
      return

def getCities(pCivPlayer):
  lCityList = pCivPlayer.get(PyPlayer).getCityList()
  lCityList.reverse()
  for pCity in list(city.GetCy() for city in lCityList):
    yield pCity

def getRebelCiv(pOriginalOwner, pCurrentOwner):
  if pOriginalOwner.get(CyPlayer).isAlive() and pOriginalOwner != pCurrentOwner:
    return pOriginalOwner
  else:
    return pBarbarianCiv
        
def checkCity(pCity, pOriginalOwner, pCurrentOwner, iForeigners):
  return ( pCity.isDisorder()
           and not pCity.isNeverLost()
           and ( pOriginalOwner == Civ("Brittania")
                 or not pCurrentOwner in lCivilized
                 or Game.getSorenRandNum(2, "rebels") ) )

def spawnRebels(pCity, pOriginalOwner, pCurrentOwner, pRebelCiv, iForeigners):
  lPlots = getPlotList(pCity.getX(), pCity.getY())
  iNumPlots = len(lPlots)
  if iNumPlots:
    iNumRebels = max(iForeigners, pCity.unhappyLevel(0) - pCity.getMilitaryHappinessUnits())
    eUnitType = pCity.getConscriptUnit()
    while iNumRebels:
      pPlot = lPlots[Game.getSorenRandNum(iNumPlots, "spawn")]
      pRebelCiv.get(PyPlayer).initUnit(eUnitType, pPlot.getX(), pPlot.getY())
      iNumRebels -= 1
    displayMessage(pCity, pOriginalOwner, pCurrentOwner, pRebelCiv)

def getPlotList(iX, iY):
  lPlots = list()
  for pPlot in list(Map.plot(iX + tCoords[0], iY + tCoords[1]) for tCoords in lBFC):
    if ( pPlot.isWater()
         or pPlot.isPeak()
         or pPlot.isCity()
         or not pPlot.isOwned() ):
      continue
    lPlots.append(pPlot)
  return lPlots
  
def displayMessage(pCity, pOriginalOwner, pCurrentOwner, pRebelCiv):
  if pCurrentOwner == pHumanCiv or pHumanCiv.get(CyTeam).isHasMet(pCurrentOwner.get(teamID)):
    tNames = getNames(pCity, pOriginalOwner, pCurrentOwner, pRebelCiv)
    Interface.addImmediateMessage(rebellionMessage % tNames, "")

def getNames(pCity, pOriginalOwner, pCurrentOwner, pRebelCiv):
  if pCurrentOwner == pOriginalOwner or pRebelCiv == pBarbarianCiv:
    ethnicName = "ethnic"
  else:
    ethnicName = pOriginalOwner.get(CyPlayer).getCivilizationAdjective(0)
  ownerName = pCurrentOwner.get(CyPlayer).getCivilizationAdjective(0)
  cityName = pCity.getName()
  return (ethnicName, ownerName, cityName)
Note that I'm importing the CivPlayer module at the top of the module. This is actually a pet project of mine, but the code is only applicable to scenario type mods. It accounts for many of the unfamiliar things happening with the code. It works because I'm importing the whole module in the same way it in turn is importing CvPythonExtensions. So I'm basically adding to the default Python API - which is available in the Rebels module through the CivPlayer module.
 
Code:
def importCustomModules():
   if CyGame().isFinalInitialized():
      global MyModule
      import MyModule
Ha! This one is great too!!
If you already know the integer values representing all the stuff in your mod you don't actually need to refer to any XML. Then you can just define any constants as integers.
You know what? I had started 'quick & dirty' making on paper a list of bonustype numbers (hence the prints in the first program version), but halfway through running around with the nomads, seeking new boni, I stopped, because I thought, it is bad style, of course it works, but I don't like do that. In principle it invites heavy compatibility problems - for CIV4 there will be no more patch, so no potential future compatibility problem, just 'in principle'.
The same are the fix unit numbers I used, of course the settler is created first, the anchor of chained objects, so it is unit(0). Followed by unit(1), which can't be assigned until you declare which civilization you wanna play, ie. you choose implicit a warrior or a scout. unit(2) & unit(3) are the 2 workers created in onStartGame(). I don't believe any patch would change _that_ ... still it looks poor.
But as I said - its not always easy to figure these things out ...
... especially if the example uses incorrect values :p But knowing the solution, I think, I SHOULD have been able to figure it out by myself :blush:
I thought I tested my range() in IDLE (the default Python GUI) but I guess not
Pah, testing is for softies! No cisc, no fun.:lol:
Code:
if abs(iX) == 4 == abs(iY):
First I thought: well, if it is syntactical ok (from left to right ... !?), and the boolean (abs(iX) == 4) isn't a type mismatch with the integer (abs(iY)), methinks it is never true - with iX, iY € of [-4, -3, -2, 2, 3, 4]
I have to get used to this syntax ... is it valid then to write:
a = 5 = b
or
a, b = 5
4 = x, y
I guess the values 3 and 2 should really be 2 and 1. Or something, logic makes me dizzy.
I figured out 3 and 4, distance seems to be far away!
(I'd like to copy just your cute code, but would have to make long distance calls in case of changing anything, in order to discuss the consequences.)
The i and e variable name prefixes are basically interchangeable.
Yes, of course, in principle names need no prefix at all. Besides that the prefixes shouldn't be interchangeable, they should clearly and correct describe the type of the variable (as the variables ie. return values and arguments of given methods should follow a clear and consistent concept!).
I am used to a language (C) which requires explicit variable declarations, so (just in case the type is easily to look up and) usually no type pre- / postfix is given in the name. In this Python (like in BASIC) variables are implicit declared (and defined), this is convenient, sure! I don't like it. Because if a variable is implicit declared as return value of a method of a class, which you only can guess ... pyXXX, cyXXX ... cyGame, cyPlayer, cyUnit ... you are in for a (long) search ... all this consumes (for the beginner) much more time than is saved for not to have explicit declarations.
Use whatever you like, because most of them are integers anyway.
I have seen awful code, done by generations of programmers; if 'it' grows over a long time this is probably unavoidable ... I am not sure, that this was necessary for the CIV4 python interface ... it shows inferior 'whatever'.
In the case eTeam I expected it to be an enumeration type as in eHuman ... but the calculateNatureYield method told me, what I was supplying (the return value of getTeam) to be an INT despite it is expecting an enumeration type ... so I REFUSE to name the return value of getTeam 'eTeam' :D (revenge of the weak and helpless)
Seriously, if it doesn't matter that much, why not ALL int, so you can at least use the running variable of loops as index into arrays without fiddling around with typecasts or even worse functioncalls just to satisfy the typeo-needs...
But its still good to know why you are doing something
absolutely, most of the time!!!
Instead of using pop-ups for your player messages you might consider using in-game text messages.
You mean Interface.addImmediateMessage()??
Just to be sure, in-game text messages are those appearing on top of the map, telling something like "while camping in the highlands your warriors killed a barbarian lamb", and disappearing after a while??
Sounds great, if can I choose anywhere a bigger font and/or a longer display time for them ... usually I miss some of them ... :cry:
(this is no joke, my visual performance is not so good)
Code:
if ... iX == 0 == iY: continue
...
         or pPlot.isCity()
seems to be redundant
Code:
    if ( pPlot.isWater()
         or pPlot.isPeak()
I supposed isWater() means Ocean, Coast, Lake (maybe even River).
But my barbarian wolves ignored isWater() and are generated on Ocean & Coast (onshore next turn), the same with peaks, no problems with generation there. In my test I had no event on Lake, so I don't know whether it works there.
Code:
         or not pPlot.isOwned() ):
means what? Plot is neutral, not belonging to a civ?
This is by the way what the custom Rebels module looks like:
I think it is good to see the similar rebels code in a slightly different context, so anybody can try to adapt it more easily to his own ideas.

Especially I like the aspect of generating with priority the rebels belonging to the original civ. I thought about that, but hesitated to ask for this.
Code:
def checkCity(pCity, pOriginalOwner, pCurrentOwner, iForeigners):
iForeigners isn't used right now
Code:
max(iForeigners, pCity.unhappyLevel(0) - pCity.getMilitaryHappinessUnits())
pCity.unhappyLevel(0) sounds interesting, what is it? The effective result of happiness calculation (people not working) or the number of red faces before meeting the yellow faces or??
 
You know what? I had started 'quick & dirty' making on paper a list of bonustype numbers (hence the prints in the first program version), but halfway through running around with the nomads, seeking new boni, I stopped, because I thought, it is bad style, of course it works, but I don't like do that. In principle it invites heavy compatibility problems - for CIV4 there will be no more patch, so no potential future compatibility problem, just 'in principle'.
The same are the fix unit numbers I used, of course the settler is created first, the anchor of chained objects, so it is unit(0). Followed by unit(1), which can't be assigned until you declare which civilization you wanna play, ie. you choose implicit a warrior or a scout. unit(2) & unit(3) are the 2 workers created in onStartGame(). I don't believe any patch would change _that_ ... still it looks poor.
While we don't need to worry about upcoming patches any more, modders are copy-pasting code left and right. So in order to promote a general atmosphere of creativity around these parts we should probably make our mod-components as pluggable as possible. :D

Code:
if abs(iX) == 4 == abs(iY):
First I thought: well, if it is syntactical ok (from left to right ... !?), and the boolean (abs(iX) == 4) isn't a type mismatch with the integer (abs(iY)), methinks it is never true - with iX, iY € of [-4, -3, -2, 2, 3, 4]
I have to get used to this syntax ... is it valid then to write:
a = 5 = b
or
a, b = 5
4 = x, y
The built-in abs() function turns a negative integer value into a absolute value. The chaining of operands and operators is actually very convenient but can also be somewhat confusing at times... :rolleyes: So the if statement could have looked like this also:
Code:
if abs(iX) == 4 and abs(iY) == 4:
or even:
Code:
if (iX == 4 or iX == -4) and (iY == 4 or iY == -4):
Did I mention that the shorthand was more convenient? :D

The other stuff you exemplified is sometimes referred to as tuple assignment. Did you mean to ask about operators instead of assignment? :confused:

Yes, of course, in principle names need no prefix at all. Besides that the prefixes shouldn't be interchangeable, they should clearly and correct describe the type of the variable (as the variables ie. return values and arguments of given methods should follow a clear and consistent concept!).
I am used to a language (C) which requires explicit variable declarations, so (just in case the type is easily to look up and) usually no type pre- / postfix is given in the name. In this Python (like in BASIC) variables are implicit declared (and defined), this is convenient, sure! I don't like it. Because if a variable is implicit declared as return value of a method of a class, which you only can guess ... pyXXX, cyXXX ... cyGame, cyPlayer, cyUnit ... you are in for a (long) search ... all this consumes (for the beginner) much more time than is saved for not to have explicit declarations.
I personally prefer to use the e prefix for everything that is referring to game related stuff (enumerated types), even though they are integers. Most of the time.

But it would make as much sense to use the i prefix for the majority of the stuff, and only resort to using the actual enumerated types like UnitAITypes or YieldTypes with the e prefix.

Most of the time the preexisting code uses whatever. :rolleyes: This can be very confusing for a beginner.

You mean Interface.addImmediateMessage()??
Just to be sure, in-game text messages are those appearing on top of the map, telling something like "while camping in the highlands your warriors killed a barbarian lamb", and disappearing after a while??
Sounds great, if can I choose anywhere a bigger font and/or a longer display time for them ... usually I miss some of them ... :cry:
(this is no joke, my visual performance is not so good)
addImmediateMessage() is the poor-mans choice. addMessage() is where all the power lies, like with priority and length of message, or with audio-visual aids like message color. But it takes a while to figure out... :p

A nice helper function that wraps up all the features you need is of course always a good idea! :D

Code:
    if ( pPlot.isWater()
         or pPlot.isPeak()
I supposed isWater() means Ocean, Coast, Lake (maybe even River).
But my barbarian wolves ignored isWater() and are generated on Ocean & Coast (onshore next turn), the same with peaks, no problems with generation there. In my test I had no event on Lake, so I don't know whether it works there.
I never had CyPlot.isWater() or CyPlot.isPeak() fail me, so there must be something else wrong with your logic. Like a "not" command that should or should not be there.

Code:
         or not pPlot.isOwned() ):
means what? Plot is neutral, not belonging to a civ?
Yeah, exactly. CyPlot.getOwner() would return -1 on such a plot, by the way.

Code:
max(iForeigners, pCity.unhappyLevel(0) - pCity.getMilitaryHappinessUnits())
pCity.unhappyLevel(0) sounds interesting, what is it? The effective result of happiness calculation (people not working) or the number of red faces before meeting the yellow faces or??
The max() function returns the bigger value, plain and simple. But it turns out that iForeigners needs to be divided by 100... :rolleyes: CyCity.unhappyLevel() returns the number of unhappy citizens - before their numbers are counted against the happy citizens. So right again. (The zero parameter has something do with bonus happiness that can be counted into the calculation, or something.)
 
Code:
if abs(iX) == 4 == abs(iY):
First I thought: well, if it is syntactical ok (from left to right ... !?), and the boolean (abs(iX) == 4) isn't a type mismatch with the integer (abs(iY)), methinks it is never true - with iX, iY € of [-4, -3, -2, 2, 3, 4]
I have to get used to this syntax ... is it valid then to write:
a = 5 = b
or
a, b = 5
4 = x, y

The built-in abs() function turns a negative integer value into a absolute value. The chaining of operands and operators is actually very convenient but can also be somewhat confusing at times... :rolleyes: So the if statement could have looked like this also:
Code:
if abs(iX) == 4 and abs(iY) == 4:
or even:
Code:
if (iX == 4 or iX == -4) and (iY == 4 or iY == -4):
Did I mention that the shorthand was more convenient? :D

The other stuff you exemplified is sometimes referred to as tuple assignment. Did you mean to ask about operators instead of assignment? :confused:

I haven't read most of this long discussion, but this caught my eye. Sorry if it's unrelated or if I've misunderstood anything :mischief:

@civjo -

You're thinking in C. It's true that in C the expression
Code:
a == 4 == b
interprets as (for example, not sure of the order):
Code:
(a == 4) == b
which actually compares a boolean value to an integer one.

In python, it is not a step-by-step parsing, so these two phrases are NOT the same.

For example:
Code:
a = 4
b = 4

a == 4 == b  --> Returns True
(a == 4) == b  --> Returns False

Code:
a = 4
b = True

a == 4 == b  --> Returns False
(a == 4) == b  --> Returns True

Hope it made sense :)
 
First of all, amazing tutorial, Baldyr! I really appreciate the time you spent explaining all of this, and thought your Rebels script was pretty neat. I'm not new to programming, but I haven't had much practice with OOP. I was able to follow your tutorial without too many problems and got your example working fine (which I wrote from scratch as I followed along, no Copy+Pasting.)

I've started tinkering around, and I'm stuck (surprise!) Before you ask, yes I read the whole tutorial, including several comments. I've gotten some of my tinkering to work, others not so much.

Right now, I'm trying to figure how to adjust a unit's current moves left (specifically to 0 after an attack.) After digging around in the API and the game files I found these:

VOID changeMoves(INT iChange)
CyUnit getSelectedUnit()

In the CvEventManager.py I found:
Code:
def onCombatResult(self, argsList):
		'Combat Result'
and
Code:
def onCombatLogHit(self, argsList):
		'Combat Message'
		global gCombatMessages, gCombatLog
		genericArgs = argsList[0][0]
		cdAttacker = genericArgs[0]
		cdDefender = genericArgs[1]
		iIsAttacker = genericArgs[2]
		iDamage = genericArgs[3]

I feel like I'm holding all of the correct pieces to this puzzle, I just don't know how to stick 'em together. I know the changeMoves() function is a void, so it acts on the Movement attribute of the Unit entity I'm trying to modify. What's confusing me is the arguments. It only wants how much change I want as an integer. I would expect a function like that to also ask which unit. If it did, I could try something like this:
Code:
cyUnit = CyUnit()
eUnit = cyUnit.getSelectedUnit()
cyUnit.changeMoves(0, eUnit)

However, because it's only taking the int as an arg, I'm stumped! I tried something like this:
Code:
cyUnit = CyUnit()
eUnit = cyUnit.getSelectedUnit()
cyUnit = eUnit
cyUnit.changeMoves(0)

Of course it doesn't work, and I felt silly typing it, because I know there's some funky logic going on there.

cyUnit = CyUnit() is a constructor that creates an instance of the CyUnit() class.
Then I'm trying to assign the output from a method in that class back into the object.

I've been learning a lot today, and I'm not asking you to just hand me the answer. Anyway someone can point me in the right direction?

In any case, all of your help has been really useful, and the time you spent contributing this guide is not un-appreciated! Thanks in advance for any advice rendered :)
 
Back
Top Bottom