CvPlot::canTrain(...) WHY?

Afforess

The White Wizard
Joined
Jul 31, 2007
Messages
12,239
Location
Austin, Texas
I'm curious, why is there a canTrain(...) function in CvPlot. Plots can't build units. Only cities can. Shouldn't it all be in the city code? Or is there some reason I am missing that it is quicker to call a plot's function 4000+ times on a standard map, then ~100 times for each city.

Can someone enlighten me to Firaxis's wisdom here?
 
Is that part of vanilla BTS, or is it possible that it was added by a mod? I only have one version of the SDK that isn't modified (version 1.61) and that function doesn't show up there.
 
Is that part of vanilla BTS, or is it possible that it was added by a mod? I only have one version of the SDK that isn't modified (version 1.61) and that function doesn't show up there.

It's in BTS. Line 9469.
Spoiler :

Code:
bool CvPlot::canTrain(UnitTypes eUnit, bool bContinue, bool bTestVisible) const
{
	CvCity* pCity = getPlotCity();

	if (GC.getUnitInfo(eUnit).isPrereqReligion())
	{
		if (NULL == pCity || pCity->getReligionCount() > 0)
		{
			return false;
		}
	}

	if (GC.getUnitInfo(eUnit).getPrereqReligion() != NO_RELIGION)
	{
		if (NULL == pCity || !pCity->isHasReligion((ReligionTypes)(GC.getUnitInfo(eUnit).getPrereqReligion())))
		{
			return false;
		}
	}

	if (GC.getUnitInfo(eUnit).getPrereqCorporation() != NO_CORPORATION)
	{
		if (NULL == pCity || !pCity->isActiveCorporation((CorporationTypes)(GC.getUnitInfo(eUnit).getPrereqCorporation())))
		{
			return false;
		}
	}

	if (GC.getUnitInfo(eUnit).isPrereqBonuses())
	{
		if (GC.getUnitInfo(eUnit).getDomainType() == DOMAIN_SEA)
		{
			bool bValid = false;

			for (int iI = 0; iI < NUM_DIRECTION_TYPES; ++iI)
			{
				CvPlot* pLoopPlot = plotDirection(getX_INLINE(), getY_INLINE(), ((DirectionTypes)iI));

				if (pLoopPlot != NULL)
				{
					if (pLoopPlot->isWater())
					{
						if (pLoopPlot->area()->getNumTotalBonuses() > 0)
						{
							bValid = true;
							break;
						}
					}
				}
			}

			if (!bValid)
			{
				return false;
			}
		}
		else
		{
			if (area()->getNumTotalBonuses() > 0)
			{
				return false;
			}
		}
	}

	if (isCity())
	{
		if (GC.getUnitInfo(eUnit).getDomainType() == DOMAIN_SEA)
		{
			if (!isWater() && !isCoastalLand(GC.getUnitInfo(eUnit).getMinAreaSize()))
			{
				return false;
			}
		}
		else
		{
			if (area()->getNumTiles() < GC.getUnitInfo(eUnit).getMinAreaSize())
			{
				return false;
			}
		}
	}
	else
	{
		if (area()->getNumTiles() < GC.getUnitInfo(eUnit).getMinAreaSize())
		{
			return false;
		}

		if (GC.getUnitInfo(eUnit).getDomainType() == DOMAIN_SEA)
		{
			if (!isWater())
			{
				return false;
			}
		}
		else if (GC.getUnitInfo(eUnit).getDomainType() == DOMAIN_LAND)
		{
			if (isWater())
			{
				return false;
			}
		}
		else
		{
			return false;
		}
	}

	if (!bTestVisible)
	{
		if (GC.getUnitInfo(eUnit).getHolyCity() != NO_RELIGION)
		{
			if (NULL == pCity || !pCity->isHolyCity(((ReligionTypes)(GC.getUnitInfo(eUnit).getHolyCity()))))
			{
				return false;
			}
		}

		if (GC.getUnitInfo(eUnit).getPrereqBuilding() != NO_BUILDING)
		{
			if (NULL == pCity)
			{
				return false;
			}

			if (pCity->getNumBuilding((BuildingTypes)(GC.getUnitInfo(eUnit).getPrereqBuilding())) == 0)
			{
				SpecialBuildingTypes eSpecialBuilding = ((SpecialBuildingTypes)(GC.getBuildingInfo((BuildingTypes)(GC.getUnitInfo(eUnit).getPrereqBuilding())).getSpecialBuildingType()));

				if ((eSpecialBuilding == NO_SPECIALBUILDING) || !(GET_PLAYER(getOwnerINLINE()).isSpecialBuildingNotRequired(eSpecialBuilding)))
				{
					return false;
				}
			}
		}

		if (GC.getUnitInfo(eUnit).getPrereqAndBonus() != NO_BONUS)
		{
			if (NULL == pCity)
			{
				if (!isPlotGroupConnectedBonus(getOwnerINLINE(), (BonusTypes)GC.getUnitInfo(eUnit).getPrereqAndBonus()))
				{
					return false;
				}
			}
			else
			{
				if (!pCity->hasBonus((BonusTypes)GC.getUnitInfo(eUnit).getPrereqAndBonus()))
				{
					return false;
				}
			}
		}

		bool bRequiresBonus = false;
		bool bNeedsBonus = true;

		for (int iI = 0; iI < GC.getNUM_UNIT_PREREQ_OR_BONUSES(); ++iI)
		{
			if (GC.getUnitInfo(eUnit).getPrereqOrBonuses(iI) != NO_BONUS)
			{
				bRequiresBonus = true;

				if (NULL == pCity)
				{
					if (isPlotGroupConnectedBonus(getOwnerINLINE(), (BonusTypes)GC.getUnitInfo(eUnit).getPrereqOrBonuses(iI)))
					{
						bNeedsBonus = false;
						break;
					}
				}
				else
				{
					if (pCity->hasBonus((BonusTypes)GC.getUnitInfo(eUnit).getPrereqOrBonuses(iI)))
					{
						bNeedsBonus = false;
						break;
					}
				}
			}
		}

		if (bRequiresBonus && bNeedsBonus)
		{
			return false;
		}
	}

	return true;
}
 
