How to make a Python mod

I figured some things out!

After doing some google searching for the phrase "civ iv changemoves", I found a Tutorial that discussed modifying CvMainInterface.py. In there is a function called "updateSelectionButtons" that has the following:
Code:
global SELECTION_BUTTON_COLUMNS
		global MAX_SELECTION_BUTTONS
		global g_pSelectedUnit

		screen = CyGInterfaceScreen( "MainInterface", CvScreenEnums.MAIN_INTERFACE )
		
		pHeadSelectedCity = CyInterface().getHeadSelectedCity()
		pHeadSelectedUnit = CyInterface().getHeadSelectedUnit()
Using this as an example, I came up with:
Code:
def changeMoves():
	global g_pUnit
	
	# Test
	g_pUnit = CyInterface().getHeadSelectedUnit()
	g_pUnit.changeMoves(1)

I still don't understand other things though...

For example, when I run this, the currently selected unit's selection circle goes from green to yellow, showing the unit doesn't have 100% movement, but in the unit stats in the bottom-left, it still does. In other words, I have a Scout selected, he has 2/2 moves, I run the code, he still has 2/2 moves, but now his ring is yellow.

So, thinking it might be a percentage based thing, I increased the value to 49, and got the same effect. I increase the iChange value to 50, and it turns yellow. Just for kicks, I do it one more time, and it drops to 1/2. Do it a third time, and it drops to 0/2, and the ring turns red. So iChange = 50 x 3 = 0/2 moves? I try iChange = 150, and voila! It drops from 2/2 to 0/2. Well, that's solved...kind of. I tested some more values and here was the outcome:

125 = 0/2, red ring
101 = 1/2, yellow ring
110 = 1/2, yellow ring
120 = 0/2, red ring
115 = 1/2, yellow ring

So apparently, 120 move is required to drain -2 moves. I wonder if iChange = 60 per move? Will have to test with higher move units.

Also, I now need to insert this inside an Event that occurs after a unit has attacked, and it only needs to affect the attacker. What I'm going for here is I want all of the unit's moves to be consumed if it attacks, no matter how many moves it may have left.

I have the following 3 events to work with (I think?):

onCombatResult(), 'Combat Result'
onCombatLogCalc(), 'Combat Result'
onCombatLogHit(), 'Combat Message'

In the first one of those, the args are used for pWinner and pLoser. Nothing else in this function definition that I see indicates who the attacker is and who the defender is. Btw, this function has the baffling following conditional:
Code:
if playerX and playerX and unitX and playerY:
Which displays the message about one unit defeating another. How the heck...?

If playerX = true, and playerX = true (twice?), and unitX = true and playerY = true, a unit got defeated? :crazyeye:

Anyway, the second event looks much more promising. As from my post above, it has:
Code:
def onCombatLogHit(self, argsList):
		'Combat Message'
		global gCombatMessages, gCombatLog
		genericArgs = argsList[0][0]
		cdAttacker = genericArgs[0]
		cdDefender = genericArgs[1]
		iIsAttacker = genericArgs[2]

So cdAttacker seems to be the unit I'm interested in, and I should try to figure out how to reduce all of its moves to 0. What is a 'cd' anyway? I know i = integer, b = boolean, p = pointer, l = list, but I haven't seen 'cd' until I looked at this function.

Again, any help is appreciated. In the meantime, I'm going to continue beating my face on this, and maybe I'll come back with another breakthrough and more questions. :P
 
Malganis, I'll do my best to shed whatever light I can on these issues.

Unit moves are calculated from a increment of 12 or 60 or something. (It should be defined in the GlobalDefines.xml file.) So you actually figured this out on your own. :goodjob:

But, the easiest way to set movement to zero is probably to use CyUnit.finishMoves(). :mischief:

The variables with the cd prefix seem to be CombatDetails instances. Look it up in the API, but I don't think you actually need these.

Instead, try to find some game event that gives you CyUnit instances, like combatResult. Then you might be able to use CyUnit.isAttacking() and/or CyUnit.isDefending() to determine which unit you need to use.

