• Civilization 7 has been announced. For more info please check the forum here .

[MOD] MagisterModmod

I have tried most civs in this modmod and I must say that I have been really very very impressed by most of them (with a special mention to Elohims, Bannors, Illians and some others).

With one exception, the Amurites, that really disappointed me.

The (undocumented) behavior of the cave of ancestors is one of the reason of my disappointment.

And Govannon the other one. Maybe I am missing something or I was very unlucky, but I let Govannon stand on a stockpile of units, and after 10 or 12 turns, I just had a couple of units that got a spell. Just one (and it was a crappy one!).
Obviously, it seems underpowered with respect to the previous Govannon behavior. But it is not the real problem, something OP is not fun either. But it is just boring. In general a completely passive feature where the player just has to wait is boring. There are several passive functionnality in ffh (planar gates, resources discovery, etc), but the player always has a way to act in order to bias the probabilities in his favor (raise AC, get earth mana, etc).

IMHO, the previous Govannon behavior was better. But a passive behavior can be fun if:

1/ the learning probability is increased (say at every turn at least one unit of the pile learns at least one spell) so that the player **sees** that something happens.

2/ The player has some means to act in order to increase this probability. By, for instance, increasing the number of meta mana, or the total number of mana, or the number of mana spheres, or by building more cave of ancestors, or more units with channelling III, or by letting Govannon kill foes, etc... This way, the player has the feeling to be active and not to have to wait for the mercyfulness of the RNG.

Besides that the modmod is really amazing!!!!


My experience was the opposite, and the spells learned by other units was almost overwhelming in number. I simply had so much magic at my disposal that I hardly had to engage in combat at all at less than >99.9% odds.
 
@Ghostslayer:
The chance of Govannon teaching magic is dependent mostly on his Level. He can't teach many units for the first few turns as he can later. The learning also starts to go much faster when his students become teachers.


Freeing Brigit turns any player Good. Evil players cannot free Brigit though.

Auric still being Neutral by the time you learn Malevolent Designs seems odd. He should turn evil once the Whit Hand religion is founded by the White Hand ritual.

In the lore, Auric Ulvin was quite decent as a boy. When he first met Talia and Varn Gosam he was probably closer to Good than to Evil. He became evil only when he began to pursue deification, at which point he quickly became more malevolent than Mulcarn ever had been.


If Auric meets the requirements to free Brigit, he should also be able to use the Sacrifice Archangel ability in order to hurry The Draw.

@Tasunke:
It looks like they cost 90 :hammers:, half way between the price of Disciples and Priests of other religions.

---

I am pretty much ready to release a new version of my modmod, based on More Naval AI v2.42 which Tholal released yesterday.

However, there seems to be a bit of a problem which seems to be caused by an SDK change Terkhen made. Every once in a while in random games, a player will be assigned to the dummy civ CIVILIZATION_RANDOM with a leader like LEADER_RANDOM_EVIL. When this happens, the player looses the game instantly. If it is the human player, you are given the chance to switch to the next civ, and this sometimes happens repeatedly as you cycle through he civs. Each time you are reassigned, you are given a Settler on the plot (0,0), which I prefer to reserve for the Sluagh dummy units.

Should I go ahead and release the game anyway? Should I make CIVILIZATION_RANDOM unplayable, so the bug would never happen but you would also not be able to set players to be random by alignment?
 
And Govannon the other one. Maybe I am missing something or I was very unlucky, but I let Govannon stand on a stockpile of units, and after 10 or 12 turns, I just had a couple of units that got a spell. Just one (and it was a crappy one!).
Obviously, it seems underpowered with respect to the previous Govannon behavior. But it is not the real problem, something OP is not fun either. But it is just boring. In general a completely passive feature where the player just has to wait is boring. There are several passive functionnality in ffh (planar gates, resources discovery, etc), but the player always has a way to act in order to bias the probabilities in his favor (raise AC, get earth mana, etc).

