Random Promotions?

topsecret

Believer
Supporter
Joined
Feb 11, 2010
Messages
6,008
Location
At the Foot of the Cross
Is there someway to use python to give random promotions?
I was going to use Occasional Promotions but it's DLL.
It needs to be python...
 
What exactly do you need? What should prompt these random promotions? Is there some set promotions that are valid or would that be situational? (Depending on unit type, prerequisites, et cetera.)

Because promotions can be granted with Python. Randomness is no obstacle, but the code can be more or less complex.

And do you know how to program in Python or do you need a ready-to-use fix?
 
What exactly do you need? What should prompt these random promotions?
Something that gives you a random promotion after a battle
Is there some set promotions that are valid or would that be situational? (Depending on unit type, prerequisites, et cetera.)
Situational, Depending on unit type and prerequisites.
And do you know how to program in Python or do you need a ready-to-use fix?
No, I can't program in python, so I need a ready-to-use fix.
 
What's it for? I might be able to help. Yes, it might be possible to give a unit a random promotion from a list when it gets out of combat. My mind's working at it already. I needed a Python idea anyway. So give me the specifics and I might be able to help. Is this a trait or what? Should it be any promotion, or just certain promotions? Should this be for all units, or just some?

You really should consider learning Python, it's really easy if you read a good book first, like Head First Programming.
 
What's it for? I might be able to help. Yes, it might be possible to give a unit a random promotion from a list when it gets out of combat. My mind's working at it already. I needed a Python idea anyway. So give me the specifics and I might be able to help. Is this a trait or what? Should it be any promotion, or just certain promotions? Should this be for all units, or just some?

You really should consider learning Python, it's really easy if you read a good book first, like Head First Programming.

Yeah TS, there are a lot of variables you need to spell out. Basically whatever you want is possible but you have to be specific.

If you want to add a promotion to someone after they win a battle, well there is a place for that in the cveventmanager. Then you'll have to decide what percentage chance to win what promotions for which units. I think you'll need to specify exactly which promotions for exactly which units because there are so many and some that require a great general or only certain unitcombats (ie siege weapons get different promotion list than a warrior).
 
This is what I did in this context with PyScenario:
Spoiler :
Code:
        def getPromotions(self, pPlayer, bGarrison):
                if self.level < 2: return
                eCombat = getCombatType(self.type)
                if not isValid(eCombat): return
                pTeam = gc.getTeam(pPlayer.getTeam())
                tScript = getScript(bGarrison, pTeam.isHasTech(iGunpowder), pTeam.isHasTech(iMilitaryScience))
                self.promotions = []
                while len(self.promotions) <= self.level - 1:
                        if self.processScript(tScript, eCombat):
                                return

        def processScript(self, tScript, eCombat):
                iLength = len(self.promotions)
                i = 0
                for tSettings in tScript:
                        lCombatTypes, bDefensive, lPrerequisites, bModern, eStart, iRange, bRandom = tSettings
                        i += 1
                        if eCombat in lCombatTypes and bDefensive and self.checkPrerequisite(lPrerequisites) and bModern:
                                if not isValid(bRandom):
                                        ePromotion = eStart
                                elif bRandom:
                                        ePromotion = self.getRandomPromotion(eStart, iRange)
                                else:
                                        ePromotion = self.getOrderedPromotion(eStart, iRange)
                                if isValid(ePromotion) and not ePromotion in self.promotions:
                                        self.promotions.append(ePromotion)
                                        if len(self.promotions) == self.level - 1: return True
                if len(self.promotions) == iLength: return True

        def checkPrerequisite(self, lPrerequisites):
                if isEmpty(lPrerequisites): return True
                for ePrerequisite in lPrerequisites:
                        if ePrerequisite in self.promotions:
                                return True
                return False
                        
        def getRandomPromotion(self, eStart, iRange):
                rnd = gc.getGame().getSorenRandNum(iRange, 'random promotion')
                for i in range(rnd, iRange + rnd):
                        ePromotion = eStart + (i % iRange)
                        if not ePromotion in self.promotions:
                                return ePromotion

        def getOrderedPromotion(self, eStart, iRange):
                for ePromotion in range(eStart, eStart + iRange):
                        if not ePromotion in self.promotions:
                                return ePromotion

        def setPromotions(self, pUnit):
                for ePromotion in self.promotions:
                        if self.forcePromotions or pUnit.isPromotionValid(ePromotion):
                                pUnit.setHasPromotion(ePromotion, True)