The rest you should be able to figure out yourself. :D
 
Thanks for the response, Baldyr. I'm at work, so relying on memory here.

Unit moves are calculated from a increment of 12 or 60 or something. (It should be defined in the GlobalDefines.xml file.) So you actually figured this out on your own.

After more experimentation, I nailed this down to iChange = 60 per 1 move.

But, the easiest way to set movement to zero is probably to use CyUnit.finishMoves().

I became very familiar with the Civ4 Python API and I was actually using "Moves" as my criteria for Ctrl+F, which is how I found CyUnit.changeMoves(). Not sure how I missed this one, but it's a lot more precise, so I'll be going back and updating that.

Instead, try to find some game event that gives you CyUnit instances, like combatResult. Then you might be able to use CyUnit.isAttacking() and/or CyUnit.isDefending() to determine which unit you need to use.

This is spot on, and agrees with what I found in my own tinkering. I tried using: cdAttacker.changeMoves(60) in the combatLogHit() and combatLogCalc() functions which both use cdAttacker and cdDefender as part of their argsList. Both produced errors. However, when I used pWinner.changeMoves(60), it worked like a charm. I now understand why.

What I don't understand is where to find this information. The function definition is right there, but the only arguments are (self, argsList). I understand what (self) is, and I see what the (argsList) is used for inside the function definition, but I don't see where the argsList itself is declared/defined/whatever. How do you know what you can feed these event functions and what you can't? Shouldn't there be a:

pWinner = CyUnit()
pLoswer = CyUnit()

somewhere above in the code? I guess I'm asking, where are the constructors?

My next goal is how to figure out how to implement 1 unit per tile (1 UPT) like Civ5 has. I saw someone already created a mod that does this for BtS, but I'm still on "Vanilla." Now I know all the cool kids are using BtS and it's the latest and greatest, but I *just* returned to the game after a long break, and Warlords existed when I quit, but BtS didn't. Even if I had BtS, I'd like to figure how to implement my own 1 UPT mod for practice and understanding gained.

Thanks again for all your help, Baldyr. I'm always amazed when individuals use their own free time to help others. Much appreciated :goodjob:

Edit: Say, while you're still active in this thread, would you mind if I bugged you once in awhile if I got stuck doing the 1 UPT mod? Either in PMs, in another thread, or here (but I don't want to "dirty" up your nice tutorial more than I have.) If not, I understand you're busy. I just understand your explanations and I have your attention, so.. :p I'd take you out to lunch if I could, hehe.
 
Hmm, I don't think I'm going to pursue 1 UPT afterall. I spent some time searching the forums and saw that others have tried with mixed success. The point that keeps coming back is that the AI is not designed for that playstyle and is too easily beaten.
 
I became very familiar with the Civ4 Python API and I was actually using "Moves" as my criteria for Ctrl+F, which is how I found CyUnit.changeMoves(). Not sure how I missed this one, but it's a lot more precise, so I'll be going back and updating that.
Don't count on everything in the BtS API to be available with the Warlords API. In fact, BtS expanded the API a great deal, so you probably should get BtS.

What I don't understand is where to find this information. The function definition is right there, but the only arguments are (self, argsList). I understand what (self) is, and I see what the (argsList) is used for inside the function definition, but I don't see where the argsList itself is declared/defined/whatever. How do you know what you can feed these event functions and what you can't? Shouldn't there be a:

pWinner = CyUnit()
pLoswer = CyUnit()