Govannon seems to be a sort of patient 0 in a pandemic - alone, he's not that big of a deal. However, once he starts transferring his promotion to other units, and especially to other archmages, it doesn't take long for virtually every unit you own to have at least channeling 1 and a full set of tier 1 spells. This is great for garrison units, since it frees up mages that would otherwise be stuck in a city maintaining city buffs. Well, partially, since spirit 2 and especially creation 2 are really powerful, but at least in your new conquests you don't need to dispatch a new mage immediately.

On a different subject, I've run into another situation where I get an infinite wait on end turn. Is there any way to troubleshoot this on my end?

Edit:By removing civilization_random, does that mean a player would have to select every opponent in a game they create?
 
CIVILIZATION_RANDOM is what shows up as @Random (by Alignment), which is not the same as the Random option that I believe is hardcoded in the game engine.

Removing @Random (by Alignment) (or simply changing a couple 1s to 0s to make it unplayable but easy to add back) would not require you select every opponent, unless you particularly care about what alignment the opponents will have.

(I also place the minor leaders under this dummy civ, so that they would be available in Unrestricted Leaders games. If a leader is not listed under some civ that is not disabled, then it will not appear on the list in the Custom Game screen.)
 
I never use that particular feature, so it'd be no loss for me. I say release with civilization_random disabled.
 
I just finished uploading the new version.

I have to get up early tomorrow morning, so I'm not going to explain much about the changes just yet.
 
Played a game with the new patch as sheaim. Didn't have any problems until I switched over to an infernal race. I don't remember if this was part of base ffh, but the fact that your heavy hitters (leader unit, balors) are limited to moving on hell terrain is crippling when faced with someone who has sanctify. Specifically, on defense, these units just become giant damage sponges as they are unable to attack any stack that chain casts sanctify and are often unable to even move out of the cities they are defending except via airdrop. If these units could move freely within cultural borders, it would make them a lot more useful.

This game ended with a seemingly unavoidable infinite end turn wait. I like this mod, but I have yet to actually finish a game in it due to stuff like this.
 
I had something odd happen (I have the latest version).

A stack of skeletons attacked a new city I had. (Those skels seem to do better than they should attacking a city.) I had two warriors and a scout in the city.

Anyway, the skeletons attacked, killed one warrior and wounded another. I sit pat, next turn the skeletons attack and kill my warrior, poof we have a new spectre.

Now this is what is weird. The spectre attacks my scout in the city, and boom...

We have a brand new Wraith.

Beats me what's up with that. Two kills is all it takes to get a Wraith?
 
Does Govannon's Ethics have a requirement before spreading? For some reason he didn't spread any spells for the longest time, then boom it worked normally.

Also, is anyone else getting those infinite end turn issues when you complete the infernal pact and the infernals get summoned?
 
If these units could move freely within cultural borders, it would make them a lot more useful.
I'd like to allow them to move freely within their own borders and otherwise be limited to hell terrain, but fear it would slow the game down considerably. Unless I can convince Tholal to add a new tag to limit their movement in the DLL, it would require using CvGameUtils.py's def unitCannotMoveInto(self,argsList): callback. That could run thousands of times per turn.

I actually started to use it, and just noticed that I accidentally left some code there. It only applied to avatars of Infernal Leaders, which wouldn't help Balors. It does not matter anyway, as I changed USE_UNIT_CANNOT_MOVE_INTO_CALLBACK's <iDefineIntVal>back to 1 in PythonCallbackDefines.xml.

I just tried a new version of def unitCannotMoveInto(self,argsList), this time dependent on a new "Bound by The Compact" promotion I added and gave to units instead of <TerrainImpassables>. It seemed to work quite nicely without any noticeable lag in the quick test game I just ran, but longer tests are required before deciding to keep it.

I'd want to do more with the callback if I have to use it. It could prevent Always Hostile units from capturing cities/superforts without war. It could allow Engineers and Workers to enter peaks only while running Arete. It could allow me to get rid of the passive python spell that gives the Charmed promotion to hostile summons near a Ring of Warding, and instead simply forbid any hostile summons from moving into the city's tile. That might even be less processor intensive, although I'm not sure.

