Great General Spare Troops (Python Modcomp)

Outitch

Chieftain
Joined
Jan 20, 2012
Messages
13
Location
Massy, France
"Great General Spare Troops" V1.0 python mod for BTS by Outitch

Hello everybody, this is my very first attempt to mod CIV ;).

This simple python modcomp consist in the following code, which should be inserted inside the function onUnitKilled() from the file CvEventManager.py in your mod folder.

Code:
	def onUnitKilled(self, argsList):
		'Unit Killed'
		unit, iAttacker = argsList
		player = PyPlayer(unit.getOwner())
		attacker = PyPlayer(iAttacker)
# Great general spare troops
		if unit.isHasPromotion(gc.getInfoTypeForString("PROMOTION_LEADER")):
			name = unit.getNameNoDesc()
			combat = unit.getUnitCombatType()
			pPlot = unit.plot()
			bGGalone = True
			bSameCombat = False
			for iUnit in range(pPlot.getNumUnits()):
				pUnit = pPlot.getUnit(iUnit)
				if pUnit.isPromotionValid(gc.getInfoTypeForString("PROMOTION_LEADER")) and pUnit.getOwner() == unit.getOwner() and pUnit.getDomainType() == unit.getDomainType() and (not pUnit.isHasPromotion(gc.getInfoTypeForString("PROMOTION_LEADER"))):
					if bGGalone:
						pNewGGunit = pUnit # We have found a new unit for our GG
						bGGalone = False # GG is no longer alone
						if pUnit.getUnitCombatType() == combat:
							bSameCombat = True # GG has found a new unit of same combat class
					elif pUnit.getUnitCombatType() == combat:
						if bSameCombat == False:
							pNewGGunit = pUnit # We have found a new unit of same combat class for our GG
							bSameCombat = True # GG has found a new unit of same combat class
						elif pUnit.baseCombatStr() > pNewGGunit.baseCombatStr(): 
							pNewGGunit = pUnit # We have found a stronger new unit of same combat class for our GG
						elif pUnit.baseCombatStr() == pNewGGunit.baseCombatStr() and pUnit.getExperience() > pNewGGunit.getExperience():
							pNewGGunit = pUnit # We have found a more experienced new unit of same combat class and same strength for our GG
					elif not bSameCombat: # If we have not yet found a new unit of the same combat class
						if pUnit.baseCombatStr() > pNewGGunit.baseCombatStr():
							pNewGGunit = pUnit # We have found a stronger new unit for our GG
						elif pUnit.baseCombatStr() == pNewGGunit.baseCombatStr() and pUnit.getExperience() > pNewGGunit.getExperience():
							pNewGGunit = pUnit # We have found a more experienced new unit of same strength for our GG
			if not bGGalone: # Put the GG to pNewGGunit
				unit.setLeaderUnitType(gc.getInfoTypeForString("NO_UNIT")) # Remove, for the game, the leader. Will avoid ingame message "GG died in combat"
				pNewGGunit.setHasPromotion(gc.getInfoTypeForString("PROMOTION_LEADER"),True) # Asign GG to its new unit, only add leader promotion
				pNewGGunit.setLeaderUnitType(gc.getInfoTypeForString("UNIT_GREAT_GENERAL"))	# Asign GG to its new unit, set for the game the unit with a leader, if not ingame message "GG died in combat" will never trigger next time our GG dies for real
				pNewGGunit.setName(name) # Name the unit with the GG's name
				pNewGGunit.setExperience(pNewGGunit.getExperience() + gc.getUnitInfo(gc.getInfoTypeForString("UNIT_GREAT_GENERAL")).getLeaderExperience() ,-1) # Gives GG xp again to the new unit, may be considered unfair and can be removed or not (to the appreciation of the user)
# End of great general spare troops
		if (not self.__LOG_UNITKILLED):
			return
		CvUtil.pyPrint('Player %d Civilization %s Unit %s was killed by Player %d' 
			%(player.getID(), player.getCivilizationName(), PyInfo.UnitInfo(unit.getUnitType()).getDescription(), attacker.getID()))

What the mod does:
  • If a unit possessing the leader promotion (i.e. having a great general attached to) dies from combat, the great general will try to attach to a new unit if there is any remaining unit from the stack which was containing the former (defeated) leader unit.
  • The leader will try to attach to a unit of the same combat class, or the stronger unit remaining if there is no unit of the same combat class.
  • The last line of python code make the leader give once again 20 xp to his new unit, but fell free to comment this line if you don't want that the leader gives xp again each times he is attached to a new unit.
A few words about the motives of this modcomp:
  • I always favored great generals as city specialist (who will grant free xp without taking risk) rather than warlords attached to units (who will either be lost after a few fights or, in my opinion, be useless since the odds are already high without warlord). With the ability for a warlord to survive as long as the stack is not empty, the choice between a warlord or a city specialist seems, in my opinion, more balanced.
  • AI, which seems to think its battles in terms of stack victory/defeat, has a tendency to waste its warlords quickly (I often saw AI warlords attached to a siege unit commiting suicide against my walls). So this modcomp should also benefit to AI.