somewhere above in the code? I guess I'm asking, where are the constructors?
The thing to understand about CivIV Python is that the API just mirroring the C++ code in the SDK. So all classes are just used to augment C++ classes (or modules, I'm not fluent in C++). This means that the constructors are in the SDK. (A new unit is instead created with the method CyPlayer.initUnit().)

Regarding the argsLists - they are data structures passed along from the SDK. Look up CvGameCoreDLL\CvDllPythonEvents.h for a spoiler on what values are hidden inside those arguments.

Hmm, I don't think I'm going to pursue 1 UPT afterall. I spent some time searching the forums and saw that others have tried with mixed success. The point that keeps coming back is that the AI is not designed for that playstyle and is too easily beaten.
Yeah, that was going to be my advice also. :p Its not a Python job in any case.
 
Well, I found this: http://forums.civfanatics.com/showthread.php?t=245703

Which looks great, and seems even better than 1 UPT. Someone else asked about the AI, and he claims it just treats "full" tiles as impassable, and doesn't have a drastic effect on it.

Even though I don't have BtS, I went ahead and downloaded it to take a peek. This happens to be extremely well documented and polished, but also very complex. I see it also requires SDK modding, which I wouldn't mind too much. My first formal language happens to be C++.

Unfortunately, I can tell this became possible because of the extended functionality offered by BtS. This mod was also last updated for 3.17, and I think the latest version of BtS is 3.19. Not sure what conflicts exist due to this.

Whether I try to create a version of this myself, or get his working in the latest version of BtS, I have to pick up BtS either way. I'm a big enough fan of Civ4 that this might be worth the time investment and harsh learning curve that comes with it.

Stack vs. Stack combat and monotonous clicking with large armies is always the same thing that inevitably burns me out on the game. If I could even get a flat tile limit for units, say 3 in non-cities, and 5-7 (maybe depending on population) in cities, I'd be happy. Doesn't have to be so fancy as to come with all the modifiers his mod has.

I have some thinking to do and decisions to make! :D
 
I'd say go for it. If you can handle C++ you probably don't even need to bother with Python. :p
 
@Asaf yes, I am quite fluent in C, but lapsing around in python ... (usual order is left to right)
your explanation was perfect! Thanks.

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.
Oh, the "test" was in the early phase when I had the gc constants on module level or inside the functions copying there and back again, fighting problems with bonus yields because of the sequence of module initialization you explained already ... obviously I goofed in that detail.
CyPlot.getOwner() would return -1 on such a plot
-1 = None in this case? or general?

I did some research & testing:
pCity.unhappyLevel()
is cute - but maybe dangerous ... in my test city of size 20 I had an unhappyLevel of 32 ... !!
---> 20: too overcrowded + 12: miss our motherland (while getCulturePercentAnger = 599, might be 12 = 2*599/100)
Do you have any idea about the upper limit of this variable?

angryPopulation() seems the same, but limited to the Population really existing
getPopulation() = angryPopulation() + getWorkingPopulation() is valid
INT getCulturePercentAnger()
seems to be in range(0, 600), but vanishes immediately when the Civ is killed.
So the inhabitants of my test city were 99% Spanish & 0% American (while getCulturePercentAnger = 599) and 100% American (while getCulturePercentAnger = 0) after Spain was gone (next turn).

I have second thoughts about the influence of military units on the iNumRebels ...
Your original approach was:
iNumRebels = max(1, pCity.getMilitaryHappinessUnits() / 4)
Ie. a positive influence.

Then I argued (Illustration: Rebels are the foreign people which are not "contented" by military means):
iNumRebels = max(2, cyCity.getCulturePercentAnger()/100 - cyCity.getMilitaryHappinessUnits())
[originally I missed the /100 ... so in my first test the modern armor, which conquered the artificial Spanish city and provoked the rebellion was attacked by 598 warriors ... and survived - you can imagine, how much I wished, that I had saved at least once]
(in Jamie's Rome Scenario)
iNumRebels = max( max(1,pCity.getCulturePercentAnger()/100), pCity.unhappyLevel(0)-pCity.getMilitaryHappinessUnits() )
In both the influence is negative, I followed the perception of the implemented game. Theme is probably intimidation: more military -> less (puplic shown) unhappiness.

Now I favor your original approach ... the theme is REBELLION: more military -> more unhappiness. The rebel mod shall not amplify the given effects, but add new! Also from a gameplay point of view it is more interesting: Add military to the city so the city state changes from unproductive to normal working, but on the other hand magnify the potential damages of rebellion.

I wasn't aware, that isDisorder()==True correlates with getCulturePercentAnger==599 and the statement of "Vive la resistance!" after conquest and not the simple unrest one experience eg. from overpopulation!
From my point of view the few turns after conquest are too few ... I mean rebellions in test cases, ok, and in real playing one every 10 games??
After the 'resistance' is over, no more rebellion!!! even if the unhappyness is so strong that NOBODY is working, ALL are angry ... I don't like that.
Ok, ok, I am looking for opinions how a rebellion could or should work.

Btw, my correlating rebel part looks now like this:
if ( ( cyGame.getSorenRandNum(100, "rebels") < 67 )
......and ( cyCity.isDisorder()
.............or ( (cyCity.getWorkingPopulation()<cyCity.angryPopulat ion(0)) and not cyCity.isNeverLost() ) ) ):
[...]
iNumRebels = 1 + pCity.getPopulation()/8 + pCity.getMilitaryHappinessUnits()/2 + max(1, pCity.getCulturePercentAnger()/199)


[edit: cyCity.getWorkingPopulation()==0 would be harder, cyCity.angryPopulat ion(0)>0 would be softer conditions
getReligionPercentAnger(), getWarWearinessPercentAnger() could be used too]
 
-1 = None in this case? or general?
I don't actually know. :p

I did some research & testing:
is cute - but maybe dangerous ... in my test city of size 20 I had an unhappyLevel of 32 ... !!
---> 20: too overcrowded + 12: miss our motherland (while getCulturePercentAnger = 599, might be 12 = 2*599/100)
Do you have any idea about the upper limit of this variable?
I don't think there is a upper limit (you could check the C++ code in the SDK to find out for sure) but you can of course limit it yourself:
Code:
min(pCity.getPopulation(), pCity.getCulturePercentAnger / 100)

I have second thoughts about the influence of military units on the iNumRebels ...

Your original approach was:
iNumRebels = max(1, pCity.getMilitaryHappinessUnits() / 4)
Ie. a positive influence.
Its actually not my own design, but the modder who originally requested the code requested this. I believe that the idea was to have rebellions but not having to lose any cities because of them, or something. :rolleyes:

I wasn't aware, that isDisorder()==True correlates with getCulturePercentAnger==599 and the statement of "Vive la resistance!" after conquest and not the simple unrest one experience eg. from overpopulation!
From my point of view the few turns after conquest are too few ... I mean rebellions in test cases, ok, and in real playing one every 10 games??
After the 'resistance' is over, no more rebellion!!! even if the unhappyness is so strong that NOBODY is working, ALL are angry ... I don't like that.
Ok, ok, I am looking for opinions how a rebellion could or should work.
I actually didn't intend the Rebels code to fire on Occupation, but rather the kind of disorder that happens because of foreign culture. This is why the CyCity.getCulturePercentAnger() method was used as a condition. You can exclude the occupation disorder from the rebels code with:
Code:
not cyCity.isOccupation()

ps I posted some pretty complex code in this post. :D That was a lot of fun. I also intend to rework the Rebels mod-comp into a Object-Oriented setup with classes for different kinds of rebellions. See j_mie6's development thread for more on this...
 
I actually didn't intend the Rebels code to fire on Occupation, but rather the kind of disorder that happens because of foreign culture. This is why the CyCity.getCulturePercentAnger() method was used as a condition.
Yes, and another condition was isDisorder, which means Occupation, Anarchy ...
Are there other forms of "disorder"?

CulturePercentAnger & Disorder together lead to very few rebellions (and disable the nice barbarians for already killed civilizations introduced with the Rome version), so you are certainly on the 'right way':
I intend to rework the Rebels [...] for different kinds of rebellions.
I also changed my version in this direction. (source below)
&#65279;
different kinds of rebellions. See j_mie6's development thread for more on this...
Found just his teamcolor thread, could you please be more specific here?

[[[edit: updated attached source file]]]
 

Attachments

Its actually this thread. The new deal begins with post #270 and you're welcome to contribute to discussions. :goodjob: I actually think that you have some good ideas that would benefit Jamie's Rome mod.

Ok, browsed through your code and its actually an interesting script. :king: I'm not sure about the wolf thing but at least its... interesting. :p

Have you had the opportunity to test this code any?
 
browsed through your code and its actually an interesting script ...
thanks, almost I had removed weak points eg. the error message, when a nomad was killed (and missing during harvest) etc.
The final version now (above) has a greater variety of rebellions and rebels ... all in all this mod moves somewhat the limits of (my habit of) having all cities all the time exactly on the border of revolution and doesn't allow to conquer 'them' such quickly as used to (because you need some troops in the interior of the empire, which is usually void).
I'm not sure about the wolf thing but at least its... interesting.
Ha! the eye catcher for the Rome scenario fan worked :D Actually, right now I like this weird unit ... it cannot defend own cities, because it may not enter the own cultural borders :eek: (poor early dogies) ... while exploring it may not enter goody huts too :goodjob: But it has a sixth sense for unknown treasures! ... (boni, but of course without knowledge which).
Or did you mean the cute barbarian wolverines?? :lol: (they balance the given own)
Have you had the opportunity to test this code any?
Uhhmm, I removed the typos at runtime (my all time favorite: the missing : ) and had test cases to see that it works as intended ... so I had kind of tests, yes.
But on the other hand I let the prints active for fine tuning the parameters to the like of everybody who cares to play it. Some may want more resistance from the rivals, some not - even some may not like to invite additional troubles at all into their glorious empire building ...
 
I think I finaly understand these lessons! But I'm going to carry on reading untill I think I have away of doing mine... can you tell me how to define the game year (I know [if iYear == (enter value here)] but how do I return the iYear to me?
 
What was the question now? :confused:

If you assign a value (any value) to a name (any name) then that variable/constant points to that value. So you use that name to access the value its pointing to:
Code:
iValue = True
if iValue == True:
    print "iValue is True"
 
sorry I know that but what is the code for stating the game year?

you have
Code:
iYear=-3800

if iYear == (insert code here)
  print "iYear is 3800 bc"

but how do you return the actual turn of the game? something like getgameturn or something
 
You basically have to check with the API on these matters. Because nobody will be able to give you the answer on any imaginable question about Python methods. The CyGame class for instance is used to fetch many values related to the active game session. So there is CyGame.getGameTurn(), CyGame.getGameTurnYear() and other variations on the theme.

Learning to read the API - and to fully understand it - is critical for any kind of effective Python modding. So make sure to ask all the stupid questions... (I've actually contemplated writing a API tutorial.)

Note however that basing conditions in game years is tricky, for purely mathematical reasons. Even if there is a game turn that equates to the year AD 1000 on Normal speed, its not certain that there is one at Quick, Epic or Marathon speeds. So if you're making a scenario with one game speed option only, you're basically better off working with game turns as far as triggering Python scripts is concerned.
 
multiple speeds going on... I might have to do something about that.
 
There is a way to work with year integers for conditions though, but it basically requires some rather odd math. I made this function for antaine's Rome mod:
Spoiler :
Code:
def isDate(iDate):
    return Game.getTurnYear(Game.getGameTurn() +1) > iDate and Game.getGameTurnYear() <= iDate
For this to work the name Game needs to be defined as Game = CyGame(). Using "CyGame()" instead of "Game" of course works just the same.

It returns True if iDate should trigger on this particular turn, and False if it shouldn't. No matter what game speed and no matter what year the turn actually is.
 
interesting! what are Date and iDate defined as?
 
interesting! what are Date and iDate defined as?
There is no name Date, there is only isDate() and iDate. isDate() is the name of the funcion, and iDate is the argument (integer). If you pass the value 1560 to the function like isDate(1560) then the function will either return True or False. If its the first available game turn that is past the year AD 1560, then it will return True. Otherwise the value will be False.

So in other words; the function will return True only once in any given game. The return value is False on all other turns.
 
Back
Top Bottom