Random Promotions?

Aha, so the winning unit always get a free "trait"?

If you're fine with just reading the XML in your mod and assigning any valid promotion, they you don't need to map each unit/class/combat type to some set of valid random promotions. (Unless you need to restrict them to some very specific setup.) That would be the first set of code I posted, and it should pretty much be done.
 
Aha, so the winning unit always get a free "trait"?

If you're fine with just reading the XML in your mod and assigning any valid promotion, they you don't need to map each unit/class/combat type to some set of valid random promotions. (Unless you need to restrict them to some very specific setup.) That would be the first set of code I posted, and it should pretty much be done.

So just copy & paste?
 
No, copy-paste is not programming. :rolleyes: But I can implement anything you like or need. You just have to be extremely precise with whatever you request. It'll be both fun and quick, for me. :D
 
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])
This code could also be augmented by adding hints - sort of catching of previous promotion lists. So each unit type would have its own array of previously valid promotions, which could be created and maintained throughout the game by makint the lValidPromotions list into a set that is stored in a dictionary. Then this data structure is used as a basis for the lValidPromotions list the next time around.

But we need one dictionary per team...
Code:
lunitTypePromotionHints = list()
for eTeam in range(gc.getMAX_TEAMS()):
	lUnitTypePromotionHints[eTeam] = dict()
Then we add to the script above:
Code:
eTeam = pUnit.getTeam()
eUnitType = pUnit.getUnitType()
unitTypePromotionsHintsDict = lunitTypePromotionsHints[eTeam]
promotionHints = unitTypePromotionsHintsDict.get(eUnitType, set())
lpromotionHints = list(promotionHints)
lValidPromotions = lpromotionHints[:]
if pUnit.isLeader():
	for ePromotion in unitCombatPromotionDict[-1]:
		if pUnit.isPromotionValid(ePromotion) and not pUnit.isHasPromotion(ePromotion):
			lValidPromotions.append(ePromotion)
eUnitCombatType = gc.getUnitInfo(eUnitType).getUnitCombatType()
for ePromotion in unitCombatPromotionDict[eUnitCombatType]:
	if ( not ePromotion in lValidPromotions
	     and pUnit.isPromotionValid(ePromotion)
             and not pUnit.isHasPromotion(ePromotion) ):
		lValidPromotions.append(ePromotion)
unitTypePromotionsHintsDict[eUnitType] = set(lpromotionHints + lValidPromotions)
iRandNum = CyGame().getSorenRandNum(len(lValidPromotions), "random promotion")
pUnit.setHasPromotion(lValidPromotions[iRandNum])
What this should do is that it stores all previous valid promotions for any unit type that has already been processed. In short: The code gets more efficient the longer it runs, as previous results are stored continuously, basically resulting in a ready-to-use database of all unit types and all valid promotions for each! :eek2: Looking up stuff in a data structure should (theoretically) be faster than running some code over and over and over again.

The only thing the script fails at is the event that promotions get obsolete, because it only adds new promotions to the sets... :rolleyes: If this happens, then another setup is needed. :p
 
The only thing the script fails at is the event that promotions get obsolete, because it only adds new promotions to the sets... :rolleyes: If this happens, then another setup is needed. :p

Don't worry about that, traits don't obselete!
 
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])
This code could also be augmented by adding hints - sort of catching of previous promotion lists. So each unit type would have its own array of previously valid promotions, which could be created and maintained throughout the game by makint the lValidPromotions list into a set that is stored in a dictionary. Then this data structure is used as a basis for the lValidPromotions list the next time around.

But we need one dictionary per team...
Code:
lunitTypePromotionHints = list()
for eTeam in range(gc.getMAX_TEAMS()):
	lUnitTypePromotionHints[eTeam] = dict()
Then we add to the script above:
Code:
eTeam = pUnit.getTeam()
eUnitType = pUnit.getUnitType()
unitTypePromotionsHintsDict = lunitTypePromotionsHints[eTeam]
promotionHints = unitTypePromotionsHintsDict.get(eUnitType, set())
lpromotionHints = list(promotionHints)
lValidPromotions = lpromotionHints[:]
if pUnit.isLeader():
	for ePromotion in unitCombatPromotionDict[-1]:
		if pUnit.isPromotionValid(ePromotion) and not pUnit.isHasPromotion(ePromotion):
			lValidPromotions.append(ePromotion)
