I don't think I'd had such a report before, but it was very helpful.
I read it while experiencing the same issue in my current game, and think it could have been the cause of problems in several previous games too where the logs looked similar but I never found the issue.
My current game kept freezing. I noticed in BBAILog - Magister.txt that the last thing seemed to be the death in python of some goblins, so I tried using worldbuilding to kill all the goblins, but then the same thing just happened with Lizardmen. Then warriors, etc. I couldn't figure out what was going on, but when I read your post I noticed that Cardith Lords was prepared to finish "Nature's Revolt" that turn.
When I tried triggering Natures Revolt in worldbuilder I had the same issue without waiting for the turn to end.
I looked in CvEventManager.py and found the code is this:
Code:
elif iProjectType == gc.getInfoTypeForString('PROJECT_NATURES_REVOLT'):
lHeroicPromotions = [ gc.getInfoTypeForString('PROMOTION_HEROIC_DEFENSE'),
gc.getInfoTypeForString('PROMOTION_HEROIC_DEFENSE2'),
gc.getInfoTypeForString('PROMOTION_HEROIC_STRENGTH'),
gc.getInfoTypeForString('PROMOTION_HEROIC_STRENGTH2')
]
## iAnimal = gc.getInfoTypeForString('UNITCOMBAT_ANIMAL')
iAxeman = gc.getInfoTypeForString('UNITCLASS_AXEMAN')
iBear = gc.getInfoTypeForString('UNIT_BEAR')
iHeld = gc.getInfoTypeForString('PROMOTION_HELD')
iHunter = gc.getInfoTypeForString('UNITCLASS_HUNTER')
iLion = gc.getInfoTypeForString('UNIT_LION')
iScout = gc.getInfoTypeForString('UNITCLASS_SCOUT')
iTiger = gc.getInfoTypeForString('UNIT_TIGER')
iWarrior = gc.getInfoTypeForString('UNITCLASS_WARRIOR')
iWolf = gc.getInfoTypeForString('UNIT_WOLF')
iWorker = gc.getInfoTypeForString('UNITCLASS_WORKER')
iBeast = gc.getInfoTypeForString('UNITCOMBAT_BEAST')
for iLoopPlayer in xrange(gc.getMAX_PLAYERS()):
pLoopPlayer = gc.getPlayer(iLoopPlayer)
if pLoopPlayer.isAlive():
(pUnit, iter) = pLoopPlayer.firstUnit(False)
while(pUnit):
if ( not pUnit.isDead() ): #is the unit alive and valid?
if pUnit.isAlive():
if pUnit.isAnimal():
for iProm in lHeroicPromotions:
pUnit.setHasPromotion(iProm, True)
elif pUnit.isBarbarian():
if pUnit.isHasPromotion(iHeld) or pUnit.getUnitCombatType() == iBeast:
continue
bValid = False
iUnitType = pUnit.getUnitClassType()
if iUnitType == iWorker:
iNewUnit = iWolf
bValid = True
elif iUnitType == iScout:
iNewUnit = iLion
bValid = True
elif iUnitType == iWarrior:
iNewUnit = iLion
bValid = True
elif iUnitType == iHunter:
iNewUnit = iTiger
bValid = True
elif iUnitType == iAxeman:
iNewUnit = iBear
bValid = True
if bValid:
newUnit = bPlayer.initUnit(iNewUnit, pUnit.getX(), pUnit.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
newUnit = bPlayer.initUnit(iNewUnit, pUnit.getX(), pUnit.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
newUnit = bPlayer.initUnit(iNewUnit, pUnit.getX(), pUnit.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
pUnit.kill(True, PlayerTypes.NO_PLAYER)
(pUnit, iter) = pLoopPlayer.nextUnit(iter, False)
for iUnit in [gc.getInfoTypeForString('UNIT_GURID'), gc.getInfoTypeForString('UNIT_MARGALARD'), gc.getInfoTypeForString('UNIT_LEVIATHAN'), gc.getInfoTypeForString('UNIT_XIEN')]:
if CyGame().getUnitCreatedCount(iUnit) == 0:
cf.addUnit(iUnit)
I did not see an obvious source of the problem, but I decided to compare with vanilla FfH2 and saw that it uses for pUnit in py.getUnitList(): instead of a while loop.
I decided to bring my mode closer in line with Kael's, using this
Code:
elif iProjectType == gc.getInfoTypeForString('PROJECT_NATURES_REVOLT'):
lHeroicPromotions = [ gc.getInfoTypeForString('PROMOTION_HEROIC_DEFENSE'),
gc.getInfoTypeForString('PROMOTION_HEROIC_DEFENSE2'),
gc.getInfoTypeForString('PROMOTION_HEROIC_STRENGTH'),
gc.getInfoTypeForString('PROMOTION_HEROIC_STRENGTH2')
]
## iAnimal = gc.getInfoTypeForString('UNITCOMBAT_ANIMAL')
iAxeman = gc.getInfoTypeForString('UNITCLASS_AXEMAN')
iBear = gc.getInfoTypeForString('UNIT_BEAR')
iHeld = gc.getInfoTypeForString('PROMOTION_HELD')
iHunter = gc.getInfoTypeForString('UNITCLASS_HUNTER')
iLion = gc.getInfoTypeForString('UNIT_LION')
iScout = gc.getInfoTypeForString('UNITCLASS_SCOUT')
iTiger = gc.getInfoTypeForString('UNIT_TIGER')
iWarrior = gc.getInfoTypeForString('UNITCLASS_WARRIOR')
iWolf = gc.getInfoTypeForString('UNIT_WOLF')
iWorker = gc.getInfoTypeForString('UNITCLASS_WORKER')
iBeast = gc.getInfoTypeForString('UNITCOMBAT_BEAST')
bPlayer = gc.getPlayer(gc.getBARBARIAN_PLAYER())
py = PyPlayer(gc.getBARBARIAN_PLAYER())
for pUnit in py.getUnitList():
if pUnit.isAlive():
if pUnit.isAnimal():
for iProm in lHeroicPromotions:
pUnit.setHasPromotion(iProm, True)
elif pUnit.isBarbarian():
if pUnit.isHasPromotion(iHeld) or pUnit.getUnitCombatType() == iBeast:
continue
bValid = False
iUnitType = pUnit.getUnitClassType()
if iUnitType == iWorker:
iNewUnit = iWolf
bValid = True
elif iUnitType == iScout:
iNewUnit = iLion
bValid = True
elif iUnitType == iWarrior:
iNewUnit = iLion
bValid = True
elif iUnitType == iHunter:
iNewUnit = iTiger
bValid = True
elif iUnitType == iAxeman:
iNewUnit = iBear
bValid = True
if bValid:
newUnit = bPlayer.initUnit(iNewUnit, pUnit.getX(), pUnit.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
newUnit = bPlayer.initUnit(iNewUnit, pUnit.getX(), pUnit.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
newUnit = bPlayer.initUnit(iNewUnit, pUnit.getX(), pUnit.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
pUnit.kill(True, PlayerTypes.NO_PLAYER)
for iUnit in [gc.getInfoTypeForString('UNIT_GURID'), gc.getInfoTypeForString('UNIT_MARGALARD'), gc.getInfoTypeForString('UNIT_LEVIATHAN'), gc.getInfoTypeForString('UNIT_XIEN')]:
if CyGame().getUnitCreatedCount(iUnit) == 0:
cf.addUnit(iUnit)
With that code the ritual caused no problems whether triggered in worldbuilder or completed normally.
That above code only effects the Barbarian player, however, whereas I had a long time ago changed it to try to effect all animals owned by anyone. If you want it to do that but not freeze or chash the game, I think this is what we should use insead:
Code:
elif iProjectType == gc.getInfoTypeForString('PROJECT_NATURES_REVOLT'):
lHeroicPromotions = [ gc.getInfoTypeForString('PROMOTION_HEROIC_DEFENSE'),
gc.getInfoTypeForString('PROMOTION_HEROIC_DEFENSE2'),
gc.getInfoTypeForString('PROMOTION_HEROIC_STRENGTH'),
gc.getInfoTypeForString('PROMOTION_HEROIC_STRENGTH2')
]
iAxeman = gc.getInfoTypeForString('UNITCLASS_AXEMAN')
iBear = gc.getInfoTypeForString('UNIT_BEAR')
iHeld = gc.getInfoTypeForString('PROMOTION_HELD')
iHunter = gc.getInfoTypeForString('UNITCLASS_HUNTER')
iLion = gc.getInfoTypeForString('UNIT_LION')
iScout = gc.getInfoTypeForString('UNITCLASS_SCOUT')
iTiger = gc.getInfoTypeForString('UNIT_TIGER')
iWarrior = gc.getInfoTypeForString('UNITCLASS_WARRIOR')
iWolf = gc.getInfoTypeForString('UNIT_WOLF')
iWorker = gc.getInfoTypeForString('UNITCLASS_WORKER')
py = PyPlayer(gc.getBARBARIAN_PLAYER())
for pUnit in py.getUnitList():
if not pUnit.isAlive():
continue
if pUnit.isHasPromotion(iHeld):
continue
if pUnit.isAnimal():
for iProm in lHeroicPromotions:
pUnit.setHasPromotion(iProm, True)
continue
bValid = False
iUnitType = pUnit.getUnitClassType()
if iUnitType == iWorker:
iNewUnit = iWolf
bValid = True
elif iUnitType == iScout:
iNewUnit = iLion
bValid = True
elif iUnitType == iWarrior:
iNewUnit = iLion
bValid = True
elif iUnitType == iHunter:
iNewUnit = iTiger
bValid = True
elif iUnitType == iAxeman:
iNewUnit = iBear
bValid = True
if bValid:
newUnit = bPlayer.initUnit(iNewUnit, pUnit.getX(), pUnit.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
newUnit = bPlayer.initUnit(iNewUnit, pUnit.getX(), pUnit.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
newUnit = bPlayer.initUnit(iNewUnit, pUnit.getX(), pUnit.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_NORTH)
pUnit.kill(True, PlayerTypes.NO_PLAYER)
for iLoopPlayer in xrange(gc.getMAX_CIV_PLAYERS()):
pLoopPlayer = gc.getPlayer(iLoopPlayer)
if pLoopPlayer.isAlive():
py = PyPlayer(iLoopPlayer)
for pUnit in py.getUnitList():
if pUnit.isAlive():
if pUnit.isAnimal():
for iProm in lHeroicPromotions:
pUnit.setHasPromotion(iProm, True)
for iUnit in [gc.getInfoTypeForString('UNIT_GURID'), gc.getInfoTypeForString('UNIT_MARGALARD'), gc.getInfoTypeForString('UNIT_LEVIATHAN'), gc.getInfoTypeForString('UNIT_XIEN')]:
if CyGame().getUnitCreatedCount(iUnit) == 0:
cf.addUnit(iUnit)
I think a long time ago I decided that using those while loops ought to be more efficient than calling py.getUnitList(), but I'm not sure it is really true or if so is very significant.
def getUnitList(self): itself just uses such a while loop, but it may make a big difference whether you make a list of al units before or after doing things to each unit.
Sometimes it may be more efficient to act on each unit in the while loop, like if you are only interested in finding a specific unit and want to break the loop as soon as you find it rather than going on to check any of the other units (e.g., when the Ascension ritual needs to find Auric in order to turn him into Auric Ascended).
It is however a very bad idea to use a while loop if you are deleting units or adding more units while continuing to cycle through them, as is done in his ritual. It could risk infinite loops or cause some units to get skipped.
I'm thinking I should go through each of the places I switched to such while looks and change them back to getUnitList unless I can think of a good reason not to in that particular case. It would make the code look cleaner and easier to read, even when it does not fix bugs.
I'm also thinking that I'd like to change this ritual to make a wider variety of animals. A bunch of wolves and bears is not as cool as panthers, elephants, baboons, gorillas, pegasi, etc.