To my knowledge there is no python modcomp that gives leaders the opportunity to survive the defeat of its unit if he has spare troops remaining inside his stack. I apologize by advance if such a mod already exist (the mod database is quite huge and I didn't check it all :dunno:).

Let me know if there is any bug.

And last, a link to download the modcomp: http://forums.civfanatics.com/downloads.php?do=file&id=18333
 
Code:
elif pUnit.getUnitCombatType() == combat and pUnit.getExperience() > pNewGGunit.getExperience():#pUnit.baseCombatStr() > pNewGGunit.baseCombatStr():

A) This line of code doesn't do what the comments say though.
It is just comparing whether:
1) They are of the same combat type
2) The new unit has more experience than the current chosen GG unit
Nothing to do with base str?

You may consider adding a check on the base str first or powervalue before comparing the experience, so that it will rather choose an artillery with 5 experience than a catapult with 10 experience

B) There is a flaw in the logic. It will not always try to attach to unit of same combat class.
Because it is only activated if both conditions are true, this simple scenario will make it fail:
Previous GG unit that died was Artillery.
First unit in the loop is Modern Armor with 20 Exp => pNewGGunit is now this modern armor
Then even if all the remaining units on the plot are Siege units, they will never be chosen if all of them have exp < 20
And since Modern Armor has highest base str in default BTS, the 3rd if statement will not be activated as well.

Results, GG will attach to the MA rather than any of the remaining Siege units

C) Logic Error 2: Same thing, GG unit Artillery.
First unit in loop is Artillery. Perfect candidate, pNewGGunit is now this Artillery.
But because it is activated in the first if statement, bGGalone is set to false, but bSameCombat is still False.
Now comes the 2nd unit, that damn MA again.
Because bSameCombat is still false, the 3rd if statement will be activated since MA str higher and bSameCombat false

Results, GG attach to that MA again

D) Didn't try this, but I think def onCombatResults is not activated upon Nuke explosions. If so, if the GG dies from nukes, even if there are remaining troops, this code is not activated

Welcome to python :goodjob:
 
Thank you Playtyping for your feedback, I will try to fix these issues as soon as I can (quite busy at the moment).
 
No problem, the idea is there, just that the if else statements need to rearrange some stuff.

You may consider this, same format different checks:
1) If statement, check if bGGalone, if so assign pUnit to pNewGGUnit.
Check if pNewGGUnit is same combat, if so set bSameCombat to 1.
2) Elif Statement, check for same combat
If bCombat is 0, assign pUnit to pNewGGUnit.
Elif, check strength, higher assign.
Elif similar strength, check experience, higher assign.
3) Elif, check strength, higher assign.
Elif similar strength, check experience, higher assign.

Lastly, you may wish to add a domain check as well. I may not want a land GG to become a naval GG
 
Not sure if you tested it, but this line
Code:
pLoser.setHasPromotion(gc.getInfoTypeForString("PROMOTION_LEADER"),False)

doesn't do what you advertised.
The promotion is removed, but the GG has died in combat msg will still be displayed
 
Not sure if you tested it, but this line
Code:
pLoser.setHasPromotion(gc.getInfoTypeForString("PROMOTION_LEADER"),False)

doesn't do what you advertised.
The promotion is removed, but the GG has died in combat msg will still be displayed

Yes Platyping, I saw that recently. I should have been very very tired when I posted the modcomp : I also mistyped your name in my previous reply !!! I so strongly believed that removing the promotion would avoid the died in combat message, that I didn't pay attention to the message when testing !!!
Well I really wanted to avoid this message ... may be I should change the message in the xml TXT_KEY_MISC_GENERAL_KILLED with something like "was defeated in combat". I don't know how to avoid this message in python.
 
I made a wonder modified from your idea and tried to get rid of the message also. I think the message may be due to SDK and not python because I tried to comment out all the default print codes below but the message will still be there, whether I do it under combatresults or unitkilled. In the end, I add a message "Great General is faking death and has escaped" before the default xxx has died in battle message.

By the way, because yours is defined under combatresults, if the GG died in a transport, it will not be activated as well
 
I have searched inside the SDK files for TXT_KEY_MISC_GENERAL_KILLED and found that the message was trigered by CvUnit::kill(), just after the python event onUnitKilled() is called. The python event onCombatResult() is called by the SDK function CvUnit::updateCombat(), but after various calls to CvUnit::setDamage() which calls CvUnit::kill(). So the message is triggered before the event onCombatResult() from what I have understood ...

I have also updated the python code with the changes you kindly proposed.
 
I have found a python way to avoid the TXT_KEY_MISC_GENERAL_KILLED message.
Looking inside SDK files, it appears that the TXT_KEY_MISC_GENERAL_KILLED message is triggered by CvUnit::kill() when the test (NO_UNIT != getLeaderUnitType()) is true, which is the case when the unit instance has its m_eLeaderUnitType value equal to UNIT_GREAT_GENERAL.

