Adapting OV's Limited Religions: Problem with religions founded simultaneously

Xyth

History Rewritten
Joined
Jul 14, 2004
Messages
4,106
Location
Aotearoa
I've been adapting OrionVeteran's Limited Religions modcomp to work with religions founded by the birth of a Great Prophet rather than from a tech. To clarify further, a religion is automatically founded in the city where a prophet is born, not from any action of the prophet itself. I'm also attempting to adapt it so that the maximum number of religions scales 1:1 with the number of starting civs in game (there are 18 religions in my mod).

It's far from complete (for example it currently triggers off the birth of any great person) but the problem I've having at this early stage is that if the player and one or more AI found a religion on the same turn the AI's religions will all be founded correctly but the player's will not. The selection popup comes up but after a choice is made... nothing happens. However if the player gets a prophet on a turn that no AI gets one it all works fine.

Code:
Here's the modified function in CvEventManager.py:

	def onGreatPersonBorn(self, argsList):
		'Unit Promoted'
		pUnit, iPlayer, pCity = argsList
		player = PyPlayer(iPlayer)

# BEGIN Limited Religions

		game = CyGame()
		pPlayer = gc.getPlayer(iPlayer)
		FoundRel = False

		if iPlayer > -1:
			# Is the game option selected for Limited Religions?
			if LimitedReligions.isOC_LIMITED_RELIGIONS():
				# Skip dead players and barbarians
				if pPlayer.isAlive() and not pPlayer.isBarbarian():
					# Must not already have a Holy City
					if not LimitedReligions.OwnsHolyCity(iPlayer):
						# Loop through all religions
						for iSlot in range(gc.getNumReligionInfos()):
							# Must be at least one religion slot available
							if not game.isReligionSlotTaken(iSlot):
								# The number of religious techs discovered must be greater than total religions founded.
								if LimitedReligions.RelTechsGreaterThanReligions:
									# PreConditions are met to found a religion.
									# If the active player is Human
									if iPlayer == gc.getGame().getActivePlayer() and pPlayer.isHuman:
										# If Autoplay is running, Let the game pick the religion.
										if( game.getAIAutoPlay() > 0 ):
											FoundRel = True
											break

										else:
											#Display the popup to pick a religion
											#CyInterface().addImmediateMessage("Pick Religion", "")
											LimitedReligions.doPickReligionPopup(iPlayer, iSlot)
											break

									else:
										# Not Human, So let the game pick the religion.
										#CyInterface().addImmediateMessage("Found a Religion", "")
										FoundRel = True
										break

						if FoundRel:
							# Get Civ's favorite religion or random choice if favorite religion is not available
							iNewReligion = LimitedReligions.AI_chooseReligion(iPlayer)
							if iNewReligion != -1:
								# Found the religion
								#CyInterface().addImmediateMessage("Found Favorite Religion", "")
								pPlayer.foundReligion(iNewReligion, iSlot, True)

# END Limited Religions

		if pUnit.isNone() or pCity.isNone():
			return
		if (not self.__LOG_GREATPERSON):
			return
		CvUtil.pyPrint('A %s was born for %s in %s' %(pUnit.getName(), player.getCivilizationName(), pCity.getName()))

Here's the relevant Limited Religion function:

Code:
def doPickReligionPopup(iPlayer, religionSlot):
	# Limited Religions
	pPlayer = gc.getPlayer(iPlayer)
	popupInfo = CyPopupInfo()
	popupInfo.setButtonPopupType(ButtonPopupTypes.BUTTONPOPUP_PYTHON)
	popupInfo.setData1(iPlayer)
	popupInfo.setData2(religionSlot)
	popupInfo.setOnClickedPythonCallback("foundReligionCallback")
	popupInfo.setText(CyTranslator().getText(("TXT_KEY_FOUNDED_RELIGION"),()))

	for i in range(gc.getNumReligionInfos()):
		if not CyGame().isReligionFounded(i):
			popupInfo.addPythonButton(gc.getReligionInfo(i).getDescription(), gc.getReligionInfo(i).getButton())

	popupInfo.addPopup(iPlayer)

And here's the callback bit from CvScreensInterface.py:

Code:
# BEGIN Limited Religions

