Request: Assistance with religion changes

ripple01

Emperor
Joined
Mar 7, 2006
Messages
1,254
Location
New York City
I am working on a comprehensive Civ4 mod and I have a couple of things I need to fix up before releasing my beta test. I need some SDK assistance to complete the religion changes I am making.

Using some ideas from the True Prophets mod, religions are now going to be founded by Great Prophets, not techs. I have implemented the code successfully from True Prophets and the Great Prophets can found the religions as intended. However, the AI is not founding the religions as much as I would like. Also, the AI picks the religions in the same order everytime. (Judiasm, Christianity, Islam, etc.)

Could anyone possibly give me some assistance in cleaning this up? I'm no code monkey, but it doesn't seem on the face of it to be something overly complicated.

Major bonus points if you can tie in the AI's favorite religion so that they are more likely to select it. Extra major bonus points if you can tell me how to make Great Prophet points accumulate separately from the rest, like Great General points.
 
Sure, here is the relevant code from CvCityAI.cpp. I'll post the code from CvPlayerAI in the next post.


Code:
 if (!isHuman() && (iCurrentEra <= ((iTotalEras * 2) / 3)))
        {
            // try to spawn a prophet for any shrines we have yet to build
            bool bNeedProphet = false;
            int iBestSpreadValue = 0;
			
			//Afforess True Prophets AI Start
			
			for (iI = 0; iI < GC.getNumBuildingInfos(); iI++)
			{
				ReligionTypes eReligion = (ReligionTypes)GC.getBuildingInfo((BuildingTypes)iI).getFoundsReligion();
				if (eReligion != NO_RELIGION)
				{
					if (!GC.getGameINLINE().isReligionFounded(eReligion))
					{
						bool bValid = false;
						if ((GET_TEAM(getTeam()).isHasTech((TechTypes)(GC.getBuildingInfo((BuildingTypes)iI).getPrereqAndTech()))))
						{
							bValid = true;
						}

						for (iJ = 0; iJ < GC.getNUM_BUILDING_AND_TECH_PREREQS(); iJ++)
						{
							if (GC.getBuildingInfo((BuildingTypes)iI).getPrereqAndTechs(iJ) != NO_TECH)
							{
								if (!(GET_TEAM(getTeam()).isHasTech((TechTypes)(GC.getBuildingInfo((BuildingTypes)iI).getPrereqAndTechs(iJ)))))
								{
									bValid = true;
									break;
								}
							}
						}
						if (bValid)
						{
							bNeedProphet = true;
							break;
						}
					}
				}
			}
			
			//Afforess True Prophets AI End



Code:
//Afforess True Prophets AI Start
	if (!bDanger && !bLandWar && !bFinancialTrouble && (isCapital() || getPopulation() > 3))
	{
		if (AI_chooseBuilding(BUILDINGFOCUS_SPECIALIST, 20))
		{
			return;
		}
	}
	//Afforess True Prophets AI End



Code:
if ((iFocusFlags & BUILDINGFOCUS_SPECIALIST) || (iPass > 0))
			{
			
				//Afforess True Prophets AI Start
				if (!isLimitedWonderClass((BuildingClassTypes)kBuilding.getBuildingClassType()))
				{
					if (kBuilding.getGreatPeopleUnitClass() != NO_UNITCLASS && kBuilding.getGreatPeopleRateChange() > 0)
					{
						UnitTypes eProphet = (UnitTypes)GC.getCivilizationInfo(getCivilizationType()).getCivilizationUnits(kBuilding.getGreatPeopleUnitClass());
						if (eProphet != NO_UNIT)
						{
							for (int iBuilding = 0; iBuilding < GC.getNumBuildingInfos(); iBuilding++)
							{
								ReligionTypes eReligion = (ReligionTypes)GC.getBuildingInfo((BuildingTypes)iBuilding).getFoundsReligion();
								if (eReligion != NO_RELIGION)
								{
									if (!GC.getGameINLINE().isReligionFounded(eReligion))
									{
										if ((!GC.getUnitInfo(eProphet).getBuildings((BuildingTypes)iBuilding) && !GC.getUnitInfo(eProphet).getForceBuildings((BuildingTypes)iBuilding)))
										{
											eProphet = NO_UNIT;
											break;
										}
									}
								}
							}
						}
						if (eProphet != NO_UNIT)
						{
							iValue += kBuilding.getGreatPeopleRateChange() * 100;
						}
					}
				}
				
				//Afforess True Prophets AI End
 
CvPlayerAI

Code:
TechTypes CvPlayerAI::AI_bestTech(int iMaxPathLength, bool bIgnoreCost, bool bAsync, TechTypes eIgnoreTech, AdvisorTypes eIgnoreAdvisor) const
{
	PROFILE("CvPlayerAI::AI_bestTech");
	
	//Afforess True Prophets AI Start
	bool bValid = (GC.getGameINLINE().isOption(GAMEOPTION_AGGRESSIVE_AI) || (((bAsync) ? GC.getASyncRand().get(8, "AI Research ASYNC") : GC.getGameINLINE().getSorenRandNum(8, "AI Research")) == 0));
	if (!bValid)
	{
		for (int iI = 0; iI < GC.getNumTraitInfos(); iI++)
		{
			if (hasTrait((TraitTypes)iI))
			{
				if (GC.getTraitInfo((TraitTypes)iI).getMaxAnarchy() >= 0)
				{
					bValid = true;
					break;
				}
			}
		}
	}
	if (bValid)
	{
		if (AI_isFinancialTrouble())
		{
			bValid = false;
		}
		if (GET_TEAM(getTeam()).getAnyWarPlanCount(true) == 0)
		{
			bValid = false;
		}
		//AI should use normal early techs
		if (getNumCities() == 1)
		{
			bValid = false;
		}
	}
	if (bValid)
	{
		TechTypes eBestRelTech = AI_bestReligiousTech(iMaxPathLength * 4, eIgnoreTech, eIgnoreAdvisor);
		if (eBestRelTech != NO_TECH)
		{
			return eBestRelTech;
		}
	}
	//Afforess True Prophets AI End


Code:
//Afforess True Prophets AI Start

TechTypes CvPlayerAI::AI_bestReligiousTech(int iMaxPathLength, TechTypes eIgnoreTech, AdvisorTypes eIgnoreAdvisor) const
{
	PROFILE("CvPlayerAI::AI_bestReligiousTech");

	int iValue;
	int iBestValue = 0;
	TechTypes eBestTech = NO_TECH;
	int iPathLength;
	
	for (int iI = 0; iI < GC.getNumTechInfos(); iI++)
	{
		if ((eIgnoreTech == NO_TECH) || (iI != eIgnoreTech))
		{
			if ((eIgnoreAdvisor == NO_ADVISOR) || (GC.getTechInfo((TechTypes)iI).getAdvisorType() != eIgnoreAdvisor))
			{
				if (canEverResearch((TechTypes)iI))
				{
					if (!GET_TEAM(getTeam()).isHasTech((TechTypes)iI))
					{
						if (GC.getTechInfo((TechTypes)iI).getEra() <= (getCurrentEra()))
						{
							iPathLength = findPathLength(((TechTypes)iI), false);

							if (iPathLength <= iMaxPathLength)
							{
								iValue = AI_religiousTechValue((TechTypes)iI);
								
								iValue /= std::max(1, iPathLength);

								if (iValue > iBestValue)
								{
									iBestValue = iValue;
									eBestTech = ((TechTypes)iI);
								}
							}
						}
					}
				}
			}
		}
	}

	return eBestTech;
}

int CvPlayerAI::AI_religiousTechValue(TechTypes eTech) const
{
	PROFILE_FUNC();
	
	int iReligionValue = 0;
	
	for (int iI = 0; iI < GC.getNumBuildingInfos(); iI++)
	{
		ReligionTypes eReligion = (ReligionTypes)GC.getBuildingInfo((BuildingTypes)iI).getFoundsReligion();
		if (eReligion != NO_RELIGION)
		{
			if (!GC.getGameINLINE().isReligionFounded(eReligion))
			{
				bool bValid = false;
				for (int iJ = 0; iJ < GC.getNUM_BUILDING_AND_TECH_PREREQS(); iJ++)
				{
					if (GC.getBuildingInfo((BuildingTypes)iI).getPrereqAndTechs(iJ) == eTech)
					{
						bValid = true;
						break;
					}
				}
				if (bValid || GC.getBuildingInfo((BuildingTypes)iI).getPrereqAndTech() == eTech)
				{
					iReligionValue += 250;
					if ((ReligionTypes)GC.getLeaderHeadInfo(getLeaderType()).getFavoriteReligion() == eReligion)
					{
						iReligionValue += 100;
					}
				}
			}
		}
	}
	return iReligionValue;
}
//Afforess True Prophets AI End


This code looks to me like it has something to do with evaluating techs that enable religions. In my mod, any religioin is foundable from the beginning of the game, so they are not tied to techs in any way.

I really, really, appreciate any help anyone could provide. Let me know if you need me to post any other code snippets, these are all from the AI files, but maybe there is relevant code in the other files modified.
 
...not a C++ person, so i can't really help, but some thoughts:
- Where exactly is gameplaywise the problem? Does the AI not try to get enough prophets, or does it not use them to found a religion?
- The reason for everytime the same religion order is the third part from CvCityAI.cpp. Essentially it chooses the building, which is the first in the XML for a not yet founded religion. So it's always the same like in the XML.
- I can't see the AI part which forces the prophetAI to build the religious building (that's how it works, right?)
 
Thanks, The_J.

The AI successfully gets enough Great Prophets, they just don't use them to found a religion often enough.

I forgot to paste the code from CvUnitAI. I'm pasting it here: this should be where the AI forces the prophet to build the religious founding building.

CvUnitAI:

Code:
//Afforess True Prophets AI Start
bool CvUnitAI::AI_foundReligion()
{
	CvPlot* pEndTurnPlot = NULL;
	CvCity* pBestCity = NULL;
	CvCity* pLoopCity;
	int iLoop;
	BuildingTypes eBestReligionCenter = NO_BUILDING;

	for (pLoopCity = GET_PLAYER(getOwnerINLINE()).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER(getOwnerINLINE()).nextCity(&iLoop))
	{
		if (AI_plotValid(pLoopCity->plot()))
		{
			if (!(pLoopCity->plot()->isVisibleEnemyUnit(this)))
			{
				if (GET_PLAYER(getOwnerINLINE()).AI_plotTargetMissionAIs(pLoopCity->plot(), MISSIONAI_CONSTRUCT, getGroup()) == 0)
				{
					if (generatePath(pLoopCity->plot(), 0, true))
					{
						BuildingTypes eReligionCenter = NO_BUILDING;
						int iValue;
						int iBestValue = 0;
						for (int iI = 0; iI < GC.getNumBuildingInfos(); iI++)
						{
							ReligionTypes eReligion = (ReligionTypes)GC.getBuildingInfo((BuildingTypes)iI).getFoundsReligion();
							if (eReligion != NO_RELIGION)
							{
								if (!GC.getGameINLINE().isReligionFounded(eReligion))
								{
									if (m_pUnitInfo->getForceBuildings((BuildingTypes)iI) || m_pUnitInfo->getBuildings((BuildingTypes)iI))
									{
										if (canConstruct(pLoopCity->plot(), (BuildingTypes)iI, false))
										{
											iValue = 0;
											for (int iJ = 0; iJ < NUM_COMMERCE_TYPES; iJ++)
											{
												iValue += GC.getReligionInfo(eReligion).getGlobalReligionCommerce(iJ) * 10;
												iValue += GC.getReligionInfo(eReligion).getHolyCityCommerce(iJ); 
											}
											iValue *= GC.getReligionInfo(eReligion).getSpreadFactor() / 100;
											if (iValue > iBestValue)
											{
												eReligionCenter = (BuildingTypes)iI;
												iBestValue = iValue;
											}
										}
									}
								}
							}
						}
						if (eReligionCenter != NO_BUILDING)
						{
							int iLoopCommerceRateRank = 0;
							int iBestCityCommerceRateRank = 0;
							if (pBestCity != NULL)
							{
								for (int iJ = 0; iJ < NUM_COMMERCE_TYPES; iJ++)
								{
									iLoopCommerceRateRank += pLoopCity->findCommerceRateRank((CommerceTypes)iJ);
									iBestCityCommerceRateRank += pBestCity->findCommerceRateRank((CommerceTypes)iJ);
								}
							}
							if (pBestCity == NULL || iLoopCommerceRateRank < iBestCityCommerceRateRank)
							{
								pBestCity = pLoopCity;
								pEndTurnPlot = getPathEndTurnPlot();
								eBestReligionCenter = eReligionCenter;
							}
						}
					}
				}
			}
		}
	}
	
	if (pBestCity != NULL && pEndTurnPlot != NULL && eBestReligionCenter != NO_BUILDING)
	{
		if (atPlot(pEndTurnPlot) || atPlot(pBestCity->plot()))
		{
			getGroup()->pushMission(MISSION_CONSTRUCT, eBestReligionCenter);
			return true;
		}
		else
		{
			FAssert(!atPlot(pEndTurnPlot));
			getGroup()->pushMission(MISSION_MOVE_TO, pEndTurnPlot->getX_INLINE(), pEndTurnPlot->getY_INLINE(), 0, false, false, MISSIONAI_CONSTRUCT, pBestCity->plot());
			return true;
		}
	}
	
	return false;
}
	//Afforess True Prophets AI End

void CvUnitAI::AI_prophetMove()
{
	PROFILE_FUNC();

	//Afforess True Prophets AI Start
	
	if (AI_foundReligion())
	{
		return;
	}
	
	//Afforess True Prophets AI End
	
	if (AI_construct(1))
	{
		return;
	}

	if (AI_discover(true, true))
	{
		return;
	}

	if (AI_construct(3))
	{
		return;
	}
	
	int iGoldenAgeValue = (GET_PLAYER(getOwnerINLINE()).AI_calculateGoldenAgeValue() / (GET_PLAYER(getOwnerINLINE()).unitsRequiredForGoldenAge()));
	int iDiscoverValue = std::max(1, getDiscoverResearch(NO_TECH));

	if (((iGoldenAgeValue * 100) / iDiscoverValue) > 60)
	{
        if (AI_goldenAge())
        {
            return;
        }

        if (iDiscoverValue > iGoldenAgeValue)
        {
            if (AI_discover())
            {
                return;
            }
            if (GET_PLAYER(getOwnerINLINE()).getUnitClassCount(getUnitClassType()) > 1)
            {
                if (AI_join())
                {
                    return;
                }
            }
        }
	}
	else
	{
		if (AI_discover())
		{
			return;
		}

		if (AI_join())
		{
			return;
		}
	}

	if ((GET_PLAYER(getOwnerINLINE()).AI_getPlotDanger(plot(), 2) > 0) ||
		  (getGameTurnCreated() < (GC.getGameINLINE().getGameTurn() - 25)))
	{
		if (AI_discover())
		{
			return;
		}
	}

	if (AI_retreatToCity())
	{
		return;
	}

	if (AI_safety())
	{
		return;
	}

	getGroup()->pushMission(MISSION_SKIP);
	return;
}
 
*scratch*
...okay...so in case the unit is a prophet, AI_prophetMove() will be called (always).
AI_prophetMove() will always first call CvUnitAI::AI_foundReligion().
CvUnitAI::AI_foundReligion() will in case that there is a suitable city (determined by commerce), that the unit can reach the city, and there is a religion to found always push the unit to do that.
So if there's a bug, then it must be part of CvUnitAI::AI_foundReligion().

mmhh...i myself can't see anything there :think:.
Besides that it will not happen if enemy units are around there. So in case you have a high barb activity or aggressive AI in your game, this should normally work :confused: (unless i'm missing something).
 
I'm bumping this in the hope that someone might be able to give me any more insight on these issues. These are the final major hurdles I am facing that are holding me back from releasing my mod I've been working on for a long time.
The order is determined by the commerce gain and the spread factor. If that is the same, then the order in the XML will determine which religion is picked.
You can change that by adding to this part of the code:
Code:
for (int iJ = 0; iJ < NUM_COMMERCE_TYPES; iJ++)
{
	iValue += GC.getReligionInfo(eReligion).getGlobalReligionCommerce(iJ) * 10;
	iValue += GC.getReligionInfo(eReligion).getHolyCityCommerce(iJ); 
}
iValue *= GC.getReligionInfo(eReligion).getSpreadFactor() / 100;
 
The order is determined by the commerce gain and the spread factor. If that is the same, then the order in the XML will determine which religion is picked.
You can change that by adding to this part of the code:
Code:
for (int iJ = 0; iJ < NUM_COMMERCE_TYPES; iJ++)
{
	iValue += GC.getReligionInfo(eReligion).getGlobalReligionCommerce(iJ) * 10;
	iValue += GC.getReligionInfo(eReligion).getHolyCityCommerce(iJ); 
}
iValue *= GC.getReligionInfo(eReligion).getSpreadFactor() / 100;

Thanks, this is helpful. Unfortunately, my C++ knowledge is very limited, if this were handled in Python I would have a much better shot at fixing this on my own.

In my mod, the commerce gain and spread factor are equal for all of the religions, hence them being picked in XML order. Could anyone add a few lines of code for me that would randomize the pick if all of the other factors are equal? I could do this in Python, but my C++ skills are just not up to par.
 
Just add a random number to iValue at the end of what I quoted. It is not too different from the way you would write that in Python.
 
Well, whaddya know, I think I got this to work: Here's the code I am using:

Code:
if (canConstruct(pLoopCity->plot(), (BuildingTypes)iI, false))
										{
											int iReligionRand = GC.getGameINLINE().getSorenRandNum(100, "Religion Randomiser");
											iValue = 0;
											for (int iJ = 0; iJ < NUM_COMMERCE_TYPES; iJ++)
											{
												iValue += GC.getReligionInfo(eReligion).getGlobalReligionCommerce(iJ) * 10;
												iValue += GC.getReligionInfo(eReligion).getHolyCityCommerce(iJ); 
											}
											iValue *= GC.getReligionInfo(eReligion).getSpreadFactor() / 100 + iReligionRand;

I don't know if this is the "correct" way to do this, but it seems to be working based on my very limited testing so far. As a bonus side effect, it sees to have made an improvement with my other issue, in that the AI wasn't using their Great Prophets enough to found religions. They seem to be making a higher priority now to found a religion.

Now my next issue is to try and figure out how to cap the max number of religions based on the world size. I will use the DefaultPlayers value (e.g. on a standard world 7 religions can be founded, on a large world 9 religions can be founded.).

Thanks, AIAndy!
 
I don't know if this is the "correct" way to do this, but it seems to be working based on my very limited testing so far. As a bonus side effect, it sees to have made an improvement with my other issue, in that the AI wasn't using their Great Prophets enough to found religions. They seem to be making a higher priority now to found a religion.
It should be their top priority for great prophets as it is the first thing they attempt with it.
One small thing: iValue *= in C++ actually means iValue = iValue *
So adding at the end there actually multiplies the random number into the value instead of adding it. That is no problem as long as your religions are equal anyway but if you change that in the future you might find it is too dominant, so best have it added in in a separate line:
iValue += iReligionRand;

Now my next issue is to try and figure out how to cap the max number of religions based on the world size. I will use the DefaultPlayers value (e.g. on a standard world 7 religions can be founded, on a large world 9 religions can be founded.).

Thanks, AIAndy!
You are probably best off adding that code to canConstruct so it has an effect on both the AI and the player.
 
It should be their top priority for great prophets as it is the first thing they attempt with it.
One small thing: iValue *= in C++ actually means iValue = iValue *
So adding at the end there actually multiplies the random number into the value instead of adding it. That is no problem as long as your religions are equal anyway but if you change that in the future you might find it is too dominant, so best have it added in in a separate line:
iValue += iReligionRand;


You are probably best off adding that code to canConstruct so it has an effect on both the AI and the player.

Great, thanks again for the tips. I am assuming this line in the code above:

if (canConstruct(pLoopCity->plot(), (BuildingTypes)iI, false))

will stop the AI from trying to found a religion if the max number of religions is reached, as long as the code to cap the # is placed under canConstruct.

You can see that I am still somewhat a newb at this. :)


This is the code that was added for this modcomp under CvCity: canConstruct

Code:
//<True Prophet Mod Start>
	ReligionTypes eReligion = (ReligionTypes)GC.getBuildingInfo(eBuilding).getFoundsReligion();
	if (eReligion != NO_RELIGION)
	{
		if (GC.getGameINLINE().isReligionFounded(eReligion))
		{
			return false;
		}

		for (int iReligion = 0; iReligion < GC.getNumReligionInfos(); ++iReligion)
		{
			if (isHolyCity((ReligionTypes)iReligion))
			{
				return false;
			}
		}
	}
	//<True Prophet Mod End>

If I am reading this correctly (a big if), the first part of the code says don't found the same religion more than once, and the second part says don't found a religion in the same city in which a religion has already been founded. If I am on the right track, would this be where I would add some code saying don't found the religion if the max # of religions has been reached?
 
Great, thanks again for the tips. I am assuming this line in the code above:

if (canConstruct(pLoopCity->plot(), (BuildingTypes)iI, false))

will stop the AI from trying to found a religion if the max number of religions is reached, as long as the code to cap the # is placed under canConstruct.

You can see that I am still somewhat a newb at this. :)
Yes, canConstruct is always called to check if a building can be constructed and somewhere in there you will probably find code that if the building can found a religion checks if that religion has already been founded. That would be a point to add code to check if the number of founded religions is already at the maximum you want.
 
If I am reading this correctly (a big if), the first part of the code says don't found the same religion more than once, and the second part says don't found a religion in the same city in which a religion has already been founded. If I am on the right track, would this be where I would add some code saying don't found the religion if the max # of religions has been reached?
Correct.
You could use the same for loop together with isReligionFounded to count the founded religions and then if the count is greater than the max # return false.
 
OK, here is what I've come up with so far. It compiles correctly, but doesn't seem to work:

Code:
ReligionTypes eReligion = (ReligionTypes)GC.getBuildingInfo(eBuilding).getFoundsReligion();
	if (eReligion != NO_RELIGION)
	{
		if (GC.getGameINLINE().isReligionFounded(eReligion))
		{
			return false;
		}

		iFounded = 0;

		for (int iReligion = 0; iReligion < GC.getNumReligionInfos(); ++iReligion)
		{
			if (isHolyCity((ReligionTypes)iReligion))
			{
				return false;
			}

			if (GC.getGameINLINE().isReligionFounded(eReligion))
			{
				iFounded = iFounded + 1;

				if (iFounded >= GC.getWorldInfo(GC.getMapINLINE().getWorldSize()).getDefaultPlayers() )
				{
					return false;
				}
			}
		}
	}

What I aimed to do:

I created an int to track the number of religions founded, iFounded. I defined it as 0, and then in the loop had it add 1 for each founded religion. When iFounded is greater or equal to the DefaultPlayers value from WorldInfos.xml, don't allow any more religions to be founded.

I think my logic is solid, but I suspect I have something in the wrong place.

I want to thank you again for your help; you are giving me just the right amount of help without doing it for me and I appreciate that. Give a man a fish, and all that stuff. :)
 
Back
Top Bottom