Skip to content

Commit

Permalink
Fix AI frivolously disbanding workers occasionally (#11222)
Browse files Browse the repository at this point in the history
Simplify worker disbanding logic. Will now only disband workers when there's more idle workers than they have cities.

Also improve worker spreading out logic when there's nothing left to build.
  • Loading branch information
KungCheops authored Aug 12, 2024
1 parent 80572a8 commit 0302e1e
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 95 deletions.
112 changes: 29 additions & 83 deletions CvGameCoreDLL_Expansion2/CvEconomicAI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2724,48 +2724,22 @@ void CvEconomicAI::DisbandExtraArchaeologists(){

void CvEconomicAI::DisbandExtraWorkers()
{
// Are we running at a deficit?
EconomicAIStrategyTypes eStrategyLosingMoney = (EconomicAIStrategyTypes) GC.getInfoTypeForString("ECONOMICAISTRATEGY_LOSING_MONEY");
bool bInDeficit = m_pPlayer->GetEconomicAI()->IsUsingStrategy(eStrategyLosingMoney);

double fWorstCaseRatio = 0.25; // one worker for four cities
int iNumWorkers = m_pPlayer->GetNumUnitsWithUnitAI(UNITAI_WORKER, true);
if(iNumWorkers <= 0)
if(iNumWorkers <= 1)
return;

int iNumCities = m_pPlayer->getNumCities();

//If we want workers in any city, don't disband.
AICityStrategyTypes eWantWorkers = (AICityStrategyTypes) GC.getInfoTypeForString("AICITYSTRATEGY_WANT_TILE_IMPROVERS");
int iLoopCity = 0;
int iNumCitiesWithStrat = 0;
CvCity* pLoopCity = NULL;
for(pLoopCity = m_pPlayer->firstCity(&iLoopCity); pLoopCity != NULL; pLoopCity = m_pPlayer->nextCity(&iLoopCity))
{
if(pLoopCity != NULL)
{
if(eWantWorkers != NO_AICITYSTRATEGY)
{
if(pLoopCity->GetCityStrategyAI()->IsUsingCityStrategy(eWantWorkers))
{
iNumCitiesWithStrat++;
}
}
}
}
//# of cities needing workers greater than number of workers? don't disband.
if(iNumCitiesWithStrat >= iNumWorkers)
{
return;
}
//Don't disband during the early game.
if(m_pPlayer->GetNumCitiesFounded() < 4 && (GC.getGame().getGameTurn() <= 100))
if (m_pPlayer->GetNumCitiesFounded() < 4 && (GC.getGame().getGameTurn() <= 100))
{
return;
}

double fCurrentRatio = iNumWorkers / (double)iNumCities;
if(fCurrentRatio <= fWorstCaseRatio || iNumWorkers == 1)
int iNumCities = m_pPlayer->getNumCities();

int iWorstCaseWorkerPerCityRatio = 25; // one worker for four cities
int iCurrentWorkerPerCityRatio = (100 * iNumWorkers) / iNumCities;

if(iCurrentWorkerPerCityRatio <= iWorstCaseWorkerPerCityRatio)
{
return;
}
Expand All @@ -2781,83 +2755,55 @@ void CvEconomicAI::DisbandExtraWorkers()
{
continue;
}
if(pPlot->isWater() || !pPlot->isValidMovePlot(m_pPlayer->GetID()) || pPlot->isCity())
if (pPlot->isWater() || !pPlot->isValidMovePlot(m_pPlayer->GetID()) || pPlot->isCity())
{
continue;
}

iNumValidPlots++;

if(pPlot->getImprovementType() != NO_IMPROVEMENT && !pPlot->IsImprovementPillaged())
if (pPlot->getImprovementType() != NO_IMPROVEMENT && !pPlot->IsImprovementPillaged())
{
iNumImprovedPlots++;
}
}

// potential div by zero
if(iNumValidPlots <= 0)
{
if (iNumValidPlots == 0)
return;
}

int iNumUnimprovedPlots = iNumValidPlots - iNumImprovedPlots;
int iUnimprovedPlotPerWorkers = 8;
int iMinWorkers = iNumUnimprovedPlots / iUnimprovedPlotPerWorkers;

if(iMinWorkers <= 0)
{
iMinWorkers = 1;
}

// less than two thirds of the plots are improved, don't discard anybody
float fRatio = iNumImprovedPlots / (float)iNumValidPlots;
if(fRatio < 0.66)
{
iMinWorkers += 2;
}

if((iNumUnimprovedPlots % iUnimprovedPlotPerWorkers) > 0)
{
iMinWorkers += 1;
}

if(!bInDeficit)
{
iMinWorkers += 1;
}

CvCity* pCapital = m_pPlayer->getCapitalCity();
if(!pCapital)
{
int iImprovedPlotsRatio = (100 * iNumImprovedPlots) / iNumValidPlots;
if (iImprovedPlotsRatio <= 66)
return;
}

int iLoop = 0;
for (CvCity* pCity = m_pPlayer->firstCity(&iLoop); pCity != NULL; pCity = m_pPlayer->nextCity(&iLoop))
// How many idle workers do we have?
int iIdleWorkers = 0;

int iLoopUnit = 0;
for (CvUnit* pLoopUnit = m_pPlayer->firstUnit(&iLoopUnit); pLoopUnit != NULL; pLoopUnit = m_pPlayer->nextUnit(&iLoopUnit))
{
if (pCity == pCapital)
{
if (!pLoopUnit)
continue;
}

if (pCapital->HasSharedAreaWith(pCity,true,false) && !pCity->IsRouteToCapitalConnected())
static const UnitTypes eWorker = m_pPlayer->GetSpecificUnitType("UNITCLASS_WORKER");

if (pLoopUnit->getDomainType() == DOMAIN_LAND && pLoopUnit->getUnitType() == eWorker && !pLoopUnit->IsCombatUnit() && pLoopUnit->getSpecialUnitType() == NO_SPECIALUNIT)
{
iMinWorkers += 1;
CvPlot* pUnitPlot = pLoopUnit->plot();
if (pUnitPlot && pUnitPlot->isCity())
iIdleWorkers++;
}
}


if (iNumWorkers <= iMinWorkers)
{
// If we have more than one idle worker per city, start disbanding
if (iIdleWorkers <= iNumCities)
return;
}

m_iLastTurnWorkerDisbanded = GC.getGame().getGameTurn();

CvUnit* pUnit = FindWorkerToScrap();
if (!pUnit)
return;

m_iLastTurnWorkerDisbanded = GC.getGame().getGameTurn();

pUnit->scrap();
LogScrapUnit(pUnit, iNumWorkers, iNumCities, iNumImprovedPlots, iNumValidPlots);
}
Expand Down
25 changes: 13 additions & 12 deletions CvGameCoreDLL_Expansion2/CvHomelandAI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3330,10 +3330,10 @@ void CvHomelandAI::ExecuteWorkerMoves()

//may need this later
// cityId to needValue
map<int, int> mapCityNeed;
map<int, int> mapCityAssignedWorkers;
int iLoop = 0;
for (CvCity* pLoopCity = m_pPlayer->firstCity(&iLoop); pLoopCity != NULL; pLoopCity = m_pPlayer->nextCity(&iLoop))
mapCityNeed[pLoopCity->GetID()] = pLoopCity->GetTerrainImprovementNeed();
mapCityAssignedWorkers[pLoopCity->GetID()] = 0;

for (list<int>::iterator it = allWorkers.begin(); it != allWorkers.end(); ++it)
{
Expand All @@ -3342,24 +3342,25 @@ void CvHomelandAI::ExecuteWorkerMoves()
continue;

//find the city which is most in need of workers
int iMaxNeed = -100;
int iLeastAssignedWorkers = INT_MAX;
int iClosestCityDistance = INT_MAX;
CvCity* pBestCity = NULL;
for (map<int, int>::iterator it2 = mapCityNeed.begin(); it2 != mapCityNeed.end(); ++it2)
for (map<int, int>::iterator it2 = mapCityAssignedWorkers.begin(); it2 != mapCityAssignedWorkers.end(); ++it2)
{
if (it2->second > iMaxNeed)
CvCity* pCity = m_pPlayer->getCity(it2->first);
int iAssignedWorkers = it2->second;
int iCityDistance = plotDistance(pUnit->getX(), pUnit->getY(), pCity->getX(), pCity->getY());
if (iAssignedWorkers < iLeastAssignedWorkers || (iAssignedWorkers == iLeastAssignedWorkers && iCityDistance < iClosestCityDistance))
{
iMaxNeed = it2->second;
pBestCity = m_pPlayer->getCity(it2->first);
iLeastAssignedWorkers = iAssignedWorkers;
iClosestCityDistance = iCityDistance;
pBestCity = pCity;
}
}

if (pBestCity && ExecuteMoveToTarget(pUnit, pBestCity->plot(), CvUnit::MOVEFLAG_NO_ENEMY_TERRITORY | CvUnit::MOVEFLAG_PRETEND_ALL_REVEALED, true))
{
int iCurrentNeed = mapCityNeed[pBestCity->GetID()];
if (iCurrentNeed > 0)
mapCityNeed[pBestCity->GetID()] = iCurrentNeed / 2; //reduce the score for this city in case we have multiple workers to distribute
else
mapCityNeed[pBestCity->GetID()]--; //in case all cities have all tiles improved, try spread the workers over all our cities
mapCityAssignedWorkers[pBestCity->GetID()]++;
}
else if (pUnit->IsCivilianUnit())
{
Expand Down

0 comments on commit 0302e1e

Please sign in to comment.