I think I'll try this:
Spoiler :
Code:
	def unitCannotMoveInto(self,argsList):
		ePlayer = argsList[0]
		iUnitId = argsList[1]
		iPlotX = argsList[2]
		iPlotY = argsList[3]

		pPlayer = gc.getPlayer(ePlayer)
		pUnit = pPlayer.getUnit(iUnitId)
		pPlot = CyMap().plot(iPlotX, iPlotY)
		if pUnit.getTeam() != pPlot.getTeam():
			if pPlot.isCity():
				pCity = pPlot.getPlotCity()
				if pCity.getNumBuilding(gc.getInfoTypeForString('BUILDING_RING_OF_WARDING')) > 0:
					if pUnit.getSummoner() != -1:
						if not pUnit.isImmuneToMagic(): #Runewyns are immune
							return True
			if pUnit.isHasPromotion(gc.getInfoTypeForString('PROMOTION_BOUND_BY_COMPACT')):
				if gc.getGame().isOption(GameOptionTypes.GAMEOPTION_NO_PLOT_COUNTER):
					iTerrain = pPlot.getTerrainType()
					if iTerrain == gc.getInfoTypeForString('TERRAIN_GRASS'):
						return True
					elif iTerrain == gc.getInfoTypeForString('TERRAIN_PLAINS'):
						return True
					elif iTerrain == gc.getInfoTypeForString('TERRAIN_DESERT'):
						return True
					elif iTerrain == gc.getInfoTypeForString('TERRAIN_MARSH'):
						return True
					elif iTerrain == gc.getInfoTypeForString('TERRAIN_TUNDRA'):
						return True
					elif iTerrain == gc.getInfoTypeForString('TERRAIN_SNOW'):
						return True
					elif iTerrain == gc.getInfoTypeForString('TERRAIN_COAST'):
						return True
					elif iTerrain == gc.getInfoTypeForString('TERRAIN_OCEAN'):
						return True
				elif pPlot.getPlotCounter() < 50:
						return True
				if pPlot.getImprovementType() == gc.getInfoTypeForString('IMPROVEMENT_HALLOWED_GROUND'):
					return True
		if pPlot.isPeak():
			if pUnit.getUnitType() == gc.getInfoTypeForString('UNIT_ENGINEER') or pUnit.getUnitType() == gc.getInfoTypeForString('UNIT_WORKER'):
				if pPlayer.getCivics(gc.getInfoTypeForString('CIVICOPTION_LABOR')) != gc.getInfoTypeForString('CIVIC_ARETE'):
					return True
		return False
This game ended with a seemingly unavoidable infinite end turn wait. I like this mod, but I have yet to actually finish a game in it due to stuff like this.

What is the cause of the various infinite end turn waits? I got that too.
I'm not sure. If it is worse in the new version, I'd guess the problem is letting Soldiers of Kilmorph build improvements. I did that because normal workers or engineers couldn't enter peaks with the Arete civic.

Posting the log files from the game with an infinite loop might help me track these down. BBAI.log is typically the most useful for this, as it shows the last decision the last player made before the loop occurred.

