Request for AI Investigation: Civics

Discussion in 'Civ4 - Creation & Customization' started by Padmewan, May 16, 2006.

  1. Padmewan

    Padmewan King

    Joined:
    Nov 26, 2003
    Messages:
    748
    Location:
    Planet
    Following up on my thread inquiring about how the AI makes decisions on building improvements, can anyone help us figure out how the AI makes decisions on civics? (I've noticed the AI switch to slavery or theocracy when under pressure, so there must be some logic to it, but there are so many possible variables with civics that it's hard to imagine how the calculation is derived).

    While I'm at it, any insight into religious conversion would be greatly appreciated as well!

    btw regarding that earlier thread, I've confirmed that the AI does NOT account for civics when building improvements. I've watched the AI dismantle improvements that are superior under its current civic in favor of inferior ones (but which are "naturally" better).
     
  2. The Great Apple

    The Great Apple Big Cheese

    Joined:
    Mar 24, 2002
    Messages:
    3,361
    Location:
    Oxford, England
    There are a lot of factors:
    Spoiler lots of code :
    Code:
    CivicTypes CvPlayerAI::AI_bestCivic(CivicOptionTypes eCivicOption)
    {
    	CivicTypes eBestCivic;
    	int iValue;
    	int iBestValue;
    	int iI;
    
    	iBestValue = MIN_INT;
    	eBestCivic = NO_CIVIC;
    
    	for (iI = 0; iI < GC.getNumCivicInfos(); iI++)
    	{
    		if (GC.getCivicInfo((CivicTypes)iI).getCivicOptionType() == eCivicOption)
    		{
    			if (canDoCivics((CivicTypes)iI))
    			{
    				iValue = AI_civicValue((CivicTypes)iI);
    
    				if (isCivic((CivicTypes)iI))
    				{
    					iValue *= 6;
    					iValue /= 5;
    				}
    
    				if (iValue > iBestValue)
    				{
    					iBestValue = iValue;
    					eBestCivic = ((CivicTypes)iI);
    				}
    			}
    		}
    	}
    
    	return eBestCivic;
    }
    
    int CvPlayerAI::AI_civicValue(CivicTypes eCivic)
    {
    	PROFILE_FUNC();
    
    	bool bWarPlan;
    	int iConnectedForeignCities;
    	int iTotalReligonCount;
    	int iHighestReligionCount;
    	int iHappiness;
    	int iValue;
    	int iTempValue;
    	int iI, iJ;
    
    	FAssertMsg(eCivic < GC.getNumCivicInfos(), "eCivic is expected to be within maximum bounds (invalid Index)");
    	FAssertMsg(eCivic >= 0, "eCivic is expected to be non-negative (invalid Index)");
    
    	bWarPlan = (GET_TEAM(getTeam()).getAnyWarPlanCount(true) > 0);
    
    	iConnectedForeignCities = countPotentialForeignTradeCitiesConnected();
    	iTotalReligonCount = countTotalHasReligion();
    	iHighestReligionCount = findHighestHasReligionCount();
    
    	iValue = (getNumCities() * 6);
    
    	iValue += (GC.getCivicInfo(eCivic).getAIWeight() * getNumCities());
    
    	iValue += (getCivicPercentAnger(eCivic) / 10);
    
    	iValue += -(GC.getCivicInfo(eCivic).getAnarchyLength() * getNumCities());
    
    	iValue += -(getSingleCivicUpkeep(eCivic, true));
    
    	iValue += ((GC.getCivicInfo(eCivic).getGreatPeopleRateModifier() * getNumCities()) / 10);
    	iValue += -((GC.getCivicInfo(eCivic).getDistanceMaintenanceModifier() * max(0, (getNumCities() - 3))) / 8);
    	iValue += -((GC.getCivicInfo(eCivic).getNumCitiesMaintenanceModifier() * max(0, (getNumCities() - 3))) / 8);
    	iValue += ((GC.getCivicInfo(eCivic).getExtraHealth() * getTotalPopulation()) / 16);
    	iValue += (GC.getCivicInfo(eCivic).getFreeExperience() * getNumCities() * ((bWarPlan) ? 6 : 2));
    	iValue += ((GC.getCivicInfo(eCivic).getWorkerSpeedModifier() * AI_getNumAIUnits(UNITAI_WORKER)) / 4);
    	iValue += ((GC.getCivicInfo(eCivic).getImprovementUpgradeRateModifier() * getNumCities()) / 50);
    	iValue += ((GC.getCivicInfo(eCivic).getMilitaryProductionModifier() * getNumCities()) / ((bWarPlan) ? 5 : 20));
    	iValue += (GC.getCivicInfo(eCivic).getBaseFreeUnits() / 2);
    	iValue += (GC.getCivicInfo(eCivic).getBaseFreeMilitaryUnits() / 3);
    	iValue += ((GC.getCivicInfo(eCivic).getFreeUnitsPopulationPercent() * getTotalPopulation()) / 200);
    	iValue += ((GC.getCivicInfo(eCivic).getFreeMilitaryUnitsPopulationPercent() * getTotalPopulation()) / 300);
    	iValue += -(GC.getCivicInfo(eCivic).getGoldPerUnit() * getNumUnits());
    	iValue += -(GC.getCivicInfo(eCivic).getGoldPerMilitaryUnit() * getNumMilitaryUnits());
    	iValue += ((GC.getCivicInfo(eCivic).getHappyPerMilitaryUnit() * getTotalPopulation()) / 5);
    	//iValue += ((GC.getCivicInfo(eCivic).isMilitaryFoodProduction()) ? 0 : 0);
    	iValue += (getWorldSizeMaxConscript(eCivic) * ((bWarPlan) ? 8 : 2));
    	iValue += ((GC.getCivicInfo(eCivic).isNoUnhealthyPopulation()) ? (getTotalPopulation() / 3) : 0);
    	iValue += ((GC.getCivicInfo(eCivic).isBuildingOnlyHealthy()) ? (getNumCities() * 3) : 0);
    	iValue += (GC.getCivicInfo(eCivic).getLargestCityHappiness() * min(getNumCities(), GC.getWorldInfo(GC.getMapINLINE().getWorldSize()).getTargetNumCities()) * 6);
    	iValue += -((GC.getCivicInfo(eCivic).getWarWearinessModifier() * getNumCities()) / ((bWarPlan) ? 20 : 100));
    	iValue += (GC.getCivicInfo(eCivic).getFreeSpecialist() * getNumCities() * 12);
    	iValue += (GC.getCivicInfo(eCivic).getTradeRoutes() * max(getNumCities(), iConnectedForeignCities) * 6);
    	iValue += -((GC.getCivicInfo(eCivic).isNoForeignTrade()) ? (iConnectedForeignCities * 3) : 0);
    	iValue += ((GC.getCivicInfo(eCivic).getCivicPercentAnger() * (GC.getGameINLINE().getNumCities() - getNumCities())) / 10);
    	iValue += (GC.getCivicInfo(eCivic).getNonStateReligionHappiness() * (iTotalReligonCount - iHighestReligionCount) * 5);
    
    	if (GC.getCivicInfo(eCivic).isStateReligion())
    	{
    		if (iHighestReligionCount > 0)
    		{
    			iValue += iHighestReligionCount;
    
    			iValue += ((GC.getCivicInfo(eCivic).isNoNonStateReligionSpread()) ? ((getNumCities() - iHighestReligionCount) * 2) : 0);
    			iValue += (GC.getCivicInfo(eCivic).getStateReligionHappiness() * iHighestReligionCount * 4);
    			iValue += ((GC.getCivicInfo(eCivic).getStateReligionGreatPeopleRateModifier() * iHighestReligionCount) / 8);
    			iValue += ((GC.getCivicInfo(eCivic).getStateReligionUnitProductionModifier() * iHighestReligionCount) / 4);
    			iValue += ((GC.getCivicInfo(eCivic).getStateReligionBuildingProductionModifier() * iHighestReligionCount) / 3);
    			iValue += (GC.getCivicInfo(eCivic).getStateReligionFreeExperience() * iHighestReligionCount * ((bWarPlan) ? 6 : 2));
    		}
    	}
    
    	for (iI = 0; iI < NUM_YIELD_TYPES; iI++)
    	{
    		iTempValue = 0;
    
    		iTempValue += ((GC.getCivicInfo(eCivic).getYieldModifier(iI) * getNumCities()) / 2);
    		iTempValue += ((GC.getCivicInfo(eCivic).getCapitalYieldModifier(iI) * 3) / 4);
    		iTempValue += ((GC.getCivicInfo(eCivic).getTradeYieldModifier(iI) * getNumCities()) / 6);
    
    		for (iJ = 0; iJ < GC.getNumImprovementInfos(); iJ++)
    		{
    			iTempValue += (GC.getCivicInfo(eCivic).getImprovementYieldChanges(iJ, iI) * getImprovementCount((ImprovementTypes)iJ) * 3);
    		}
    
    		iTempValue *= AI_yieldWeight((YieldTypes)iI);
    		iTempValue /= 100;
    
    		iValue += iTempValue;
    	}
    
    	for (iI = 0; iI < NUM_COMMERCE_TYPES; iI++)
    	{
    		iTempValue = 0;
    
    		iTempValue += ((GC.getCivicInfo(eCivic).getCommerceModifier(iI) * getNumCities()) / 3);
    		iTempValue += (GC.getCivicInfo(eCivic).getCapitalCommerceModifier(iI) / 2);
    		iTempValue += ((GC.getCivicInfo(eCivic).getSpecialistExtraCommerce(iI) * getTotalPopulation()) / 6);
    
    		iTempValue *= AI_commerceWeight((CommerceTypes)iI);
    		iTempValue /= 100;
    
    		iValue += iTempValue;
    	}
    
    	for (iI = 0; iI < GC.getNumBuildingInfos(); iI++)
    	{
    		if (GC.getCivicInfo(eCivic).getBuildingHappinessChanges(iI) != 0)
    		{
    			iValue += (GC.getCivicInfo(eCivic).getBuildingHappinessChanges(iI) * countNumBuildings((BuildingTypes)iI) * 4);
    		}
    	}
    
    	for (iI = 0; iI < GC.getNumFeatureInfos(); iI++)
    	{
    		iHappiness = GC.getCivicInfo(eCivic).getFeatureHappinessChanges(iI);
    
    		if (iHappiness != 0)
    		{
    			iValue += (iHappiness * countCityFeatures((FeatureTypes)iI) * 3);
    		}
    	}
    
    	for (iI = 0; iI < GC.getNumHurryInfos(); iI++)
    	{
    		if (GC.getCivicInfo(eCivic).isHurry(iI))
    		{
    			iTempValue = 0;
    
    			if (GC.getHurryInfo((HurryTypes)iI).getGoldPerProduction() > 0)
    			{
    				iTempValue += ((((AI_avoidScience()) ? 50 : 25) * getNumCities()) / GC.getHurryInfo((HurryTypes)iI).getGoldPerProduction());
    			}
    			iTempValue += ((GC.getHurryInfo((HurryTypes)iI).getProductionPerPopulation() * getNumCities()) / 5);
    
    			iValue += iTempValue;
    		}
    	}
    
    	for (iI = 0; iI < GC.getNumSpecialBuildingInfos(); iI++)
    	{
    		if (GC.getCivicInfo(eCivic).isSpecialBuildingNotRequired(iI))
    		{
    			iValue += ((getNumCities() / 2) + 1); // XXX
    		}
    	}
    
    	for (iI = 0; iI < GC.getNumSpecialistInfos(); iI++)
    	{
    		if (GC.getCivicInfo(eCivic).isSpecialistValid(iI))
    		{
    			iValue += ((getNumCities() / 3) + 1); // XXX
    		}
    	}
    
    	if (GC.getLeaderHeadInfo(getPersonalityType()).getFavoriteCivic() == eCivic)
    	{
    		iValue += (getNumCities() * 25);
    	}
    
    	return iValue;
    }

    If you want me to explain any lines or chunks just yell, and I'll explain away. It'd take quite a while to explain it all in detail.
     
  3. Padmewan

    Padmewan King

    Joined:
    Nov 26, 2003
    Messages:
    748
    Location:
    Planet
    Interesting. Re: Civic effects on improvement yields (the part I really wanted to know), it looks like the AI actually counts how many improvements it has of the types that will benefit, measures how important that yield is, puts in in the context of all other yields (capital, trade), and weighs it accordingly.

    Code:
    	for (iI = 0; iI < NUM_YIELD_TYPES; iI++)
    	{
    		iTempValue = 0;
    
    		iTempValue += ((GC.getCivicInfo(eCivic).getYieldModifier(iI) * getNumCities()) / 2);
    		iTempValue += ((GC.getCivicInfo(eCivic).getCapitalYieldModifier(iI) * 3) / 4);
    		iTempValue += ((GC.getCivicInfo(eCivic).getTradeYieldModifier(iI) * getNumCities()) / 6);
    
    		for (iJ = 0; iJ < GC.getNumImprovementInfos(); iJ++)
    		{
    			iTempValue += (GC.getCivicInfo(eCivic).getImprovementYieldChanges(iJ, iI) * getImprovementCount((ImprovementTypes)iJ) * 3);
    		}
    
    		iTempValue *= AI_yieldWeight((YieldTypes)iI);
    		iTempValue /= 100;
    
    		iValue += iTempValue;
    	}
    
    In wonder if this is the reason why the AI does not take civic effects on improvements into account when choosing improvements -- if it did, it could "dig itself into a hole" where the more improvements it has, the more it wants to stick with a civic, so that it will never, ever change. Does that make sense?
     
  4. Impaler[WrG]

    Impaler[WrG] Civ4:Col UI programmer

    Joined:
    Dec 5, 2005
    Messages:
    1,750
    Location:
    Vallejo, California
    I noticed these functions and though to my self, all thouse *2 and /5's at the end of each line are what realy controls the AI and they should be exposed at the XML layer. They could even be replaced with function calls to make AI's that are unique in their desision making.
     
  5. Padmewan

    Padmewan King

    Joined:
    Nov 26, 2003
    Messages:
    748
    Location:
    Planet
    I imagine these magic numbers (as I think we called them in CS) are specially-tuned to default Civ settings on such factors as terrain yield, etc., and based on the relatively intelligent AI behavior (at least compared to past Civs) I think they aren't bad.

    The problem is when you start bending the entire model in ways the original programmers didn't anticipate. For example, in the case of improvements, what if in your mod hammers are more valuable than food than in vanilla? How do you tweak the AI to understand this? How do you know how to tweak the AI in the first place? (From Chalid's investigations it turns out improvement decisions do take into account the XML values for how much food is needed to support each pop, etc...)

    I think the developers made an end-run around this by having leaders with preferred civics and preferred improvements. If that system of preferences was just a tad more subtle, perhaps it would be an OK system -- my guess is that your substituted judgment as a mod developer will be superior to creating "true" intelligence, at least in proportion to the time you'd spend...
     
  6. Optimizer

    Optimizer Sthlm, SWE

    Joined:
    Dec 29, 2001
    Messages:
    692
    Thanks a lot for showing these formulae! It will help a lot in my strife to make a new civic mod.

    Where can I find them?
     
  7. The Great Apple

    The Great Apple Big Cheese

    Joined:
    Mar 24, 2002
    Messages:
    3,361
    Location:
    Oxford, England
    SDK. CvPlayerAI - just run a search for it in there.
     

Share This Page