eUnitCombatType = gc.getUnitInfo(eUnitType).getUnitCombatType()
for ePromotion in unitCombatPromotionDict[eUnitCombatType]:
	if ( not ePromotion in lValidPromotions
	     and pUnit.isPromotionValid(ePromotion)
             and not pUnit.isHasPromotion(ePromotion) ):
		lValidPromotions.append(ePromotion)
unitTypePromotionsHintsDict[eUnitType] = set(lpromotionHints + lValidPromotions)
iRandNum = CyGame().getSorenRandNum(len(lValidPromotions), "random promotion")
pUnit.setHasPromotion(lValidPromotions[iRandNum])
What this should do is that it stores all previous valid promotions for any unit type that has already been processed. In short: The code gets more efficient the longer it runs, as previous results are stored continuously, basically resulting in a ready-to-use database of all unit types and all valid promotions for each! :eek2: Looking up stuff in a data structure should (theoretically) be faster than running some code over and over and over again.

The only thing the script fails at is the event that promotions get obsolete, because it only adds new promotions to the sets... :rolleyes: If this happens, then another setup is needed. :p

Looks good.
 
Ok, I'm pretty much done with the script/module but it may be awhile before I get the time to do any actual testing. Do you also want in-game messages heralding new traits gained by human units? Like:
Excelsior was granted the Tactics Trait!
 
Ok, I'm pretty much done with the script/module but it may be awhile before I get the time to do any actual testing. Do you also want in-game messages heralding new traits gained by human units? Like:

Yes, please.
BTW, thank you.
I won't be on 'till tommorrow, got to play Rome: Total War :D
 
Blasphemer! ;)
Spoiler :
Code:
def displayMessage(pUnit, ePromotion):
    ePlayer = pUnit.getOwner()
    pPlayer = gc.getPlayer(ePlayer)
    if pPlayer.isHuman():
        unitName = pUnit.getName()
        pPromotionInfo = gc.getPromotionInfo(ePromotion)
        promotionName = pPromotonInfo.getDescription()
        message = "%s received the %s Trait!" % (unitName, promotionName)
        Interface.addImmediateMessage(message, "")
Is your mod multi-lingual, by the way? Because then the message needs to be translated...
 
Is your mod multi-lingual, by the way? Because then the message needs to be translated...

No, it is not Multi-lingual!
Just english!
 
Ok, this is what the module looks like as of now:
Spoiler :
Code:
### Random Promotions script, by Baldyr

from CvPythonExtensions import *

# constants

gc = CyGlobalContext()
Game = CyGame()
Interface = CyInterface()
unitTypePromotionHintsDict = dict()
iLeaderIndex = -1

# functions

def setup():
    global unitCombatPromotionDict
    unitCombatPromotionDict = {iLeaderIndex:[]}
    for ePromotion in xrange(gc.getNumUnitCombatInfos()):
        pPromotionInfo = gc.getPromotionInfo(ePromotion)
        if pPromotionInfo.isLeader():
            unitCombatPromotionDict[iLeaderIndex].append(ePromotion)
        else:
            for eUnitCombatType in xrange(gc.getNumPromotionInfos()):
                if pPromotionInfo.getUnitCombat(eUnitCombatType):
                    unitCombatPromotionDict.setdefault(eUnitCombatType, []).append(ePromotion)