def foundReligionCallback(argsList):
	iButtonId = argsList[0]
	iData1 = argsList[1]
	iData2 = argsList[2]
	iData3 = argsList[3]
	iData4 = argsList[4]
	szText = argsList[5]
	bOption1 = argsList[6]
	bOption2 = argsList[7]

	pPlayer = gc.getPlayer(iData1)
	CyInterface().addImmediateMessage("Callback", "")
	count = -1
	for i in range(gc.getNumReligionInfos()):
		if not CyGame().isReligionFounded(i):
			count += 1
			if count == iButtonId:
				pPlayer.foundReligion(i, iData2, True)
				break

# END Limited Religions

Any idea what's going on and how to fix it? This could be an issue in unaltered Limited Religions, I'm not sure. The situation of religions being founded in the same turn probably doesn't happen very often when triggered by techs. I know OrionVeteran set LR up this way so that it wouldn't break multiplayer, hopefully any necessary change wouldn't break it again.

I should also mention the mod that I'm merging this into uses BUG.
 
The only thing I can think of right now is that for some reason the order of the actions is not exactly what you think, and the same religion is selected by both the AI and the human in the same turn.
Try adding debug prints in foundReligionCallback() to see if it's called at all and with which parameters (mainly the button id).
 
I've done some further testing and I think I know what's happening. It seems the list of options for the player to choose from is being generated prior to the AI making their choices. However once the player makes a choice the 'list' has changed and no the ButtonID no longer matches the now shortened list.

So for example if I choose an early religion in the list like Christianity and the AI have selected religions higher up than that or even Christianity itself already I end up founding a religion further down the list like Taoism. If I select a religion nearer the end of the list then if the AI have founded enough religions in that turn then my choice can get 'knocked off' the end of the list altogether and I found no religion is founded at all.

I don't know very much about how callbacks work (this is almost entirely borrowed code). Is there some way I can make the player choose first before the AI do or vice versa? And not break multiplayer in the process?
 
I'm just guessing here, but this might be the type of situation where you need to use a mod net message.

Also, enable Python exceptions and logging. Never try to edit any .py files without enabling the error messages, because your bound to generate such.

edit: Try this:
Spoiler :
Code:
# BEGIN Limited Religions

def foundReligionCallback(argsList):
	iButtonId = argsList[0]
	iData1 = argsList[1]
	iData2 = argsList[2]
	iData3 = argsList[3]
	iData4 = argsList[4]
	szText = argsList[5]
	bOption1 = argsList[6]
	bOption2 = argsList[7]

	gc.getPlayer(iData1).foundReligion(iData2, iData2, True)

# END Limited Religions
 
I tried that code but it always founded the second religion (as listed in the xml), no matter was chosen. I guess because the religion's ID and the available slot aren't necessarily the same. Or something.

I'm just guessing here, but this might be the type of situation where you need to use a mod net message.

A little over my head but that does sound like what should be used. I wonder why OrionVeteran didn't use it? He does use it in his Inquisition modcomp (which I've also merged). Gives me an example to work from though, I'll see if I can figure it out. However I think I should focus on fixing this simultaneous foundation issue first before I inevitably introduce a wealth of new issues trying something new :P

Also, enable Python exceptions and logging. Never try to edit any .py files without enabling the error messages, because your bound to generate such.

I always have logging enabled (and peruse the logs very regularly) but I hadn't noticed the exceptions option, thanks!
 