The python function setLeaderUnitType() is able to change the value of m_eLeaderUnitType.
If we apply setLeaderUnitType(gc.getInfoTypeForString("NO_UNIT")) to the dying unit, it will no longer trigger the TXT_KEY_MISC_GENERAL_KILLED message. We also have to apply setLeaderUnitType(gc.getInfoTypeForString("UNIT_GREAT_GENERAL")) to the new unit the GG is now attached to.

All this will work only if the modcomp is defined inside the python event onUnitKilled(), which is called before the TXT_KEY_MISC_GENERAL_KILLED message. And there is no way to avoid the TXT_KEY_MISC_YOU_UNIT_DIED_ATTACKING, TXT_KEY_MISC_YOU_UNIT_WAS_DESTROYED or TXT_KEY_MISC_YOU_UNIT_WAS_DESTROYED_UNKNOWN messages which are called by CvUnit::updateCombat(). At least we can change the "died" by "was defeated" inside those messages.

I will do more testing before updating the changes.
 
I put this into my mod, but for now im going to take it now for a problem i see: The notificiations sort of screw it up. It says "Great general has died in combat" yet the general lives, anyway you can remove this? Or maybe what would be cool if you gave this a 50% chance of happening, and in a turn it says "Great General has been found alive" and given to a unit. The other problem is if the general is named, it doesnt keep that name. Based on what i know from java, how to fix that would be to take his name, set it to a variable, then set the new generals name to that variable, but im not sure how easy that is via python
 
I put this into my mod, but for now im going to take it now for a problem i see: The notificiations sort of screw it up. It says "Great general has died in combat" yet the general lives, anyway you can remove this? Or maybe what would be cool if you gave this a 50% chance of happening, and in a turn it says "Great General has been found alive" and given to a unit. The other problem is if the general is named, it doesnt keep that name. Based on what i know from java, how to fix that would be to take his name, set it to a variable, then set the new generals name to that variable, but im not sure how easy that is via python

Hello Dacubz145,

I have updated the modcomp using the python event onUnitKilled() and the function setLeaderUnitType() which will avoid the TXT_KEY_MISC_GENERAL_KILLED message, which was a major problem since it is a global message displayed to all players.
However, I know no way to avoid either the TXT_KEY_MISC_YOU_UNIT_DIED_ATTACKING, TXT_KEY_MISC_YOU_UNIT_WAS_DESTROYED or TXT_KEY_MISC_YOU_UNIT_WAS_DESTROYED_UNKNOWN messages. I can only suggest you to change the "died" by "was defeated" inside those messages.
 
Hello Dacubz145,

I have updated the modcomp using the python event onUnitKilled() and the function setLeaderUnitType() which will avoid the TXT_KEY_MISC_GENERAL_KILLED message, which was a major problem since it is a global message displayed to all players.
However, I know no way to avoid either the TXT_KEY_MISC_YOU_UNIT_DIED_ATTACKING, TXT_KEY_MISC_YOU_UNIT_WAS_DESTROYED or TXT_KEY_MISC_YOU_UNIT_WAS_DESTROYED_UNKNOWN messages. I can only suggest you to change the "died" by "was defeated" inside those messages.

some ideas:
just set the default message to "" or "*"
copy the original message to a new TXT block
then in python, have an if statement for normal general death and your mod
then for normal death it calls then duplicate(new) TXT
and for your mod it calls your TXT

the problem TXT message will still get called, but its an empty string.

sort of brute force, but it gets rid of the offending message. (not the sound)

also to keep the general's name that is probably easy. in python record the name of the previous unit (which died) and apply it to the next unit.
 
Hmm, the name should be transfered from the old unit to the new one since I record the name with the line:
PHP:
			name = unit.getNameNoDesc()

And then set the name to the new unit with the line:
PHP:
				pNewGGunit.setName(name) # Name the unit with the GG's name

Could you tell me what was the name of your GG ? (there could be a problem with some specific letters)
 
Can somebody write last (full) version to the CvEventManager.py file and take here for download, please ?
thanks
It is interesant idea. Maybe write second GG attach take max 10 XP. It is better. 20 again is :mischief:

BTW you all are pythonists :) I have problem.
In game start I have set civilization XY will have promotion XYZ when unit trained.

My problem is, I want when civs have two and more leaders, ad promotion ABC with first leader and promotion XYZ with second leader. Is possible write some as in python?

Thanks for your time

Hrochland
 
Don't really understand, I think it is easier if you give examples of how it is supposed to work
 
OK :) Now I will play as russia.
If I choose Stalin - every new unit will have Combat I
If I choose Peter - every new unit will have City Garrison I
If I choose Cathy - every new unit will have

Now I can set adding promotion by nation (civ) as complex. I very need possibility set adding promotion by leader.
 
Top Bottom