Two kills is all it takes to get a Wraith?
This works the same way in Magister Modmod as in base FfH2, except that in this modmod the Barbarian State has the Charismatic trait and thus lower xp requires per level so long as Orthus is alive.
[to_xp]Gekko;12229329 said:
it needs lvl5 iirc, which you reach very quickly by winning a couple battles at lucky odds. lvl 6 would be better.
I just now changed the level prereq for Skeletons upgrading to Spectres to 4 instead of 3, and Spectres upgrading to Wraiths to 6 instead of 5.
Does Govannon's Ethics have a requirement before spreading? For some reason he didn't spread any spells for the longest time, then boom it worked normally.
Here is the relevant code:
Spoiler :
Code:
def effectTeachMagic(pCaster):
	if pCaster.getImmobileTimer() < 1:
		if not (pCaster.getImmobileTimer() > 0 or pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_HELD'))):
			pPlot = pCaster.plot()
			iNumUnits = pPlot.getNumUnits()
			if iNumUnits > 1:
				iLevel = pCaster.getLevel()
				if CyGame().getSorenRandNum(21, "School of Govannon") < iLevel:
					iX = pCaster.getX()
					iY = pCaster.getY()
					pPlayer = gc.getPlayer(pCaster.getOwner())
					iTeam = pCaster.getTeam()
					sTeacherName = pCaster.getName()
					iTaught = 0
					iMagLib = gc.getInfoTypeForString('PROMOTION_MAGICALLY_LIBERAL')
					iAdept = gc.getInfoTypeForString('UNITCOMBAT_ADEPT')
					iAnimal = gc.getInfoTypeForString('UNITCOMBAT_ANIMAL')
					iBeast = gc.getInfoTypeForString('UNITCOMBAT_BEAST')

					lList = []
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CHANNELING2')):
						lList += [gc.getInfoTypeForString('PROMOTION_CHANNELING1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_EXTENSION2')):
						lList += [gc.getInfoTypeForString('PROMOTION_EXTENSION1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_AIR1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_AIR')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_AIR1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_BODY1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_BODY')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_BODY1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CHAOS1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_CHAOS')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_CHAOS1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CREATION1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_CREATION')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_CREATION1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DEATH1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_DEATH')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_DEATH1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DIMENSIONAL1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_DIMENSIONAL')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_DIMENSIONAL1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_EARTH1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_EARTH')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_EARTH1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ENCHANTMENT1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_ENCHANTMENT')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_ENCHANTMENT1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ENTROPY1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_ENTROPY')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_ENTROPY1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_FIRE1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_FIRE')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_FIRE1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_FORCE1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_FORCE')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_FORCE1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ICE1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_ICE')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_ICE1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_LAW1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_LAW')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_LAW1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_LIFE1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_LIFE')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_LIFE1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_METAMAGIC1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_METAMAGIC')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_METAMAGIC1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_MIND1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_MIND')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_MIND1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_NATURE1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_NATURE')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_NATURE1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SHADOW1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_SHADOW')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_SHADOW1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SPIRIT1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_SPIRIT')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_SPIRIT1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SUN1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_SUN')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_SUN1')]
					if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_WATER1')):
						if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_WATER')) > 0:
							lList += [gc.getInfoTypeForString('PROMOTION_WATER1')]

					while len(lList) > 0 and iTaught < iLevel:
						iProm = lList.pop(CyGame().getSorenRandNum(len(lList), "Primary School of Govannon"))
						infoPromotion = gc.getPromotionInfo(iProm)
						for i in range(iNumUnits):
							pUnit = pPlot.getUnit(i)
							if pUnit.isAlive():
								if pUnit.getTeam() == iTeam:
									if not pUnit.isHasPromotion(iProm):
										iUnitCombat = pUnit.getUnitCombatType()
										if iUnitCombat != iAnimal and iUnitCombat != iBeast and (iUnitCombat != iAdept or pUnit.isHasPromotion(iMagLib)):
											pUnit.setHasPromotion(iProm, True)
											pUnit.setHasPromotion(iMagLib, True)
											szBuffer = CyTranslator().getText("TXT_KEY_MESSAGE_MAGIC_TAUGHT", (pUnit.getName(),infoPromotion.getDescription(),sTeacherName,))
											CyInterface().addMessage(pUnit.getOwner(),True,25, szBuffer,'',1,infoPromotion.getButton(),ColorTypes(8),iX,iY,True,True)
											iTaught += 1
											if iTaught >= iLevel:
												break

					if iTaught < iLevel and pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CHANNELING3')):
						lList = [iMagLib]
						lList += [gc.getInfoTypeForString('PROMOTION_CHANNELING2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_EXTENSION3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_EXTENSION2')):
								lList += [gc.getInfoTypeForString('PROMOTION_EXTENSION2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_AIR3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_AIR2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_AIR')) > 0:
									lList += [gc.getInfoTypeForString('PROMOTION_AIR2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_BODY3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_BODY2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_BODY')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_BODY2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CHAOS3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CHAOS2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_CHAOS')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_CHAOS2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CREATION3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CREATION2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_CREATION')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_CREATION2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DEATH3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DEATH2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_DEATH')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_DEATH2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DIMENSIONAL3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DIMENSIONAL2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_DIMENSIONAL')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_DIMENSIONAL2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_EARTH3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_EARTH2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_EARTH')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_EARTH2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ENCHANTMENT3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ENCHANTMENT2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_ENCHANTMENT')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_ENCHANTMENT2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ENTROPY3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ENTROPY2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_ENTROPY')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_ENTROPY2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_FIRE3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_FIRE2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_FIRE')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_FIRE2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_FORCE3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_FORCE2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_FORCE')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_FORCE2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ICE3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ICE2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_ICE')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_ICE2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_LAW3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_LAW2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_LAW')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_LAW2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_LIFE3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_LIFE2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_LIFE')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_LIFE2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_METAMAGIC3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_METAMAGIC2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_METAMAGIC')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_METAMAGIC2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_MIND3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_MIND2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_MIND')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_MIND2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_NATURE3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_NATURE2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_NATURE')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_NATURE2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SHADOW3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SHADOW2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_SHADOW')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_SHADOW2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SPIRIT3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SPIRIT2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_SPIRIT')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_SPIRIT2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SUN3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SUN2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_SUN')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_SUN2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_WATER3')):
							if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_WATER2')):
								if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_WATER')) > 1:
									lList += [gc.getInfoTypeForString('PROMOTION_WATER2')]

						while len(lList) > 0 and iTaught < iLevel:
							iProm = lList.pop(CyGame().getSorenRandNum(len(lList), "Secondary School of Govannon"))
							infoPromotion = gc.getPromotionInfo(iProm)
							for i in range(iNumUnits):
								pUnit = pPlot.getUnit(i)
								if pUnit.isAlive():
									if pUnit.getTeam() == iTeam:
										if not pUnit.isHasPromotion(iProm):
											if gc.getUnitInfo(pUnit.getUnitType()).getTier() > 2:
												iUnitCombat = pUnit.getUnitCombatType()
												if iProm == iMagLib:
													if iUnitCombat == iAdept:
														pUnit.setHasPromotion(iMagLib, True)
														szBuffer = CyTranslator().getText("TXT_KEY_MESSAGE_MAGIC_TAUGHT", (pUnit.getName(),infoPromotion.getDescription(),sTeacherName, ))
														CyInterface().addMessage(pUnit.getOwner(),True,25, szBuffer,'',1,infoPromotion.getButton(),ColorTypes(8),iX,iY,True,True)
														iTaught += 7
														if iTaught >= iLevel:
															break
												elif infoPromotion.getPrereqPromotion() == -1 or pUnit.isHasPromotion(infoPromotion.getPrereqPromotion()):
													if iUnitCombat != iAnimal and iUnitCombat != iBeast and (iUnitCombat != iAdept or pUnit.isHasPromotion(iMagLib)):
														pUnit.setHasPromotion(iProm, True)
														pUnit.setHasPromotion(iMagLib, True)
														szBuffer = CyTranslator().getText("TXT_KEY_MESSAGE_MAGIC_TAUGHT", (pUnit.getName(),infoPromotion.getDescription(),sTeacherName, ))
														CyInterface().addMessage(pUnit.getOwner(),True,25, szBuffer,'',1,infoPromotion.getButton(),ColorTypes(8),iX,iY,True,True)
														iTaught += 7
														if iTaught >= iLevel:
															break

					if iTaught < iLevel and pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CASWALLAWN')):
						lList = [iMagLib]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CHANNELING2')):
							lList += [gc.getInfoTypeForString('PROMOTION_CHANNELING2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CHANNELING3')):
							lList += [gc.getInfoTypeForString('PROMOTION_CHANNELING3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_EXTENSION2')):
							lList += [gc.getInfoTypeForString('PROMOTION_EXTENSION2')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_EXTENSION3')):
							lList += [gc.getInfoTypeForString('PROMOTION_EXTENSION3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_AIR3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_AIR')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_AIR3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_BODY3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_BODY')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_BODY3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CHAOS3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_CHAOS')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_CHAOS3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_CREATION3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_CREATION')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_CREATION3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DEATH3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_DEATH')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_DEATH3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_DIMENSIONAL3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_DIMENSIONAL')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_DIMENSIONAL3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_EARTH3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_EARTH')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_EARTH3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ENCHANTMENT3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_ENCHANTMENT')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_ENCHANTMENT3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ENTROPY3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_ENTROPY')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_ENTROPY3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_FIRE3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_FIRE')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_FIRE3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_FORCE3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_FORCE')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_FORCE3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_ICE3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_ICE')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_ICE3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_LAW3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_LAW')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_LAW3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_LIFE3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_LIFE')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_LIFE3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_METAMAGIC3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_METAMAGIC')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_METAMAGIC3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_MIND3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_MIND')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_MIND3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_NATURE3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_NATURE')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_NATURE3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SHADOW3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_SHADOW')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_SHADOW3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SPIRIT3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_SPIRIT')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_SPIRIT3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_SUN3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_SUN')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_SUN3')]
						if pCaster.isHasPromotion(gc.getInfoTypeForString('PROMOTION_WATER3')):
							if pPlayer.getNumAvailableBonuses(gc.getInfoTypeForString('BONUS_MANA_WATER')) > 2:
								lList += [gc.getInfoTypeForString('PROMOTION_WATER3')]

						while len(lList) > 0 and iTaught < iLevel:
							iProm = lList.pop(CyGame().getSorenRandNum(len(lList), "Tertiary School of Govannon"))
							infoPromotion = gc.getPromotionInfo(iProm)
							for i in range(iNumUnits):
								pUnit = pPlot.getUnit(i)
								if pUnit.isAlive():
									if pUnit.getTeam() == iTeam:
										if not pUnit.isHasPromotion(iProm):
											if gc.getUnitInfo(pUnit.getUnitType()).getTier() > 3:
												iUnitCombat = pUnit.getUnitCombatType()
												if iProm == iMagLib:
													if iUnitCombat == iAdept:
														pUnit.setHasPromotion(iMagLib, True)
														szBuffer = CyTranslator().getText("TXT_KEY_MESSAGE_MAGIC_TAUGHT", (pUnit.getName(),infoPromotion.getDescription(),sTeacherName, ))
														CyInterface().addMessage(pUnit.getOwner(),True,25, szBuffer,'',1,infoPromotion.getButton(),ColorTypes(8),iX,iY,True,True)
														iTaught += 21
														if iTaught >= iLevel:
															break
												elif infoPromotion.getPrereqPromotion() == -1 or pUnit.isHasPromotion(infoPromotion.getPrereqPromotion()):
													if iUnitCombat != iAnimal and iUnitCombat != iBeast and (iUnitCombat != iAdept or pUnit.isHasPromotion(iMagLib)):
														pUnit.setHasPromotion(iProm, True)
														pUnit.setHasPromotion(iMagLib, True)
														szBuffer = CyTranslator().getText("TXT_KEY_MESSAGE_MAGIC_TAUGHT", (pUnit.getName(),infoPromotion.getDescription(),sTeacherName, ))
														CyInterface().addMessage(pUnit.getOwner(),True,25, szBuffer,'',1,infoPromotion.getButton(),ColorTypes(8),iX,iY,True,True)
														iTaught += 21
														if iTaught >= iLevel:
															break
	return False