While this is not a ready-to-copy-paste solution for your specific needs, its still a working script for my PyScenario application. (It sets promotions for spawned units. Some are set randomly while others are set in priority order depending on the prerequisites. The whole thing is designed to create slightly different outcomes from game to game, mimicking some sort of "artificial intelligence".)

So its doable. The promotions themselves are defined in this data structure:
Spoiler :
Code:
def getScript(bGar, bGP, bMS):
        return (([3,4],         not bGar,[0,29],True,   23,     3,      False), #city raider
                ([1,5],         bGar,   [],     True,   26,     3,      False), #city garrison
                ([3],           not bGar,[1],   True,   5,      2,      True),  #shock/cover
                ([2,3],         True,   [1],    not bMS,8,      2,      True),  #formation/charge
                ([5],           True,   [2],    bMS,    10,     3,      True),  #gunpowder
                ([4],           not bGar,[],    True,   36,     0,      None),  #accuracy
                ([9],           True,   [2],    True,   53,     0,      None),  #ace
                ([9],           True,   [],     True,   49,     2,      False), #range
                ([2,5,6,7,9],   True,   [1],    bGP,    7,      0,      None),  #pinch
                ([2,6,7,8],     not bGar,[2],   bMS,    13,     0,      None),  #blitz
                ([0,1,2,3,5,6], not bGar,[3],   bMS,    14,     0,      None),  #commando
                ([0,1,2,3,4,5,6,8],True,[3,32], True,   15,     2,      False), #medic
                ([0,1,2,3,5,6,7,8,9],True,[],   True,   0,      5,      False), #combat
                ([1,4,6],       True,   [],     True,   29,     4,      False), #drill
                ([8],           True,   [37],   True,   41,     2,      False), #navigation
                ([2,6,7,8],     True,   [],     True,   37,     2,      False), #flanking
                ([4],           True,   [],     True,   33,     3,      False), #barrage
                ([0],           True,   [0,29], True,   20,     2,      False), #woodsman(scout)
                ([0],           True,   [0,29], True,   17,     2,      False), #guerilla(scout)
                ([5],           not bGar,[0,29],True,   20,     3,      False), #woodsman
                ([1,5],         not bGar,[0,29],True,   17,     3,      False)) #guerilla
This of course doesn't make much sense at a glance, but the data is basically parsed by the script and the various settings are then used to determine what promotions should be granted. The whole thing could of course be totally random, but then there wouldn't be any "intelligence" behind it.
 
It's traits for units, so certain units and certain promotions.
Sorry for keeping you waiting, but I go to church on Sunday (more important than Civ).
 
Yeah, like spit out the entire spreadsheet with units and their available promotions.

Or should everything just "make sense" with regard to how the original game is designed? Basically lookup all available promotions, check which ones are valid for the unit in question at a given point in the game, add those to a list and choose one of those at random?

edit: I looked into it and the main challenge is of curse to make any such code as efficient as possible. A good start might be to map all available promotions to some UnitCombatTypes value, and also weed out special cases like leader promotions. This should be possible to do with CvPromotionInfo.getUnitCombat() and CvPromotionsInfo.isLeader(). Something like this:
Spoiler :

@init:
PHP:
unitCombatPromotionDict = dict()
for eUnitCombatType in range(-1, gc.getNumUnitCombatInfos()):
	unitCombatPromotionDict[eUnitCombatType] = list()
for ePromotion in range(gc.getNumPromotionInfos()):
	pPromotionInfo = gc.getPromotionInfo(ePromotion)
	if pPromotionInfo.isLeader():
		unitCombatPromotionDict[-1].append(ePromotion)
	else:
		for eUnitCombatType in range(gc.getNumUnitCombatInfos()):
			if pPromotionInfo.getUnitCombat(eUnitCombatType):
				unitCombatPromotionDict[eUnitCombatType].append(ePromotion)
@script:
PHP:
lValidPromotions = list()
if pUnit.isLeader():
	for ePromotion in unitCombatPromotionDict[-1]:
		if pUnit.isPromotionValid(ePromotion) and not pUnit.isHasPromotion(ePromotion):
			lValidPromotions.append(ePromotion)