def execute(pUnit, unitInfo):
    eTeam = pUnit.getTeam()
    eUnitType = unitInfo.getID()
    eUnitCombatType = unitInfo.getUnitCombatType()
    lUnitCombatPromotions = unitCombatPromotionDict.get(eUnitCombatType, [])
    global unitTypePromotionHintsDict
    teamUnitTypePromotionHintsDict = unitTypePromotionsHintsDict.get(eTeam, dict())
    promotionHints = teamUnitTypePromotionHintsDict.get(eUnitType, set())
    lValidPromotions = list(promotionHints)
    if not promotionHints.issuperset(lUnitCombatPromotions):
        for ePromotion in promotionHints.difference(lUnitCombatPromotions):
            if pUnit.isPromotionValid(ePromotion):
                lValidPromotions.append(ePromotion)
        unitTypePromotionHintsDict[eTeam][eUnitType] = promotionHints.union(lValidPromotions)
    if pUnit.isLeader():
        lLeaderPromotions = unitCombatPromotionDict[iLeaderIndex]
        lValidPromotions.extend(lLeaderPromotions)
    promote(pUnit, lValidPromotions)

def promote(pUnit, lPromotions):
    while lPromotions:
        iRandNum = Game.getSorenRandNum(len(Promotions), "random promotion")
        ePromotion = lPromotions[iRandNum]
        if pUnit.isHasPromotion(ePromotion):
            lPromotions.remove(ePromotion)
        else:
            pUnit.setHasPromotion(ePromotion, True)
            display(pUnit, ePromotion)
            return

def display(pUnit, ePromotion):
    if gc.getPlayer(pUnit.getOwner()).isHuman():
        unitName = pUnit.getName()
        promotionName = gc.getPromotionInfo(ePromotion).getDescription()
        message = "%s received the %s Trait!" % (unitName, promotionName)
        Interface.addImmediateMessage(message, "")
Now I just have to test and debug it. :p Since this certainly is one of the most complex scripts I've ever written, it may take awhile. :rolleyes: But if I get this off the ground I except it to not only work but to be very efficient!
 
I finally got it right! Download at the bottom of the message. Unpack files into the \Assets\Python\ folder of your mod. (If you don't have one already you should create this folder path.)

Now, I've only done some testing of the code - not played entire games with it. So there is no telling what it will do in-game. But in theory it should work as intended.

And I've designed the code for use with the standard BtS setup - not any mod or with any custom promotions. But the way the code is structured it should work for practically any setup. Provided that promotions are also available with experience, that the XML for all promotions make logical sense, and such.

Also, these lines need to be customized for use with any modded setup:
PHP:
# custom settings

leaderTag = "PROMOTION_LEADER"
combatMessage = "%s received the %s Promotion!"
The leaderTag value is the set to the default name of the "Led by Warlord" promotion that comes with attaching a GG to a unit. Change the string to whatever it is that this promotion is labeled as in your mod.

The combatMessage string can be altered to customize what the in-game message should read. (In your case you'd wanna change the word "Promotion" to "Trait".)

And if you can't get this mod-comp working with your mod, please try it out with a standard BtS setup instead. Because that way we can figure out if its my code as such that isn't doing what it should, or if it just has to be customized to work with your mod - or if you made a mistake somewhere. (Only then would I need access to your mod myself.)

edit: I didn't document the CvEventManager module because all the bits of code (in four places) can be found with searching for RandomPromotion (which is the name of the module containing the actual script). Comment out the highlighted lines in order to disable the mod-comp - or merge them into your own edited copy of CvEventManager if you wanna import it to your own mod:
Spoiler :
Code:
gc = CyGlobalContext()
localText = CyTranslator()
PyPlayer = PyHelpers.PyPlayer
PyInfo = PyHelpers.PyInfo

[B]import RandomPromotion
if gc.getGame().isFinalInitialized():
	RandomPromotion.setup()[/B]

...

	def onLoadGame(self, argsList):
		CvAdvisorUtils.resetNoLiberateCities()

[B]		RandomPromotion.setup()[/B]

...

	def onGameStart(self, argsList):
		'Called at the start of the game'

[B]		RandomPromotion.setup()[/B]

...

	def onCombatResult(self, argsList):
		'Combat Result'
		pWinner,pLoser = argsList
		playerX = PyPlayer(pWinner.getOwner())
		unitX = PyInfo.UnitInfo(pWinner.getUnitType())
		playerY = PyPlayer(pLoser.getOwner())
		unitY = PyInfo.UnitInfo(pLoser.getUnitType())

[B]		RandomPromotion.execute(pWinner, unitX)[/B]
 

Attachments

Back
Top Bottom