As you can see, it is dependent on the caster's level, the caster's promotions, the amount of mana you control, and the promotions of the other units on the tile.

Any unit with the Govannon's Ethics promotion shares the Govannon's Ethics promotion (which in the XML is still called PROMOTION_MAGICALLY_LIBERAL) whenever it shares any promotion. The chance that the code will be called to share any promotion depends on the units level. How much it can teach depends on its level too. It can only teach spell sphere promotions in sphere promotions if its owner controls enough of the prerequisite mana, but there are no mana prereqs for the channeling or extension promotions.

As Govannon does not care for mage guilds and prefers to share his knowledge with those they exclude, he will not teach magic to arcane units unless they have already adopted Govannon's Ethics and would share this knowledge with others. Units with Govannon's Ethics and Channeling III may share Govannon's Ethics with arcane units, but they usually also have enough spell sphere promotions that they do not do this very often.
 
Posting the log files from the game with an infinite loop could possibly help me track these down. BBAI.log is typically the most useful for this, as it would reveal which player's turn is causing the problem and show the last decision that player made.

Is there anything in particular i should do when i run into one of these endless end turn waits, or just play through till it happens, force quit and post the BBAI.log?

Also, a cursory search didn't find this file on my machine - where is it located?
 
BBAI.log, and all of the other log files, should be located under C:\Users\[instert user name here]\Documents\My Games\Beyond the Sword\Logs, if it was created.