I'm not sure it'll work, but you could try using the built-in religion selection popup (it'll handle all the multiplayer issues, and I think it will construct the popup at a later stage - in which the AI religion was already founded).

Try using this code instead of calling doPickReligionPopup():

Code:
popupInfo = CyPopupInfo()
popupInfo.setButtonPopupType(ButtonPopupTypes.BUTTONPOPUP_FOUND_RELIGION)
popupInfo.addPopup(iPlayer)
 
I'm not sure it'll work, but you could try using the built-in religion selection popup (it'll handle all the multiplayer issues, and I think it will construct the popup at a later stage - in which the AI religion was already founded).

I never realized that popup could be called. Unfortunately it crashes (Mac BTS lacks a lot of error handling) when I click on one of the options. From a cursory glance through the crash report it seems that it's assuming the religions are still tied to the tech tree. Thanks for the suggestion though!
 
Can you post this crash report?

If you attach your files (and a save game) I might be able to run it on my PC, and see if the problem happens there as well.
 
The whole issues could probably be avoided by adding all religion founding in a queue of sorts, where all founding is done at the end of the turn, when all players already have had their turn. Because then any selection made by the human player would have precedence over AI religion founding. But this of course would involve rewriting the original mod-comp.
 
Perhaps you could try saving the list of religions used to populate the pop-up instead of trying to figure out which religion corresponds to the number of the button that is based on the order they were added (which may no longer match the list of available religions).

Create a list somewhere - global or as a field in some object like CvEventManager.
When adding buttons to the pop-up also append an entry to the list to store the index of the religion.
Don't forget to clear the list first, though.
When processing the callback, use the button ID as the index into the list to get ID of the religion.

That ought to make them match.

But you probably do need to do a mod net message to get it to work in multiplayer, since the other player's computers will have no idea what you picked and therefore they won't have you founding a religion when yours does have it which should cause and out of synch error. Instead of directly founding the religion in the callback, you'd send a mod net message with data that includes the actual religion's ID value (looked up from the list) as well as the player ID and in the mod net message processing you'd found that religion for that player.

Or something like that.
 
Can you post this crash report?

Okay, here's a bizarre twist. I set the code up again to reproduce the crash but this time it didn't crash (I think because I was testing this time on a smaller map). The choose religion popup came up and I noticed that 2 religions were missing from the list (confirmed to be the ones the AI had chosen - yay!). I selected a religion and the game froze, just as last night. However after several minutes the correct religion was founded! Here's the bizarre bit though: the city also had several hundred missionaries placed on it. I'm guessing creating all of those was the cause of the freeze.

I then went to each religion and changed all the free missionaries to NONE and the number to receive to 0. Tried again, same result but this time city had several hundred settlers placed on it. What could be causing this mass unit creation?

Here's the code as it looked when I tested this:

Code:
	def onGreatPersonBorn(self, argsList):
		'Unit Promoted'
		pUnit, iPlayer, pCity = argsList
		player = PyPlayer(iPlayer)

# BEGIN Limited Religions

		game = CyGame()
		pPlayer = gc.getPlayer(iPlayer)
		iUnitType = pUnit.getUnitType()
		iMaxCiv = gc.getGame().countCivPlayersEverAlive()
		iReligionCount = 0
		FoundRel = False

		if iPlayer > -1:
			#Is it a Great Prophet?
			if iUnitType == gc.getInfoTypeForString('UNIT_PROPHET'):
				# Is the game option selected for Limited Religions?
				if LimitedReligions.isOC_LIMITED_RELIGIONS():
					# Skip dead players and barbarians
					if pPlayer.isAlive() and not pPlayer.isBarbarian():
						# Must not already have a Holy City
						#if not LimitedReligions.OwnsHolyCity(iPlayer):
						# Find out how many religions have been founded
						for ixReligion in range(gc.getNumReligionInfos()):
							# If the religion has not been founded
							if gc.getGame().isReligionFounded(ixReligion):
								iReligionCount = iReligionCount + 1

						# Number of religions founded cannot exceed number of players
						if iReligionCount < iMaxCiv:
							# Unit expended
							#pUnit.kill( 0, -1 )
							# Loop through all religions
							for iSlot in range(gc.getNumReligionInfos()):
								# Must be at least one religion slot available
								if not game.isReligionSlotTaken(iSlot):
									# Preconditions are met to found a religion.
									# If the active player is human
									if iPlayer == gc.getGame().getActivePlayer() and pPlayer.isHuman:
										# If Autoplay is running, Let the game pick the religion.
										if( game.getAIAutoPlay() > 0 ):
											FoundRel = True
											break

										else:
											#Display the popup to pick a religion
											popupInfo = CyPopupInfo()
											popupInfo.setButtonPopupType(ButtonPopupTypes.BUTTONPOPUP_FOUND_RELIGION)
											popupInfo.addPopup(iPlayer)

											#popupInfo = CyPopupInfo()
											#popupInfo.setButtonPopupType(ButtonPopupTypes.BUTTONPOPUP_PYTHON)
											#popupInfo.setData1(iPlayer)
											#popupInfo.setData2(iSlot)
											#popupInfo.setOnClickedPythonCallback("foundReligionCallback")
											#popupInfo.setText(CyTranslator().getText(("TXT_KEY_FOUNDED_RELIGION"),()))

											#for i in range(gc.getNumReligionInfos()):
											#	if not CyGame().isReligionFounded(i):
											#		popupInfo.addPythonButton(gc.getReligionInfo(i).getDescription(), gc.getReligionInfo(i).getButton())

											#popupInfo.addPopup(iPlayer)
											break

									else:
										# Not Human, so let the game pick the religion.
										#CyInterface().addImmediateMessage("Found a Religion", "")
										FoundRel = True
										break

						if FoundRel:
							# Get Civ's favorite religion or random choice if favorite religion is not available
							iNewReligion = LimitedReligions.AI_chooseReligion(iPlayer)
							if iNewReligion != -1:
								# Found the religion
								#CyInterface().addImmediateMessage("Found Favorite Religion", "")
								pPlayer.foundReligion(iNewReligion, iSlot, True)

# END Limited Religions

		if pUnit.isNone() or pCity.isNone():
			return
		if (not self.__LOG_GREATPERSON):
			return
		CvUtil.pyPrint('A %s was born for %s in %s' %(pUnit.getName(), player.getCivilizationName(), pCity.getName()))


If you attach your files (and a save game) I might be able to run it on my PC, and see if the problem happens there as well.

The whole mod is a bit over 400MB but I could attach specific files or folders if it's still useful.


Baldyr and God-Emperor:

My 20 month old son is insisting we head to the park now so I'll respond to your suggestions when I'm back.
 
PHP:
if iPlayer == gc.getGame().getActivePlayer() and pPlayer.isHuman:

should be pPlayer (not iPlayer, that's the ID) at the beginning and the end isHuman() .

Looking through the code...no idea what could have happened, probably some nearly infinite loop for the AI which founded your religion a bazillion of times, until it crashed :dunno:.
 
The whole issues could probably be avoided by adding all religion founding in a queue of sorts, where all founding is done at the end of the turn, when all players already have had their turn. Because then any selection made by the human player would have precedence over AI religion founding. But this of course would involve rewriting the original mod-comp.

I seem to recall OrionVeteran mention somewhere that he shifted the founding to the start of a turn because it broke multiplayer if at the end. I haven't been able to find that discussion again though. I understand what you're saying though but I suspect that could be well beyond my meager python skills. I also don't have a very good understanding of the sequence the game tries to do things in which doesn't help.

Perhaps you could try saving the list of religions used to populate the pop-up instead of trying to figure out which religion corresponds to the number of the button that is based on the order they were added (which may no longer match the list of available religions).

Create a list somewhere - global or as a field in some object like CvEventManager.
When adding buttons to the pop-up also append an entry to the list to store the index of the religion.
Don't forget to clear the list first, though.
When processing the callback, use the button ID as the index into the list to get ID of the religion.

That ought to make them match.

That makes sense and I was wondering if that was the source of the issue. I'll do some googling and see if I can't teach myself to make such a list.

But you probably do need to do a mod net message to get it to work in multiplayer, since the other player's computers will have no idea what you picked and therefore they won't have you founding a religion when yours does have it which should cause and out of synch error. Instead of directly founding the religion in the callback, you'd send a mod net message with data that includes the actual religion's ID value (looked up from the list) as well as the player ID and in the mod net message processing you'd found that religion for that player.

Do I need both the callback and the mod net message or could the latter replace the former completely? Please forgive me if that's a dumb question, still a Python novice.

PHP:
if iPlayer == gc.getGame().getActivePlayer() and pPlayer.isHuman:

should be pPlayer (not iPlayer, that's the ID) at the beginning and the end isHuman() .

Thanks!

Looking through the code...no idea what could have happened, probably some nearly infinite loop for the AI which founded your religion a bazillion of times, until it crashed :dunno:.

When it was crashing the crash report mentioned various tech-related functions a lot. In the mod all 18 religions are tied to a single tech that isn't part of the tech tree (coords -1, -1). My best guess is that it's looking for free slots or something and not finding what it needs. How that leads to spamming me with missionaries and settlers is beyond me ><

EDIT: Found the crash log in the console:
 

Attachments

Okay trying out God-Emperor's list idea, this is what I have so far (relevent bits only):

Code:
del ReligionList[:]

for i in range(gc.getNumReligionInfos()):
	if not CyGame().isReligionFounded(i):
		ReligionList.append(i)
		popupInfo.addPythonButton(gc.getReligionInfo(i).getDescription(), gc.getReligionInfo(i).getButton())

popupInfo.addPopup(iPlayer)
break

Code:
def foundReligionCallback(argsList):
	iButtonId = argsList[0]
	iData1 = argsList[1]
	iData2 = argsList[2]
	iData3 = argsList[3]
	iData4 = argsList[4]
	szText = argsList[5]
	bOption1 = argsList[6]
	bOption2 = argsList[7]

	RelToFound = ReligionList(iButtonId)
	gc.getPlayer(iData1).foundReligion(RelToFound, iData2, True)

Hopefully I'm on the right track.

The bit I can't figure out is how to create the list as a global or "as a field in some object like CvEventManager". I know the list can be created by ReligionList = [] but I guess I need to put that somewhere in particular. Is this what all the self.XXXXX stuff is about?
 
Well, from the crash report it looks like the code tries accessing a tech ID which is out of the array bounds. This could be a result of a bad eSlotReligion though, which returns a very bad prereq tech ID. My guess would be the bad religion slot.

That would also explain the 'many missionaries' - this info is taken from the religion in 'slot', which contains garbage data.

Actually, since in the code I suggested I never supplied the slot - it makes sense.

The solution is to supply a religion slot, like this (the popup expects the slot to be defined as data1):
Code:
popupInfo = CyPopupInfo()
popupInfo.setButtonPopupType(ButtonPopupTypes.BUTTONPOPUP_FOUND_RELIGION)
[B]popupInfo.setData1(iSlot)[/B]
popupInfo.addPopup(iPlayer)

The problem is that if you specify a slot here, this slot could be taken by another AI and we're back to square one.
This could probably be solved by maintaining an 'About to be taken slot' list in Python, similar to Baldyr's and GE's suggestions.

The advantage in this is that you don't have to write any human founding code yourself - you just use the built-in popup which also handles multiplayer.

Why are there 2 parameters here anyway? Religion ID and religion slot? Does anyone know?
 
IT WORKED!!!

I didn't set up any sort of list, I just added that extra line and everything started working beautifully. Even with 11 AI civs founding a religion at the same time as me the correct religion was founded every time. And multiplayer compatibility too :)

Thanks so much to everyone in this thread, not only was a solution found but I learned a lot more about Python too! :)


The problem is that if you specify a slot here, this slot could be taken by another AI and we're back to square one.

Seems like the game handles this itself. When I did the tests with me + 11 AI the popup only listed 7 out of 18 religions, the others having been chosen/founded already despite the notifications not coming until after I founded mine.

Why are there 2 parameters here anyway? Religion ID and religion slot? Does anyone know?

I think the ID represents the order of the religions as defined in the xml file, while the slot is their place on the tech tree. From the Python API:

BOOL isReligionSlotTaken (ReligionType eIndex)
bool (ReligionID) - is religion in that tech slot founded?​
 
Nice! congratulations :goodjob:

Oh! I think I've figured it out:

It's similar to what Xyth said (tech tree slot).
This is all because of the option to choose the founded religion - a religion slot of Taoism, for example, is used once someone discovers philosophy, no matter which religion is actually selected.

Then certain properties of the religion (name, icon) are taken from the selected religion, and other properties (how many free units you get) are taken from the slot religion. This is because you want to give a free missionary to the later religions, no matter which religion was actually founded.

I hope it makes sense (it barely makes sense inside my head).
 
Its a pretty confusing concept, I have to admit... :p
 
Interesting.

Good that it works now.

You probably don't need to use a mod net message either, since the regular religion founding stuff ought to take care of that already.
 
Nice! congratulations :goodjob:

Oh! I think I've figured it out:

It's similar to what Xyth said (tech tree slot).
This is all because of the option to choose the founded religion - a religion slot of Taoism, for example, is used once someone discovers philosophy, no matter which religion is actually selected.

Then certain properties of the religion (name, icon) are taken from the selected religion, and other properties (how many free units you get) are taken from the slot religion. This is because you want to give a free missionary to the later religions, no matter which religion was actually founded.

I hope it makes sense (it barely makes sense inside my head).

Wow! If I understand this correctly, I can replace the doPickReligionPopup and the foundReligionCallback functions with these 4 lines:

Code:
popupInfo = CyPopupInfo()
popupInfo.setButtonPopupType(ButtonPopupTypes.BUTTONPOPUP_FOUND_RELIGION)
popupInfo.setData1(iSlot)
popupInfo.addPopup(iPlayer)


If I can get it to work, I will post an update to the Limited Religions mod. I won't be able to test it until I have finished coding Phase 3 of the combined forces mod.
 
Back
Top Bottom