eUnitCombatType = gc.getUnitInfo(pUnit.getUnitType()).getUnitCombatType()
for ePromotion in unitCombatPromotionDict[eUnitCombatType]:
	if pUnit.isPromotionValid(ePromotion) and not pUnit.isHasPromotion(ePromotion):
		lValidPromotions.append(ePromotion)
iRandNum = CyGame().getSorenRandNum(len(lValidPromotions), "random promotion")
pUnit.setHasPromotion(lValidPromotions[iRandNum])
If this is valid (I haven't tested any of it myself) its only a matter of knowing where to put the code. :king:
 
That would seem like a good option.

You could even check the list of currently valid promotions to separate out those that are specifically appropriate for the fight, like Cover if the enemy was an archer or Shock if the enemy was melee, or what terrain the fight happened on to get woodsman or guerrilla (perhaps limiting it to promotions that give an offensive bonus for the terrain the defended was in if you won while attacking, and for defensive terrain bonuses for the terrain you were in if you won as defender). You could then pick a situational promotion if there is one or more, otherwise going with a purely random promotion.

There are many possible levels of complexity...
 
Alright, I made a new unit with new promotions, so is there a way you can make code that I could copy & paste and type the names of the units and promotions myself?
 
Alright, not a spreadsheet and just 1 unit & 3 promotions so far, but...

Name - Basic General
Traits (Promotions) - Good Commander, Intelligent, and Learned.
 
Yeah, like spit out the entire spreadsheet with units and their available promotions.

Or should everything just "make sense" with regard to how the original game is designed? Basically lookup all available promotions, check which ones are valid for the unit in question at a given point in the game, add those to a list and choose one of those at random?

edit: I looked into it and the main challenge is of curse to make any such code as efficient as possible. A good start might be to map all available promotions to some UnitCombatTypes value, and also weed out special cases like leader promotions. This should be possible to do with CvPromotionInfo.getUnitCombat() and CvPromotionsInfo.isLeader(). Something like this:
Spoiler :

@init:
PHP:
unitCombatPromotionDict = dict()
for eUnitCombatType in range(-1, gc.getNumUnitCombatInfos()):
	unitCombatPromotionDict[eUnitCombatType] = list()
for ePromotion in range(gc.getNumPromotionInfos()):
	pPromotionInfo = gc.getPromotionInfo(ePromotion)
	if pPromotionInfo.isLeader():
		unitCombatPromotionDict[-1].append(ePromotion)
	else:
		for eUnitCombatType in range(gc.getNumUnitCombatInfos()):
			if pPromotionInfo.getUnitCombat(eUnitCombatType):
				unitCombatPromotionDict[eUnitCombatType].append(ePromotion)
@script:
PHP:
lValidPromotions = list()
if pUnit.isLeader():
	for ePromotion in unitCombatPromotionDict[-1]:
		if pUnit.isPromotionValid(ePromotion) and not pUnit.isHasPromotion(ePromotion):
			lValidPromotions.append(ePromotion)
eUnitCombatType = gc.getUnitInfo(pUnit.getUnitType()).getUnitCombatType()
for ePromotion in unitCombatPromotionDict[eUnitCombatType]:
	if pUnit.isPromotionValid(ePromotion) and not pUnit.isHasPromotion(ePromotion):
		lValidPromotions.append(ePromotion)
iRandNum = CyGame().getSorenRandNum(len(lValidPromotions), "random promotion")
pUnit.setHasPromotion(lValidPromotions[iRandNum])
If this is valid (I haven't tested any of it myself) its only a matter of knowing where to put the code. :king:

I'll try it, but i don't know where to post it in CvEventManger...
 
If you submit your copy of CvEventManager I can implant a customized version of my code into it.
 
Alright, not a spreadsheet and just 1 unit & 3 promotions so far, but...

Name - Basic General
Traits (Promotions) - Good Commander, Intelligent, and Learned.
The basic idea for a custom setup like this is to put all units/unit classes/unit combat types into a dictionary or some other data structure. Like this:
Code:
unitPromotionDict = { eBasicGeneral: [eGoodCommander, eIntelligent, eLearned] }
Firstly, this is a dictionary with the unit as the key and a list of promotions as the value. Secondly, you add more entries in the same fashion but you separate them with a comma:
Code:
unitPromotionDict = {
eBasicGeneral: [eGoodCommander, eIntelligent, eLearned],
eAnotherUnit: [eSomePromotion, eOtherPromotion]
}
Note that line breaks are arbitrary.

Now, before using these variables you should define them:
Code:
eBasicGeneral = gc.getInfoTypeForString("UNIT_BASIC_GENERAL")
eGoodCommander = gc.getInfoTypeForString("PROMOTION_GOOD_GENERAL")
eIntelligent = gc.getInfoTypeForString("PROMOTION_INTELLIGENT")
eLearned = gc.getInfoTypeForString("PROMOTION_LEARNED")
Two things of note: The string tags must correspond to the actual XML entries - these are just examples. And since the XML is loaded only after Python is loaded you can't define the variables in this fashion at initialization. This complicates things somewhat.

But once you have all the constants done you can access the dictionary to do your dirty work:
Code:
eUnitType = pUnit.getUnitType()
lValidPromotions = unitPromotionDict.get(eUnitType, [])
lAvailablePromotions = lValidPromotins[:]
while lAvailablePromotions:
	iNumPromotions = len(lAvailablePromotions)
	iRandNum = Game().getSorenRandNum(iNumPromotions, "random promotion")
	ePromotion = lAvailablePromotions[iRandNum]
	if pUnit.isHasPromotion(ePromotion):
		lAvailablePromotions.remove(ePromotion)
	pUnit.setHasPromotion(ePromotion)
 
If you submit your copy of CvEventManager I can implant a customized version of my code into it.

I don't have one ;)
You can use the default BtS!
 
The basic idea for a custom setup like this is to put all units/unit classes/unit combat types into a dictionary or some other data structure. Like this:
Code:
unitPromotionDict = { eBasicGeneral: [eGoodCommander, eIntelligent, eLearned] }
Firstly, this is a dictionary with the unit as the key and a list of promotions as the value. Secondly, you add more entries in the same fashion but you separate them with a comma:
Code:
unitPromotionDict = {
eBasicGeneral: [eGoodCommander, eIntelligent, eLearned],
eAnotherUnit: [eSomePromotion, eOtherPromotion]
}
Note that line breaks are arbitrary.

Now, before using these variables you should define them:
Code:
eBasicGeneral = gc.getInfoTypeForString("UNIT_BASIC_GENERAL")
eGoodCommander = gc.getInfoTypeForString("PROMOTION_GOOD_GENERAL")
eIntelligent = gc.getInfoTypeForString("PROMOTION_INTELLIGENT")
eLearned = gc.getInfoTypeForString("PROMOTION_LEARNED")
Two things of note: The string tags must correspond to the actual XML entries - these are just examples. And since the XML is loaded only after Python is loaded you can't define the variables in this fashion at initialization. This complicates things somewhat.

But once you have all the constants done you can access the dictionary to do your dirty work:
Code:
eUnitType = pUnit.getUnitType()
lValidPromotions = unitPromotionDict.get(eUnitType, [])
lAvailablePromotions = lValidPromotins[:]
while lAvailablePromotions:
	iNumPromotions = len(lAvailablePromotions)
	iRandNum = Game().getSorenRandNum(iNumPromotions, "random promotion")
	ePromotion = lAvailablePromotions[iRandNum]
	if pUnit.isHasPromotion(ePromotion):
		lAvailablePromotions.remove(ePromotion)
	pUnit.setHasPromotion(ePromotion)

WOW :wow:
 
Aha, but what game event will prompt a unit to get a random promotion/trait? Because this is what the Event Handler does - it fires Python code on game events.

Is it on unit creation, perhaps? (Thats a game event.)

If you get the dictionary of units and traits in order I can create a RandomPromotion module for you, and plug it into CvEventManager. You also need to define all the promotions and units as variables - see example. (Its mostly a copy-paste job.)
 
Aha, but what game event will prompt a unit to get a random promotion/trait? Because this is what the Event Handler does - it fires Python code on game events.

Is it on unit creation, perhaps? (Thats a game event.)

The game event should be after a battle.
 
If you get the dictionary of units and traits in order I can create a RandomPromotion module for you, and plug it into CvEventManager. You also need to define all the promotions and units as variables - see example. (Its mostly a copy-paste job.)

That will take a while.
Maybe two days to a week or more...
 
Back
Top Bottom