You need to edit C:\Users\Magister\Documents\My Games\Beyond the Sword\CivilizationIV.ini to enable logging.

Posting the saved game with the logs might help. Being able to see all of the units and tech that the last player has might help me guess what decision is so hard for it to make.

You could look through the logs and see if you can figure anything out first. You could try deleting some units in worldbuilder. If you find that eliminating all Soldiers of Kilmorph from the game (or removing the build commands from their unit defines) solves the problem, then I'd rather not have to download anything to test it myself.
 
As you can see, it is dependent on the caster's level, the caster's promotions, the amount of mana you control, and the promotions of the other units on the tile.

Any unit with the Govannon's Ethics promotion shares the Govannon's Ethics promotion (which in the XML is still called PROMOTION_MAGICALLY_LIBERAL) whenever it shares any promotion. The chance that the code will be called to share any promotion depends on the units level. How much it can teach depends on its level too. It can only teach spell sphere promotions in sphere promotions if its owner controls enough of the prerequisite mana, but there are no mana prereqs for the channeling or extension promotions.

As Govannon does not care for mage guilds and prefers to share his knowledge with those they exclude, he will not teach magic to arcane units unless they have already adopted Govannon's Ethics and would share this knowledge with others. Units with Govannon's Ethics and Channeling III may share Govannon's Ethics with arcane units, but they usually also have enough spell sphere promotions that they do not do this very often.
sorry magister, I'm a sucker at coding so I don't understand exactly:
does this means that the less spell promotion a "govannon's ethic" bearer has, the more likely he is to share his knowledge ?