Ahh.. the makes sense. Still seems expensive to call for every plot every turn. Shouldn't it call just for each plot a unit is on at the start of each turn?
 
Did you trace where it is called? I imagine there is a loop over units, which checks to see if the unit can be upgraded. So for most (unoccupied) plots it is never called, but for a plot with multiple upgradeable units, it might be called multiple times for the same plot.
 
Did you trace where it is called? I imagine there is a loop over units, which checks to see if the unit can be upgraded. So for most (unoccupied) plots it is never called, but for a plot with multiple upgradeable units, it might be called multiple times for the same plot.

You're right, CvPlot::canTrain is called a lot more than once every turn. I'm measuring ~ 100000 calls in 1 turn during the late game. :eek:
 
It seems I must offer a retraction. I just glanced at the code to see if there was a quick and easy way to reduce the number of calls, or if it was structured to be a fast function so didn't matter if it was called plentifully.

Turns out that the only place I can find that actually does call this function is in Advanced Start to find the cost of a unit, and in CvCity::canUpgrade. Thus once beyond advanced start, this will never be called on a non-city tile.

The routine to find where to upgrade for the AI also only works through cities. Though quite likely that is the source of your issue for so many calls anyway. You'd want to restructure starting from within that routine, either re-organizing to get a more selective and quicker check to be higher priority, or by changing the ease with which it can clear the checks already performed first (like CvPlayer::canUpgrade). One fairly simple thing you could do is a simple check for the distance to a potential upgrade city and deny it outright if too far away, though accounting for airlifts may make that a difficult decision.

Adding a function along the lines of CvUnitInfo::isUpgradeInCity() may also prove worthwhile, as if an upgrade doesn't REQUIRE a city, the functions will still wind up checking the cities, it just means it'll get a "yes" from the closest one. I am actually not sure if the AI will still attempt to walk back to the city in order to upgrade, or if it is smart enough to upgrade in the field when possible (Is it ever possible in base BtS?)
 
Top Bottom