and you said govanon doesn't share with arcane units unless they have the "govanon'ethic" promotion... however this comes from being teached by someone with "govanon's ethics"... does that means that govanon has to teach a random unit which will teach to an arcane unit and then, only then will the arcane unit be able to listen to govanon?

best regards,
 
The more spell sphere promotions a unit has, the less likely it is to teach any specific spell sphere promotion. It would not make it less likely to teach any promotion in general.

Sharing the Govannon's Ethics promotion by itself (which is the only way to get an arcane unit to learn it and thus be able to be taught more later) would be less likely if the caster knows more spell spheres.

What unit does the teaching is irrelevant.
 
I'd like to allow them to move freely within their own borders and otherwise be limited to hell terrain, but fear it would slow the game down considerably. Unless I can convince Tholal to add a new tag to limit their movement in the DLL, it would require using CvGameUtils.py's def unitCannotMoveInto(self,argsList): callback. That could run thousands of times per turn.
...

I pretty sure work boats can move on owned ocean, maybe a solution can be found there?
 
I pretty sure work boats can move on owned ocean, maybe a solution can be found there?

That is true, but I have no idea why. I haven't found anything in python or xml files to explain it. Maybe it is hard coded in the DLL somewhere?

I certainly wouldn't want demon lords to be able to move freely through any owned territory though, only through hell terrain and plots owned by their team.



In my test games so far, I have not really noticed any extra lag from using def unitCannotMoveInto(self,argsList): except in the Lord of the Balors scenario.
 
I'm having an issue where I never see revolutions happen to any civ, although I have the option checked in "Custom Game". Each civ becomes a sprawling empire with easily 20+ cities. I had the same issue in the last release, although prior to that it wasn't a problem.

Where is the RevolutionIndex stored in your mod? I don't see an entry in XML\Gameinfo\CIV4HandicapInfo, which is where I typically see it.
 
Top Bottom