|
From: <imp...@us...> - 2007-03-14 18:47:37
|
Revision: 43
http://civ4ccp.svn.sourceforge.net/civ4ccp/?rev=43&view=rev
Author: impalerwrg
Date: 2007-03-14 11:46:06 -0700 (Wed, 14 Mar 2007)
Log Message:
-----------
TR merging, adding AI Defines, lots of AI code
Modified Paths:
--------------
CvGameCoreDLL/CvCityAI.cpp
CvGameCoreDLL/CvCityAI.h
CvGameCoreDLL/CvGameCoreDLL.cbp
CvGameCoreDLL/CvGameCoreDLL.depend
CvGameCoreDLL/CvGameCoreDLL.layout
CvGameCoreDLL/CvGameCoreUtils.cpp
CvGameCoreDLL/CvGameCoreUtils.h
CvGameCoreDLL/CvGameTextMgr.cpp
CvGameCoreDLL/CvPlayerAI.cpp
CvGameCoreDLL/CvPlayerAI.h
CvGameCoreDLL/CvSelectionGroup.h
CvGameCoreDLL/CvUnit.cpp
CvGameCoreDLL/CvUnit.h
CvGameCoreDLL/CvUnitAI.h
Added Paths:
-----------
CvGameCoreDLL/AI_Defines.h
Added: CvGameCoreDLL/AI_Defines.h
===================================================================
--- CvGameCoreDLL/AI_Defines.h (rev 0)
+++ CvGameCoreDLL/AI_Defines.h 2007-03-14 18:46:06 UTC (rev 43)
@@ -0,0 +1,36 @@
+#ifndef AI_DEFINES_H
+#define AI_DEFINES_H
+
+#define DEFAULT_PLAYER_CLOSENESS 6
+#define AI_DAGGER_THRESHOLD 100 //higher is a lower chance
+
+#define AI_DEFAULT_STRATEGY (1 << 0)
+#define AI_STRATEGY_DAGGER (1 << 1)
+#define AI_STRATEGY_SLEDGEHAMMER (1 << 2)
+#define AI_STRATEGY_CASTLE (1 << 3)
+#define AI_STRATEGY_FASTMOVERS (1 << 4)
+#define AI_STRATEGY_SLOWMOVERS (1 << 5)
+#define AI_STRATEGY_CULTURE1 (1 << 6) //religions and wonders
+#define AI_STRATEGY_CULTURE2 (1 << 7) //mass culture buildings
+#define AI_STRATEGY_CULTURE3 (1 << 8) //culture slider
+#define AI_STRATEGY_CULTURE4 (1 << 9)
+#define AI_STRATEGY_MISSIONARY (1 << 10)
+#define AI_STRATEGY_CRUSH (1 << 11) //convert units to City Attack
+#define AI_STRATEGY_PRODUCTION (1 << 12)
+#define AI_STRATEGY_PEACE (1 << 13) //lucky... neglect defenses.
+#define AI_STRATEGY_GET_BETTER_UNITS (1 << 14)
+
+
+#define AI_CITY_ROLE_VALID (1 << 1) //zero is bad
+#define AI_CITY_ROLE_BIG_CULTURE (1 << 2) //culture victory, probably
+#define AI_CITY_ROLE_BIG_PRODUCTION (1 << 3) //don't build girly NW's
+#define AI_CITY_ROLE_BIG_MILITARY (1 << 4) //stick with military stuff
+#define AI_CITY_ROLE_SCIENCE (1 << 5) //
+#define AI_CITY_ROLE_GOLD (1 << 6) //
+#define AI_CITY_ROLE_PRODUCTION (1 << 7) //
+#define AI_CITY_ROLE_SPECIALIST (1 << 8) //
+#define AI_CITY_ROLE_FISHING (1 << 9) //
+#define AI_CITY_ROLE_STAGING (1 << 10) //send troops here
+#define AI_CITY_ROLE_LICHPIN (1 << 11) //this city must not fall
+
+#endif // AI_DEFINES_H
Modified: CvGameCoreDLL/CvCityAI.cpp
===================================================================
--- CvGameCoreDLL/CvCityAI.cpp 2007-03-14 05:15:29 UTC (rev 42)
+++ CvGameCoreDLL/CvCityAI.cpp 2007-03-14 18:46:06 UTC (rev 43)
@@ -12,6 +12,7 @@
#include "CyCity.h"
#include "CyArgsList.h"
#include "CvInfos.h"
+//#include "CvExtraSaveData.h"
#include "FProfiler.h"
#include "CvDLLPythonIFaceBase.h"
@@ -19,25 +20,85 @@
#include "CvDLLFAStarIFaceBase.h"
-#define BUILDINGFOCUS_FOOD (0x00000001)
-#define BUILDINGFOCUS_PRODUCTION (0x00000002)
-#define BUILDINGFOCUS_GOLD (0x00000004)
-#define BUILDINGFOCUS_RESEARCH (0x00000008)
-#define BUILDINGFOCUS_CULTURE (0x00000010)
-#define BUILDINGFOCUS_DEFENSE (0x00000020)
-#define BUILDINGFOCUS_HAPPY (0x00000040)
-#define BUILDINGFOCUS_HEALTHY (0x00000080)
-#define BUILDINGFOCUS_EXPERIENCE (0x00000100)
-#define BUILDINGFOCUS_MAINTENANCE (0x00000200)
-#define BUILDINGFOCUS_SPECIALIST (0x00000400)
+#define BUILDINGFOCUS_FOOD (1 << 1)
+#define BUILDINGFOCUS_PRODUCTION (1 << 2)
+#define BUILDINGFOCUS_GOLD (1 << 3)
+#define BUILDINGFOCUS_RESEARCH (1 << 4)
+#define BUILDINGFOCUS_CULTURE (1 << 5)
+#define BUILDINGFOCUS_DEFENSE (1 << 6)
+#define BUILDINGFOCUS_HAPPY (1 << 7)
+#define BUILDINGFOCUS_HEALTHY (1 << 8)
+#define BUILDINGFOCUS_EXPERIENCE (1 << 9)
+#define BUILDINGFOCUS_MAINTENANCE (1 << 10)
+#define BUILDINGFOCUS_SPECIALIST (1 << 11)
+#define BUILDINGFOCUS_BIGCULTURE (1 << 12)
+#define BUILDINGFOCUS_WORLDWONDER (1 << 13)
+#define BUILDINGFOCUS_DOMAINSEA (1 << 14)
+#define BUILDINGFOCUS_WONDEROK (1 << 15)
+// for testing only
+#ifndef FINAL_RELEASE
+//#define TESTING_EXTRA_SAVE_DATA
+#define DEBUG_OUT_OF_SYNCS
+#endif
+
+// our own version number on our extra data
+// if we want to reorder what we save, change this number
+// adding to the list should be fine
+#ifdef TESTING_EXTRA_SAVE_DATA
+
+#define CITYEXTRADATA_CURRENTVERSION 0x4000
+enum CityExtraDataIndicies
+{
+ CITYEXTRADATA_VERSION = 0,
+ CITYEXTRADATA_XCOORD,
+ CITYEXTRADATA_YCOORD,
+};
+#else
+
+#define CITYEXTRADATA_CURRENTVERSION 1
+enum CityExtraDataIndicies
+{
+ CITYEXTRADATA_VERSION = 0,
+ //CITYEXTRADATA_UNUSED1,
+ //CITYEXTRADATA_UNUSED2,
+};
+#endif
+
+//#define DEBUG_CITY_BUILDS
+#ifdef DEBUG_CITY_BUILDS
+inline void getBuildingFocusString(CvWString& szString, int iFocusFlags)
+{
+ bool bFirst = true; szString = "";
+ if (iFocusFlags & BUILDINGFOCUS_FOOD) {szString+=L"food"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (iFocusFlags & BUILDINGFOCUS_PRODUCTION) {szString+=L"production"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (iFocusFlags & BUILDINGFOCUS_GOLD) {szString+=L"gold"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (iFocusFlags & BUILDINGFOCUS_RESEARCH) {szString+=L"research"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (iFocusFlags & BUILDINGFOCUS_CULTURE) {szString+=L"culture"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (iFocusFlags & BUILDINGFOCUS_DEFENSE) {szString+=L"defense"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (iFocusFlags & BUILDINGFOCUS_HAPPY) {szString+=L"happy"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (iFocusFlags & BUILDINGFOCUS_HEALTHY) {szString+=L"healthy"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (iFocusFlags & BUILDINGFOCUS_EXPERIENCE) {szString+=L"experience"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (iFocusFlags & BUILDINGFOCUS_MAINTENANCE) {szString+=L"maintenance"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (iFocusFlags & BUILDINGFOCUS_SPECIALIST) {szString+=L"specialist"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (iFocusFlags & BUILDINGFOCUS_BIGCULTURE) {szString+=L"big culture"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (iFocusFlags & BUILDINGFOCUS_WORLDWONDER) {szString+=L"world wonder"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (iFocusFlags & BUILDINGFOCUS_DOMAINSEA) {szString+=L"domain sea"; if (!bFirst){szString+=L", ";} bFirst = false;}
+ if (bFirst) {szString+=L"none";}
+}
+#endif
+
+
// Public Functions...
CvCityAI::CvCityAI()
{
m_aiEmphasizeYieldCount = new int[NUM_YIELD_TYPES];
m_aiEmphasizeCommerceCount = new int[NUM_COMMERCE_TYPES];
+ m_bForceEmphasizeCulture = false;
+ m_aiSpecialYieldMultiplier = new int[NUM_YIELD_TYPES];
+ m_aiPlayerCloseness = new int[MAX_PLAYERS];
m_pbEmphasize = NULL;
@@ -51,6 +112,8 @@
SAFE_DELETE_ARRAY(m_aiEmphasizeYieldCount);
SAFE_DELETE_ARRAY(m_aiEmphasizeCommerceCount);
+ SAFE_DELETE_ARRAY(m_aiSpecialYieldMultiplier);
+ SAFE_DELETE_ARRAY(m_aiPlayerCloseness);
}
@@ -82,7 +145,9 @@
m_iEmphasizeAvoidGrowthCount = 0;
m_iEmphasizeGreatPeopleCount = 0;
+ m_bForceEmphasizeCulture = false;
+
m_bAssignWorkDirty = false;
m_bChooseProductionDirty = false;
@@ -108,6 +173,20 @@
m_aeBestBuild[iI] = NO_BUILD;
}
+ for (iI = 0; iI < NUM_YIELD_TYPES; iI++)
+ {
+ m_aiSpecialYieldMultiplier[iI] = 0;
+ }
+ for (iI = 0; iI < MAX_PLAYERS; iI++)
+ {
+ m_aiPlayerCloseness[iI] = 0;
+ }
+ m_iCachePlayerClosenessTurn = -1;
+ m_iCachePlayerClosenessDistance = -1;
+
+ m_iNeededFloatingDefenders = -1;
+ m_iNeededFloatingDefendersCacheTurn = -1;
+
FAssertMsg(m_pbEmphasize == NULL, "m_pbEmphasize not NULL!!!");
FAssertMsg(GC.getNumEmphasizeInfos() > 0, "GC.getNumEmphasizeInfos() is not greater than zero but an array is being allocated in CvCityAI::AI_reset");
m_pbEmphasize = new bool[GC.getNumEmphasizeInfos()];
@@ -115,6 +194,15 @@
{
m_pbEmphasize[iI] = false;
}
+
+#ifdef NEW_CITY_GOVERNOR
+ m_governorValues.bInitialized = false;
+ m_governorCitizens.clear();
+#endif
+
+#ifdef COMPARE_NEW_CITY_GOVERNOR
+ m_bDisableTest = false;
+#endif
}
@@ -132,6 +220,11 @@
}
}
+ if (!isHuman())
+ {
+ AI_stealPlots();
+ }
+
AI_assignWorkingPlots();
AI_updateBestBuild();
@@ -140,9 +233,15 @@
if (isHuman())
{
+ if (isProductionAutomated())
+ {
+ AI_doHurry();
+ }
return;
}
+ AI_doPanic();
+
AI_doDraft();
AI_doHurry();
@@ -156,9 +255,18 @@
{
PROFILE_FUNC();
+#ifdef NEW_CITY_GOVERNOR
+ test_AI_AssignWorkingPlots();
+#endif
+
+#ifdef USE_NEW_CITY_GOVERNOR
+ return;
+#endif
+
CvPlot* pHomePlot;
int iI;
+ // remove all assigned plots if we automated
if (!isHuman() || isCitizensAutomated())
{
for (iI = 0; iI < NUM_CITY_PLOTS; iI++)
@@ -167,16 +275,33 @@
}
}
+ //update the special yield multiplier to be current
+ AI_updateSpecialYieldMultiplier();
+
+ // remove any plots we can no longer work for any reason
verifyWorkingPlots();
+ // if forcing specialists, try to make all future specialists of the same type
+ bool bIsSpecialistForced = false;
+ int iTotalForcedSpecialists = 0;
+
+ // make sure at least the forced amount of specialists are assigned
for (iI = 0; iI < GC.getNumSpecialistInfos(); iI++)
{
- if (!isHuman() || isCitizensAutomated() || (getSpecialistCount((SpecialistTypes)iI) < getForceSpecialistCount((SpecialistTypes)iI)))
+ int iForcedSpecialistCount = getForceSpecialistCount((SpecialistTypes)iI);
+ if (iForcedSpecialistCount > 0)
{
- setSpecialistCount(((SpecialistTypes)iI), getForceSpecialistCount((SpecialistTypes)iI));
+ bIsSpecialistForced = true;
+ iTotalForcedSpecialists += iForcedSpecialistCount;
}
+
+ if (!isHuman() || isCitizensAutomated() || (getSpecialistCount((SpecialistTypes)iI) < iForcedSpecialistCount))
+ {
+ setSpecialistCount(((SpecialistTypes)iI), iForcedSpecialistCount);
+ }
}
+ // if we have more specialists of any type than this city can have, reduce to the max
for (iI = 0; iI < GC.getNumSpecialistInfos(); iI++)
{
if (!isSpecialistValid((SpecialistTypes)iI))
@@ -188,13 +313,14 @@
}
}
+ // always work the home plot (center)
pHomePlot = getCityIndexPlot(CITY_HOME_PLOT);
-
if (pHomePlot != NULL)
{
setWorkingPlot(CITY_HOME_PLOT, ((getPopulation() > 0) && canWork(pHomePlot)));
}
+ // keep removing the worst citizen until we are not over the limit
while (extraPopulation() < 0)
{
if (!AI_removeWorstCitizen())
@@ -204,29 +330,79 @@
}
}
+ // extraSpecialists() is less than extraPopulation()
FAssertMsg(extraSpecialists() >= 0, "extraSpecialists() is expected to be non-negative (invalid Index)");
+#ifdef COMPARE_NEW_CITY_GOVERNOR
+ // dont check all the calls to AI_addBestCitizen and AI_removeWorstCitizen called past this point
+ m_bDisableTest = true;
+#endif
+
+ // do we have population unassigned
while (extraPopulation() > 0)
{
- if (!AI_addBestCitizen(true, !isSpecialistForced()))
+ // (AI_addBestCitizen now handles forced specialist logic)
+ if (!AI_addBestCitizen(/*bWorkers*/ true, /*bSpecialists*/ true))
{
break;
}
}
+ // if forcing specialists, assign any other specialists that we must place based on forced specialists
+ int iInitialExtraSpecialists = extraSpecialists();
+ int iExtraSpecialists = iInitialExtraSpecialists;
+ if (bIsSpecialistForced && iExtraSpecialists > 0)
+ {
+ FAssertMsg(iTotalForcedSpecialists > 0, "zero or negative total forced specialists");
+ for (iI = 0; iI < GC.getNumSpecialistInfos(); iI++)
+ {
+ if (isSpecialistValid((SpecialistTypes)iI, 1))
+ {
+ int iForcedSpecialistCount = getForceSpecialistCount((SpecialistTypes)iI);
+ if (iForcedSpecialistCount > 0)
+ {
+ int iSpecialistCount = getSpecialistCount((SpecialistTypes)iI);
+ int iMaxSpecialistCount = getMaxSpecialistCount((SpecialistTypes)iI);
+
+ int iSpecialistsToAdd = ((iInitialExtraSpecialists * iForcedSpecialistCount) + (iTotalForcedSpecialists/2)) / iTotalForcedSpecialists;
+ if (iExtraSpecialists < iSpecialistsToAdd)
+ {
+ iSpecialistsToAdd = iExtraSpecialists;
+ }
+
+ iSpecialistCount += iSpecialistsToAdd;
+ iExtraSpecialists -= iSpecialistsToAdd;
+
+ // if we cannot fit that many, then add as many as we can
+ if (iSpecialistCount > iMaxSpecialistCount && !GET_PLAYER(getOwnerINLINE()).isSpecialistValid((SpecialistTypes)iI))
+ {
+ iExtraSpecialists += iSpecialistCount - iMaxSpecialistCount;
+ iSpecialistCount = iMaxSpecialistCount;
+ }
+
+ setSpecialistCount((SpecialistTypes)iI, iSpecialistCount);
+ }
+ }
+ }
+ }
+ FAssertMsg(iExtraSpecialists >= 0, "added too many specialists");
+
+ // if we still have population to assign, assign specialists
while (extraSpecialists() > 0)
{
- if (!AI_addBestCitizen(false, true))
+ if (!AI_addBestCitizen(/*bWorkers*/ false, /*bSpecialists*/ true))
{
break;
}
}
+ // if automated, look for better choices than the current ones
if (!isHuman() || isCitizensAutomated())
{
AI_juggleCitizens();
}
+ // at this point, we should not be over the limit
FAssert((getWorkingPopulation() + getSpecialistPopulation()) <= (totalFreeSpecialists() + getPopulation()));
AI_setAssignWorkDirty(false);
@@ -235,9 +411,1687 @@
{
gDLL->getInterfaceIFace()->setDirty(CitizenButtons_DIRTY_BIT, true);
}
+
+#ifdef COMPARE_NEW_CITY_GOVERNOR
+ m_bDisableTest = false;
+
+ AI_compareGovernorCitizens();
+#endif
}
+#ifdef NEW_CITY_GOVERNOR
+void CvCityAI::test_AI_AssignWorkingPlots()
+{
+ PROFILE_FUNC();
+
+ //update the special yield multiplier to be current
+ AI_updateSpecialYieldMultiplier();
+
+ // calculate the modifiers to each yield type
+ AI_calculateGovernorValues(m_governorValues);
+
+ // build and sort the list of plots and specialists
+ bool bGovernorActive = (!isHuman() || isCitizensAutomated());
+ AI_buildGovernorCitizens(m_governorCitizens, m_governorValues, /*bReassign*/ bGovernorActive);
+
+ // first pass, count the number of citizens already assigned, check forced specialists
+ CvGovernorWork governorWork;
+ AI_initializeGovernorWork(governorWork, m_governorCitizens, /*bDoForced*/ true);
+
+ // are we working more population than we have?
+ int iAvailableToWork = governorWork.availableToWork();
+ if (iAvailableToWork < 0)
+ {
+ AI_workLessCitizens(governorWork, m_governorCitizens, -iAvailableToWork);
+ }
+ // do we need to work some more?
+ int iUnassignedWorkers = governorWork.unassignedWorkers();
+ if (iUnassignedWorkers > 0)
+ {
+ AI_workMoreCitizens(governorWork, m_governorCitizens, iUnassignedWorkers);
+ }
+
+ // at this point, we should have assigned exactly the right amount
+ FAssert((governorWork.availableToWork() == 0 && governorWork.availableSpecialists() == 0));
+
+ // adjust for growth and starvation
+ AI_foodAdjustCitizens(governorWork, m_governorCitizens, m_governorValues);
+
+ // at this point, we should still have assigned exactly the right amount
+ FAssert((governorWork.availableToWork() == 0 && governorWork.availableSpecialists() == 0));
+
+ // actually work the citizens
+ AI_workGovernorCitizens(m_governorCitizens);
+
+ // at this point, we should not be over the limit
+ FAssert((getWorkingPopulation() + getSpecialistPopulation()) <= (totalFreeSpecialists() + getPopulation()));
+
+ // assign is done
+ AI_setAssignWorkDirty(false);
+}
+
+bool CvCityAI::test_AI_AddBestCitizen(bool bWorkers, bool bSpecialists)
+{
+ PROFILE_FUNC();
+
+ // calculate the modifiers to each yield type
+ AI_calculateGovernorValues(m_governorValues);
+
+ // build and sort the list of plots and specialists
+ AI_buildGovernorCitizens(m_governorCitizens, m_governorValues, /*bReassign*/ false, /*bCapWorkers*/ !bWorkers, /*bCapSpecialists*/ !bSpecialists);
+
+ // first pass, count the number of citizens already assigned, check forced specialists
+ CvGovernorWork governorWork;
+ AI_initializeGovernorWork(governorWork, m_governorCitizens, /*bDoForced*/ true);
+
+ // add one citizen
+ int iChangeCount = AI_workMoreCitizens(governorWork, m_governorCitizens, 1);
+ FAssert(iChangeCount <= 1);
+
+ // adjust for growth and starvation
+ AI_foodAdjustChangedCitizen(governorWork, m_governorCitizens, m_governorValues);
+
+ // actually work the citizens
+ AI_workGovernorCitizens(m_governorCitizens);
+
+ return (iChangeCount > 0);
+}
+
+bool CvCityAI::test_AI_removeWorstCitizen(SpecialistTypes eIgnoreSpecialist)
+{
+ PROFILE_FUNC();
+
+ // calculate the modifiers to each yield type
+ AI_calculateGovernorValues(m_governorValues);
+
+ // build and sort the list of plots and specialists
+ AI_buildGovernorCitizens(m_governorCitizens, m_governorValues, /*bReassign*/ false);
+
+ // first pass, count the number of citizens already assigned, check forced specialists
+ CvGovernorWork governorWork;
+ AI_initializeGovernorWork(governorWork, m_governorCitizens, /*bDoForced*/ true);
+
+ // ensure that we do not remove the ignore specialist, by marking it forced
+ if (eIgnoreSpecialist != NO_SPECIALIST)
+ {
+ for (CvCitizensIterator it = m_governorCitizens.begin(); it != m_governorCitizens.end(); ++it)
+ {
+ // is this our ignore specialist?
+ CvGovernorCitizen* pCitizen = getCitizen(it);
+ if (pCitizen->bIsSpecialist && pCitizen->iIndex == eIgnoreSpecialist)
+ {
+ // set forced to the current value
+ int iForcedDelta = (pCitizen->iCount - pCitizen->iForcedCount);
+ pCitizen->iForcedCount += iForcedDelta;
+ governorWork.iTotalForcedSpecialists += iForcedDelta;
+
+ // finished (only one of each specialist)
+ break;
+ }
+ }
+ }
+
+ // remove one citizen
+ int iChangeCount = AI_workLessCitizens(governorWork, m_governorCitizens, 1);
+ FAssert(iChangeCount <= 1);
+
+ // adjust for growth and starvation
+ AI_foodAdjustChangedCitizen(governorWork, m_governorCitizens, m_governorValues);
+
+ // actually work the citizens
+ AI_workGovernorCitizens(m_governorCitizens);
+
+ return (iChangeCount > 0);
+}
+
+// counts the workers already assigned, assigns forced specialists, returns forced specialist count
+void CvCityAI::AI_initializeGovernorWork(CvGovernorWork &governorWork, CvSortedCitizens& governorCitizens, bool bDoForced)
+{
+ PROFILE_FUNC();
+
+ // counters to keep track what needs to be done
+ governorWork.iWorkablePopulation = getPopulation() - angryPopulation();
+ governorWork.iWorkablePlots = 0;
+ governorWork.iTotalFreeSpecialists = totalFreeSpecialists();
+ governorWork.iWorkingPlots = 0;
+ governorWork.iWorkingSpecialists = 0;
+ governorWork.iTotalForcedSpecialists = 0;
+
+ // first pass, count the number of citizens already assigned, check forced specialists
+ for (CvCitizensIterator it = governorCitizens.begin(); it != governorCitizens.end(); ++it)
+ {
+ CvGovernorCitizen* pCitizen = getCitizen(it);
+
+ // specialist
+ if (pCitizen->bIsSpecialist)
+ {
+ int iCount = pCitizen->iCount;
+ int iForcedCount = pCitizen->iForcedCount;
+ if (iForcedCount > 0)
+ {
+ if (bDoForced && iCount < iForcedCount)
+ {
+ iForcedCount = min(iForcedCount, pCitizen->iMax);
+ pCitizen->iCount = iForcedCount;
+ }
+
+ governorWork.iTotalForcedSpecialists += iForcedCount;
+ }
+
+ governorWork.iWorkingSpecialists += pCitizen->iCount;
+ }
+ // plot
+ else
+ {
+ governorWork.iWorkingPlots += pCitizen->iCount;
+
+ governorWork.iWorkablePlots += pCitizen->iMax;
+ }
+ }
+
+ // only used in food recalcs (for now), availableToWork() or unassignedWorkers() are probably better choices most of the time
+ governorWork.iPopulationAssignable = governorWork.availableToWork();
+ governorWork.iFreeSpecialistsAssignable = max(0, governorWork.iTotalFreeSpecialists - governorWork.iWorkingSpecialists);
+}
+
+int CvCityAI::AI_workMoreCitizens(CvGovernorWork &governorWork, CvSortedCitizens& governorCitizens, int iCountToAdd, bool bIgnoreForced)
+{
+ PROFILE_FUNC();
+
+ // how many we have left to remove
+ int iPopulationToAdd = iCountToAdd;
+
+ // if we have forced specialists, then we will only assign of those types
+ bool bOnlyForced = (!bIgnoreForced && governorWork.isForcedSpecialists());
+
+ // little macros to make more readable
+ #define GET_MAX_ADD(P_CITIZEN) \
+ ( ((P_CITIZEN)->bIsSpecialist) ? \
+ ((P_CITIZEN)->iMax - (P_CITIZEN)->iCount) : \
+ (min(((P_CITIZEN)->iMax - (P_CITIZEN)->iCount), governorWork.availableToWork())) )
+
+ #define ADD_CITIZEN(P_CITIZEN, I_ADD_COUNT) \
+ { int iAddAddCount = (I_ADD_COUNT); \
+ CvGovernorCitizen* pAddCitizen = (P_CITIZEN); \
+ pAddCitizen->iCount += iAddAddCount; \
+ iPopulationToAdd -= iAddAddCount; \
+ if (pAddCitizen->bIsSpecialist) \
+ governorWork.iWorkingSpecialists += iAddAddCount; \
+ else \
+ governorWork.iWorkingPlots += iAddAddCount; }
+
+ // start from the best citizen, working downward (list is sorted worst to best, so do in reverse order)
+ for (CvCitizensReverseIterator ir = governorCitizens.rbegin(); iPopulationToAdd > 0 && ir != governorCitizens.rend(); ++ir)
+ {
+ CvGovernorCitizen* pCitizen = getCitizen(ir);
+
+ // can we work more of this citizen?
+ int iAddCount = GET_MAX_ADD(pCitizen);
+ if (iAddCount > 0)
+ {
+ // is this one we should add?
+ if (!bOnlyForced || !pCitizen->bIsSpecialist || pCitizen->iForcedCount > 0)
+ {
+ // if only adding one, then simply add it and move to next citizen
+ if (iAddCount == 1 || iPopulationToAdd == 1)
+ {
+ ADD_CITIZEN(pCitizen, 1);
+ }
+ // we want to add more than one, so look ahead, to see if there are other citizens with close values
+ else
+ {
+ // any value within 95% is considered close enough
+ int iMinValue = ((95 * getCitizenValue(ir)) + 99) / 100;
+
+ // how many are there with close values we can add?
+ int iCloseCount = 1;
+ int iTotalAddCount = iAddCount;
+ int iHighestAddCount = iAddCount;
+ CvCitizensReverseIterator irNext = ir;
+ while ((iCloseCount < iPopulationToAdd) && ++irNext != governorCitizens.rend() && getCitizenValue(irNext) >= iMinValue)
+ {
+ // can we work more of this citizen?
+ CvGovernorCitizen* pNextCitizen = getCitizen(irNext);
+ int iNextAddCount = GET_MAX_ADD(pNextCitizen);
+ if (iNextAddCount > 0)
+ {
+ // is this one we should add?
+ if (!bOnlyForced || !pNextCitizen->bIsSpecialist || pNextCitizen->iForcedCount > 0)
+ {
+ ++iCloseCount;
+
+ iTotalAddCount += iNextAddCount;
+ if (iNextAddCount > iHighestAddCount)
+ {
+ iHighestAddCount = iNextAddCount;
+ }
+ }
+ }
+ }
+
+ // if there were none close, just add them all to this one
+ if (iCloseCount == 1)
+ {
+ ADD_CITIZEN(pCitizen, min(iAddCount, iPopulationToAdd));
+ }
+ // otherwise, loop over them all
+ else
+ {
+ // if adding the max from every one will not finish us, then do that all at once
+ // otherwise will only add one at a time
+ bool bAllAtOnce = (iTotalAddCount < iPopulationToAdd);
+
+ while (iPopulationToAdd > 0 && iHighestAddCount-- > 0)
+ {
+ CvCitizensReverseIterator irNext = ir;
+ do
+ {
+ // can we work more of this citizen?
+ CvGovernorCitizen* pNextCitizen = getCitizen(irNext);
+ iAddCount = GET_MAX_ADD(pNextCitizen);
+ if (iAddCount > 0)
+ {
+ // is this one we should add?
+ if (!bOnlyForced || !pNextCitizen->bIsSpecialist || pNextCitizen->iForcedCount > 0)
+ {
+ ADD_CITIZEN(pNextCitizen, bAllAtOnce ? iAddCount : 1);
+ }
+ }
+ }
+ while (iPopulationToAdd > 0 && ++irNext != governorCitizens.rend() && getCitizenValue(irNext) >= iMinValue);
+
+ // if all at once, then done, stop loop
+ if (bAllAtOnce)
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // if we did not add enough, try again allowing non-forced specialists
+ if (bOnlyForced && iPopulationToAdd > 0)
+ {
+ iPopulationToAdd -= AI_workMoreCitizens(governorWork, governorCitizens, iPopulationToAdd, /*bIgnoreForced*/ true);
+ }
+
+ // return the number we added
+ return (iCountToAdd - iPopulationToAdd);
+}
+
+int CvCityAI::AI_workLessCitizens(CvGovernorWork &governorWork, CvSortedCitizens& governorCitizens, int iCountToRemove)
+{
+ PROFILE_FUNC();
+
+ // how many we have left to remove
+ int iPopulationToRemove = iCountToRemove;
+
+ // special case check, if we have more forced specialists than we can possibly assign, we will have to remove some
+ bool bRemoveForced = (governorWork.iTotalForcedSpecialists > (governorWork.iWorkingPlots + governorWork.iWorkingSpecialists - iCountToRemove));
+
+ // little macro to make more readable
+ #define REMOVE_CITIZEN(P_CITIZEN, I_REMOVE_COUNT) \
+ { int iRemoveRemoveCount = (I_REMOVE_COUNT); \
+ CvGovernorCitizen* pRemoveCitizen = (P_CITIZEN); \
+ pRemoveCitizen->iCount -= iRemoveRemoveCount; \
+ iPopulationToRemove -= iRemoveRemoveCount; \
+ if (pRemoveCitizen->bIsSpecialist) \
+ governorWork.iWorkingSpecialists -= iRemoveRemoveCount; \
+ else \
+ governorWork.iWorkingPlots -= iRemoveRemoveCount; }
+
+ // start from the worst citizen, working upward (list is sorted worst to best)
+ for (CvCitizensIterator it = governorCitizens.begin(); iPopulationToRemove > 0 && it != governorCitizens.end(); ++it)
+ {
+ CvGovernorCitizen* pCitizen = getCitizen(it);
+
+ // are we working this citizen?
+ int iCount = pCitizen->iCount;
+ if (iCount > 0)
+ {
+ // is this one we should remove?
+ if (bRemoveForced || iCount > pCitizen->iForcedCount)
+ {
+ // if only removing one, then simply remove it and move to next citizen
+ int iRemoveCount = bRemoveForced ? iCount : (iCount - pCitizen->iForcedCount);
+ if (iRemoveCount == 1 || iPopulationToRemove == 1)
+ {
+ REMOVE_CITIZEN(pCitizen, 1);
+ }
+ // we want to remove more than one, so look ahead, to see if there are other citizens with close values
+ else
+ {
+ // any value within 95% is considered close enough
+ int iMaxValue = (105 * getCitizenValue(it)) / 100;
+
+ // how many are there with close values we can remove?
+ int iCloseCount = 1;
+ int iTotalRemoveCount = iRemoveCount;
+ int iHighestRemoveCount = iRemoveCount;
+ CvCitizensIterator itNext = it;
+ while ((iCloseCount < iPopulationToRemove) && ++itNext != governorCitizens.end() && getCitizenValue(itNext) <= iMaxValue)
+ {
+ // is this citizen worked
+ CvGovernorCitizen* pNextCitizen = getCitizen(itNext);
+ if (pNextCitizen->iCount > 0)
+ {
+ int iNextRemoveCount = bRemoveForced ? pNextCitizen->iCount : (pNextCitizen->iCount - pNextCitizen->iForcedCount);
+ if (iNextRemoveCount > 0)
+ {
+ ++iCloseCount;
+
+ iTotalRemoveCount += iNextRemoveCount;
+ if (iNextRemoveCount > iHighestRemoveCount)
+ {
+ iHighestRemoveCount = iNextRemoveCount;
+ }
+ }
+ }
+ }
+
+ // if there were none close, just remove them all from this one
+ if (iCloseCount == 1)
+ {
+ REMOVE_CITIZEN(pCitizen, min(iRemoveCount, iPopulationToRemove));
+ }
+ // otherwise, loop over them all
+ else
+ {
+ // if removing the max from every one will not finish us, then do that all at once
+ // otherwise will only remove one at a time
+ bool bAllAtOnce = (iTotalRemoveCount < iPopulationToRemove);
+
+ while (iPopulationToRemove > 0 && iHighestRemoveCount-- > 0)
+ {
+ CvCitizensIterator itNext = it;
+ do
+ {
+ // is this citizen worked
+ CvGovernorCitizen* pNextCitizen = getCitizen(itNext);
+ if (pNextCitizen->iCount > 0)
+ {
+ iRemoveCount = bRemoveForced ? pNextCitizen->iCount : (pNextCitizen->iCount - pCitizen->iForcedCount);
+ if (iRemoveCount > 0)
+ {
+ REMOVE_CITIZEN(pNextCitizen, bAllAtOnce ? iRemoveCount : 1);
+ }
+ }
+ }
+ while (iPopulationToRemove > 0 && ++itNext != governorCitizens.end() && getCitizenValue(itNext) <= iMaxValue);
+
+ // if all at once, then done, stop loop
+ if (bAllAtOnce)
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // return the number we removed
+ return (iCountToRemove - iPopulationToRemove);
+}
+int CvCityAI::AI_foodAdjustCitizens(CvGovernorWork &governorWork, CvSortedCitizens& governorCitizens, CvGovernorValues& governorValues)
+{
+ PROFILE_FUNC();
+
+ // how many were changed? (return result)
+ int iTotalChangeCount = 0;
+
+ int iCurrentFood = AI_countCitizenFood(governorCitizens);
+
+ int iFoodConsumption = foodConsumption();
+ int iFoodDifference = iCurrentFood - iFoodConsumption;
+
+ // is our food difference too low? (shoot for min of iConsumtionPerPop if we want to grow, and at least zero so we dont starve)
+ int iConsumtionPerPop = GC.getFOOD_CONSUMPTION_PER_POPULATION();
+ bool bEncourageGrowth = (!isFoodProduction() && (governorWork.iWorkablePlots > governorWork.iWorkingPlots) && (governorValues.bGrowPastHappy || governorValues.iHappyLevel > 0)) ? true : false;
+ if (iFoodDifference < 0 || bEncourageGrowth)
+ {
+ int iMaxFood = AI_countMaximumCitizenFood(governorWork, governorCitizens);
+
+ // can we avoid starvation by working the highest food possible? (if so continue, if not, do not bother)
+ if ((iMaxFood - iFoodConsumption) >= 0 && iMaxFood > iCurrentFood)
+ {
+ // most of the time, 1 pass should be sufficient, never do more than one pass unless starving
+ int iPass = 0;
+ while ((iFoodDifference < 0 || bEncourageGrowth) && ++iPass < 16)
+ {
+ FAssert(iPass < 3);
+ if (iPass > 1)
+ {
+ bEncourageGrowth = false;
+ }
+
+ // search iterator will be the first citizen with a higher value than our first swap
+ bool bRemovedFirst = false;
+ CvCitizensReverseIterator irFirstRemove;
+
+ // keep track of the lowest food value we working when possible
+ int iLowestFood = -1;
+ CvCitizensReverseIterator irLowestFood;
+
+ // start from the best citizen, working downward (list is sorted worst to best, so do in reverse order)
+ // looking for a citizen that is not worked, we will then search for a better citizen that is less food to swap
+ for (CvCitizensReverseIterator ir = governorCitizens.rbegin(); (bEncourageGrowth || iFoodDifference < 0) && ir != governorCitizens.rend(); ++ir)
+ {
+ iTotalChangeCount += swapHigherFoodCitizen(ir, iFoodDifference, bEncourageGrowth, iLowestFood, irLowestFood, governorWork, governorCitizens);
+ }
+ }
+ }
+ }
+
+ return iTotalChangeCount;
+}
+
+inline bool isCitizenValueCloseEnough(int iFoodDifference, int iLowerCitizenValue, int iHigherCitizenValue, int iFoodChange)
+{
+ bool bCloseEnough = false;
+
+ if (iFoodChange > 0)
+ {
+ bCloseEnough = (iFoodDifference < 0);
+ if (!bCloseEnough)
+ {
+ int iMaxPercentage = max(1, 10 - iFoodDifference);
+ iMaxPercentage = (iMaxPercentage * iMaxPercentage) / max(2, 5 - iFoodChange);
+ bCloseEnough = (iHigherCitizenValue <= (iLowerCitizenValue * (100 + iMaxPercentage)) / 100);
+ }
+ }
+
+ return bCloseEnough;
+}
+
+int CvCityAI::swapHigherFoodCitizen(CvCitizensReverseIterator ir, int& iFoodDifference, bool bEncourageGrowth, int& iLowestFood, CvCitizensReverseIterator& irLowestFood, CvGovernorWork &governorWork, CvSortedCitizens& governorCitizens)
+{
+ PROFILE_FUNC();
+
+ int iTotalChangeCount = 0;
+
+ CvGovernorCitizen* pCitizen = getCitizen(ir);
+ int iFood = pCitizen->iFood;
+
+ int iCount = pCitizen->iCount;
+ int iCannotChangeCount = min(iCount, max(pCitizen->iInitialCount, pCitizen->iForcedCount));
+ int iCanChangeCount = iCount - iCannotChangeCount;
+ int iCanChangeMax = pCitizen->iMax - iCannotChangeCount;
+
+ // is this the worst food we can remove? (once we remove one, do not look at lower values for low food)
+ if (iCanChangeCount > 0 && (iLowestFood < 0 || iFood < iLowestFood))
+ {
+ iLowestFood = iFood;
+ irLowestFood = ir;
+ }
+
+ // if we are at the top of the list, then there are no better ones we can swap in
+ // (since we always start lower and look for higher food)
+ if (ir == governorCitizens.rbegin())
+ {
+ return 0;
+ }
+
+ // is this one we can increase food by swapping in?
+ int iMoveCount = (iCanChangeMax - iCanChangeCount);
+ if (iFood > 0 && iFood > iLowestFood && iMoveCount > 0)
+ {
+ // remove points to the citizen we will remove one (or more) of
+ CvCitizensReverseIterator irRemove = governorCitizens.rend();
+
+ // if we are one greater than the lowest food, start with the cached lowest food
+ if ((iFood == iLowestFood + 1) && isCitizenValueCloseEnough(iFoodDifference, getCitizenValue(ir), getCitizenValue(irLowestFood), 1))
+ {
+ irRemove = irLowestFood;
+ }
+
+ // when/if we search for the worst citizen we are working that has less food starting just above this one
+ CvCitizensReverseIterator irSearch = ir;
+
+ // if we at start, we in trouble. should never happen
+ FAssert(irSearch != governorCitizens.rbegin());
+
+ // while we still have more of this one we can potentially work, and not done searching
+ while ((iFoodDifference < 0 || bEncourageGrowth) && iMoveCount > 0 && irSearch != governorCitizens.rbegin())
+ {
+ // search for the worst citizen we are working that has less food
+ if (irRemove == governorCitizens.rend())
+ {
+ // do the search
+ bool bDone = false;
+ while (!bDone)
+ {
+ // first decrement, then check to see whether we are at the beginning, if so, this is the last one to check
+ bDone = (--irSearch == governorCitizens.rbegin());
+
+ CvGovernorCitizen* pSearchCitizen = getCitizen(irSearch);
+
+ iCount = pSearchCitizen->iCount;
+ iCannotChangeCount = min(iCount, max(pSearchCitizen->iInitialCount, pSearchCitizen->iForcedCount));
+ iCanChangeCount = iCount - iCannotChangeCount;
+ // can we remove at least one citizen from this one?
+ if (iCanChangeCount > 0)
+ {
+ int iSearchFood = pSearchCitizen->iFood;
+
+ // is this one low enough food? then we done
+ if (isCitizenValueCloseEnough(iFoodDifference, getCitizenValue(ir), getCitizenValue(irSearch), iFood - iSearchFood))
+ {
+ irRemove = irSearch;
+ break;
+ }
+ // otherwise, update lowest food cached value
+ else if (iLowestFood < 0 || iSearchFood < iLowestFood)
+ {
+ iLowestFood = iSearchFood;
+ irLowestFood = irSearch;
+ }
+ }
+ }
+ }
+
+ // did we find a match?
+ if (irRemove != governorCitizens.rend())
+ {
+ CvGovernorCitizen* pRemoveCitizen = getCitizen(irRemove);
+
+ iCount = pRemoveCitizen->iCount;
+ iCannotChangeCount = min(iCount, max(pRemoveCitizen->iInitialCount, pRemoveCitizen->iForcedCount));
+ iCanChangeCount = iCount - iCannotChangeCount;
+
+ FAssert(iCanChangeCount > 0);
+
+ // how many will we move?
+ int iChangeCount = min(iCanChangeCount, iMoveCount);
+
+ // if we are at the cached lowest food, and we cannot reduce this item any further
+ // then 'clear' the cache, so we do not try to use it again later
+ if (irRemove == irLowestFood && iCanChangeCount <= iChangeCount)
+ {
+ iLowestFood = -1;
+ }
+
+ // check to see if we would be removing a free specialist
+ if (!pCitizen->bIsSpecialist && pRemoveCitizen->bIsSpecialist)
+ {
+ iChangeCount = min(iChangeCount, governorWork.iWorkingSpecialists - governorWork.iTotalFreeSpecialists);
+
+ // if we would be removing a free specialist, bail, continue through search loop
+ if (iChangeCount <= 0)
+ {
+ irRemove = governorCitizens.rend();
+ continue;
+ }
+ }
+
+ // do the swap
+ pCitizen->iCount += iChangeCount;
+ pRemoveCitizen->iCount -= iChangeCount;
+
+ FAssert(pRemoveCitizen->iFood < iFood);
+ iFoodDifference += (iFood - pRemoveCitizen->iFood) * iChangeCount;
+
+ // are both not the same, either both specialist or both not specialist?
+ if (pCitizen->bIsSpecialist != pRemoveCitizen->bIsSpecialist)
+ {
+ // we are adding a specialist
+ if (pCitizen->bIsSpecialist)
+ {
+ governorWork.iWorkingSpecialists += iChangeCount;
+ governorWork.iWorkingPlots -= iChangeCount;
+ }
+ // we are removing a specialist
+ else
+ {
+ governorWork.iWorkingSpecialists -= iChangeCount;
+ governorWork.iWorkingPlots += iChangeCount;
+ }
+ }
+
+ // check to see if there is a more valueble, lower food one than the one we just swapped, to swap for that one
+ int iRecursiveChangeCount = swapHigherFoodCitizen(irRemove, iFoodDifference, bEncourageGrowth, iLowestFood, irLowestFood, governorWork, governorCitizens);
+
+ // the change count is the max of the two counts (so we do not count a double swap as two)
+ iTotalChangeCount += max(iChangeCount, iRecursiveChangeCount);
+
+ // clear remove, so we can search again
+ irRemove = governorCitizens.rend();
+
+ // recalc move count (recursive call to swapHigherFoodCitizen might have changed it)
+ iMoveCount = (pCitizen->iMax - pCitizen->iCount);
+ }
+ }
+ }
+
+ return iTotalChangeCount;
+}
+
+bool CvCityAI::AI_foodAdjustChangedCitizen(CvGovernorWork &governorWork, CvSortedCitizens& governorCitizens, CvGovernorValues& governorValues)
+{
+ PROFILE_FUNC();
+
+ bool bWasChanged = false;
+
+ int iCurrentFood = AI_countCitizenFood(governorCitizens);
+
+ int iFoodConsumption = foodConsumption();
+ int iFoodDifference = iCurrentFood - iFoodConsumption;
+
+ // is our food difference too low? (shoot for min of iConsumtionPerPop if we want to grow, and at least zero so we dont starve)
+ int iConsumtionPerPop = GC.getFOOD_CONSUMPTION_PER_POPULATION();
+ int iMinDifference = (!isFoodProduction() && (governorValues.bGrowPastHappy || governorValues.iHappyLevel > 0)) ? iConsumtionPerPop : 0;
+ if (iFoodDifference < iMinDifference)
+ {
+ // find the changed citizen
+ bool bFoundCitizen = false;
+ CvCitizensReverseIterator irCitizen;
+ int iCitizenFood = -1;
+ int iOriginalCitizenFood = -1;
+ int iOriginalCitizenValue = 0;
+ bool bIsRemove = false;
+
+ // start from the best citizen, working downward (list is sorted worst to best, so do in reverse order)
+ for (CvCitizensReverseIterator ir = governorCitizens.rbegin(); iFoodDifference < iMinDifference && ir != governorCitizens.rend(); ++ir)
+ {
+ CvGovernorCitizen* pCitizen = getCitizen(ir);
+ int iFood = pCitizen->iFood;
+
+ int iCount = pCitizen->iCount;
+ int iCannotChangeCount = min(iCount, max(pCitizen->iInitialCount, pCitizen->iForcedCount));
+ int iCanChangeCount = iCount - iCannotChangeCount;
+ int iCanChangeMax = pCitizen->iMax - iCannotChangeCount;
+
+ // still searching for the one that was changed
+ if (!bFoundCitizen)
+ {
+ if (pCitizen->iCount != pCitizen->iInitialCount)
+ {
+ bFoundCitizen = true;
+ irCitizen = ir;
+ iOriginalCitizenFood = iCitizenFood = iFood;
+ iOriginalCitizenValue = getCitizenValue(irCitizen);
+ bIsRemove = (pCitizen->iInitialCount > pCitizen->iCount);
+ }
+ }
+ // found the changed one, now look for higher food (lower value) one
+ else if (iFood > iCitizenFood && isCitizenValueCloseEnough(iFoodDifference, getCitizenValue(ir), iOriginalCitizenValue, iFood - iOriginalCitizenFood))
+ {
+ // can we swap this one
+ if (bIsRemove ? (pCitizen->iCount > max(pCitizen->iInitialCount, pCitizen->iForcedCount)) : (pCitizen->iCount < pCitizen->iMax))
+ {
+ CvGovernorCitizen* pOldCitizen = getCitizen(irCitizen);
+
+ // swap
+ int iChange = (bIsRemove) ? -1 : 1;
+ pCitizen->iCount += iChange;
+ pOldCitizen->iCount -= iChange;
+
+ // update difference
+ iFoodDifference += iFood - iCitizenFood;
+ bWasChanged = true;
+
+ // remember new one
+ irCitizen = ir;
+ iCitizenFood = iFood;
+ }
+ }
+ }
+ }
+
+ return bWasChanged;
+}
+
+
+int CvCityAI::AI_countCitizenFood(CvSortedCitizens& governorCitizens)
+{
+ int iTotalFood = 0;
+
+ for (CvCitizensIterator it = governorCitizens.begin(); it != governorCitizens.end(); ++it)
+ {
+ CvGovernorCitizen* pCitizen = getCitizen(it);
+
+ int iFood = pCitizen->iFood;
+ if (iFood > 0)
+ {
+ int iCount = pCitizen->iCount;
+ if (iCount > 0)
+ {
+ iTotalFood += iFood * iCount;
+ }
+
+ int iFreeCount = pCitizen->iFreeCount;
+ if (iFreeCount > 0)
+ {
+ iTotalFood += iFood * iFreeCount;
+ }
+ }
+ }
+
+ return iTotalFood;
+}
+
+int CvCityAI::AI_countMaximumCitizenFood(CvGovernorWork &governorWork, CvSortedCitizens& governorCitizens)
+{
+ PROFILE_FUNC();
+
+ int iMaxFood = 0;
+
+ // make a new list, food sorted
+ CvSortedCitizens foodSortedCitizens;
+
+ // build a list of entries we can assign, sorted by food (do not bother with entries that have zero food)
+ int iPopulationToAssign = governorWork.iWorkablePopulation;
+ int iFreeSpecialistsLeft = governorWork.iTotalFreeSpecialists;
+ for (CvCitizensIterator it = governorCitizens.begin(); it != governorCitizens.end(); ++it)
+ {
+ CvGovernorCitizen* pCitizen = getCitizen(it);
+
+ if (pCitizen->iFood > 0)
+ {
+ // copy the citizen so we can make changes
+ CvGovernorCitizen citizen = *pCitizen;
+
+ // if there is intial or forced, remove that portion (counting it below)
+ int iInitialCount = min(pCitizen->iInitialCount, pCitizen->iCount);
+ int iForcedCount = min(pCitizen->iForcedCount, pCitizen->iCount);
+ int iCannotChangeCount = max(iInitialCount, iForcedCount);
+ citizen.iCount -= iCannotChangeCount;
+ citizen.iMax -= iCannotChangeCount;
+
+ // if there is food here we cannot reassign, count it now
+ if (iCannotChangeCount > 0 || citizen.iFreeCount > 0)
+ {
+ iMaxFood += citizen.iFood * (iCannotChangeCount + citizen.iFreeCount);
+ }
+
+ // lower pop to add based on these
+ if (iCannotChangeCount > 0)
+ {
+ // if this a specialist, first remove from the free specialists
+ if (citizen.bIsSpecialist && iFreeSpecialistsLeft > 0)
+ {
+ int iToRemove = min(iCannotChangeCount, iFreeSpecialistsLeft);
+ iFreeSpecialistsLeft -= iToRemove;
+ iCannotChangeCount -= iToRemove;
+ }
+
+ // remove from pop to assign
+ iPopulationToAssign -= iCannotChangeCount;
+ }
+
+ // if we can assign some changable amount of this citizen
+ if (citizen.iMax > 0)
+ {
+ foodSortedCitizens.insert(std::make_pair(citizen.iFood, citizen));
+ }
+ }
+ }
+
+ // remember the values from this recalc
+ governorWork.iPopulationAssignable = iPopulationToAssign;
+ governorWork.iFreeSpecialistsAssignable = iFreeSpecialistsLeft;
+
+ // grab the highest food items
+ for (CvCitizensReverseIterator ir = foodSortedCitizens.rbegin(); (iPopulationToAssign > 0 || iFreeSpecialistsLeft > 0) && ir != foodSortedCitizens.rend(); ++ir)
+ {
+ CvGovernorCitizen* pCitizen = getCitizen(ir);
+
+ // if this a specialist, first remove from the free specialists
+ if (pCitizen->bIsSpecialist && iFreeSpecialistsLeft > 0)
+ {
+ int iFreeAssignCount = min(pCitizen->iMax, iFreeSpecialistsLeft);
+ iFreeSpecialistsLeft -= iFreeAssignCount;
+ pCitizen->iMax -= iFreeAssignCount;
+
+ FAssert(pCitizen->iFood > 0);
+ iMaxFood += pCitizen->iFood * iFreeAssignCount;
+ }
+
+ if (iPopulationToAssign > 0 && pCitizen->iMax > 0)
+ {
+ int iAssignCount = min(pCitizen->iMax, iPopulationToAssign);
+ iPopulationToAssign -= iAssignCount;
+
+ FAssert(pCitizen->iFood > 0);
+ iMaxFood += pCitizen->iFood * iAssignCount;
+ }
+ }
+
+ return iMaxFood;
+}
+
+void CvCityAI::AI_calculateGovernorValues()
+{
+ AI_calculateGovernorValues(m_governorValues);
+}
+
+void CvCityAI::AI_calculateGovernorValues(CvGovernorValues& governorValues)
+{
+ PROFILE_FUNC();
+
+ PlayerTypes ePlayer = getOwnerINLINE();
+ CvPlayerAI& kPlayer = GET_PLAYER(ePlayer);
+
+ bool bEmphasizeFood = AI_isEmphasizeYield(YIELD_FOOD);
+ bool bEmphasizeProduction = AI_isEmphasizeYield(YIELD_PRODUCTION);
+ bool bFoodIsProduction = isFoodProduction();
+ bool bCanPopRush = kPlayer.canPopRush();
+ bool bIsNoUnhappiness = isNoUnhappiness();
+
+ int iNumCities = kPlayer.getNumCities();
+ int iPopulation = getPopulation();
+
+ // calculate modifiers
+ int iExtraProductionModifier = 0;
+ int iBaseProductionModifier = 100;
+ for (int iYield = 0; iYield < NUM_YIELD_TYPES; iYield++)
+ {
+ governorValues.yieldModifiers.aiYield[iYield] = getBaseYieldRateModifier((YieldTypes) iYield);
+
+ if (iYield == YIELD_PRODUCTION)
+ {
+ iBaseProductionModifier = governorValues.yieldModifiers.aiYield[YIELD_PRODUCTION];
+ iExtraProductionModifier = getProductionModifier();
+ governorValues.yieldModifiers.aiYield[YIELD_PRODUCTION] += iExtraProductionModifier;
+ }
+ }
+
+ for (int iCommerce = 0; iCommerce < NUM_COMMERCE_TYPES; iCommerce++)
+ {
+ governorValues.yieldModifiers.aiCommerce[iCommerce] = getTotalCommerceRateModifier((CommerceTypes) iCommerce);
+ }
+
+ governorValues.yieldModifiers.iGPPRate = getTotalGreatPeopleRateModifier();
+ governorValues.yieldModifiers.iBonusExperience = 100;
+
+ // set initial yield values, based on constant weights (default any new types to commerce weight)
+ for (int iYield = 0; iYield < NUM_YIELD_TYPES; iYield++)
+ {
+ governorValues.yieldValues.aiYield[iYield] = iGovernor_CommerceWeight;
+ }
+ governorValues.yieldValues.aiYield[YIELD_FOOD] = iGovernor_FoodWeight;
+ governorValues.yieldValues.aiYield[YIELD_PRODUCTION] = iGovernor_ProductionWeight;
+ governorValues.yieldValues.aiYield[YIELD_COMMERCE] = iGovernor_CommerceWeight;
+
+ for (int iCommerce = 0; iCommerce < NUM_COMMERCE_TYPES; iCommerce++)
+ {
+ governorValues.yieldValues.aiCommerce[iCommerce] = iGovernor_CommerceWeight;
+ }
+
+ governorValues.yieldValues.iGPPRate = iGovernor_CommerceWeight / 2;
+ governorValues.yieldValues.iBonusExperience = 2;
+
+ // now do the real work, modifying these values based on the situation
+ int iHealthLevel = goodHealth() - badHealth(/*bNoAngry*/ false, 1);
+ int iHappyLevel = (bIsNoUnhappiness ? max(3, iHealthLevel + 5) : happyLevel() - unhappyLevel(0));
+
+ governorValues.iHappyLevel = iHappyLevel;
+ governorValues.bGrowPastHappy = false;
+
+ // if food is production, we will set food value to the same as production value
+ if (!bFoodIsProduction)
+ {
+ // if emphasizing food, value food high in all cases, no need to do other checks
+ if (bEmphasizeFood)
+ {
+ governorValues.yieldValues.aiYield[YIELD_FOOD] *= iGovernor_EmphasizeMultiplier;
+ governorValues.bGrowPastHappy = true;
+ }
+ // food value: if food isnt production, then adjust for growth
+ else
+ {
+ // value food much less if we avoiding growth
+ if (AI_isEmphasizeAvoidGrowth())
+ {
+ governorValues.yieldValues.aiYield[YIELD_FOOD] /= 3;
+ governorValues.bGrowPastHappy = false;
+ }
+ // otherwise, do some checks based on our situation
+ else
+ {
+ int iConsumtionPerPop = GC.getFOOD_CONSUMPTION_PER_POPULATION();
+ int iFoodToGrow = max(1, (growthThreshold() * (100 - getMaxFoodKeptPercent())) / 100);
+
+ // if unhealthy, value food less
+ if (iHealthLevel < 0)
+ {
+ governorValues.yieldValues.aiYield[YIELD_FOOD] *= (iHealthLevel < -4) ? 60 : 80;
+ governorValues.yieldValues.aiYield[YIELD_FOOD] /= 100;
+ }
+
+ // if we smallish, and have room to grow, value food more, adjusted by unhealth
+ if (iHappyLevel > 0 && iPopulation < 15)
+ {
+ bool bUnhealthy = (iHealthLevel < 0);
+ governorValues.yieldValues.aiYield[YIELD_FOOD] += (iGovernor_FoodWeight * (bUnhealthy ? 4 : 5)) / 8;
+ }
+
+ // if we are at or above the happy cap, value food less (50% if exactly at cap, 30% if over cap)
+ if (iHappyLevel <= 0)
+ {
+ int iAdjustment = ((iHappyLevel == 0) ? 5 : 3);
+ governorValues.yieldValues.aiYield[YIELD_FOOD] *= iAdjustment;
+ governorValues.yieldValues.aiYield[YIELD_FOOD] /= 10;
+ }
+
+ // if we can pop rush, value food more
+ if (bCanPopRush)
+ {
+ int iBestProductionPerPopulation = 0;
+ int iHurryPopulation = 0;
+ for (int iHurry = 0; iHurry < GC.getNumHurryInfos(); iHurry++)
+ {
+ if (kPlayer.getHurryCount((HurryTypes) iHurry) > 0)
+ {
+ int iProductionPerPopulation = GC.getHurryInfo((HurryTypes) iHurry).getProductionPerPopulation();
+ if (iProductionPerPopulation > iBestProductionPerPopulation)
+ {
+ iBestProductionPerPopulation = iProductionPerPopulation;
+ iHurryPopulation = hurryPopulation((HurryTypes) iHurry);
+ }
+ }
+ }
+
+ // productionWeight * (slaveryProductionPerPop/foodToGrowOnePop) * (percentageCostToRushCurrentBuild) * (4 or 2)
+ int iSlaveryValue = iGovernor_ProductionWeight * iBestProductionPerPopulation * getHurryCostModifier(/*bIgnoreNew*/ true);
+ iSlaveryValue *= bIsNoUnhappiness ? 4 : 2;
+ iSlaveryValue /= (iFoodToGrow * 100 * iConsumtionPerPop);
+
+ // if we have to grow too much to actually use slavery on this build, then value slavery bonus much less
+ int iExtraPopNeeded = max(0, (iHurryPopulation * 2) - iPopulation);
+ iSlaveryValue /= max(1, iExtraPopNeeded - 5);
+
+ if (bEmphasizeProduction)
+ {
+ iSlaveryValue *= iGovernor_EmphasizeMultiplier;
+ }
+
+ governorValues.yieldValues.aiYield[YIELD_FOOD] += iSlaveryValue;
+ governorValues.bGrowPastHappy = (iSlaveryValue > (iGovernor_FoodWeight / 8));
+ }
+
+ // if we can draft, value food more
+ int iMaxConscript;
+ UnitTypes eConscriptUnit;
+ bool bCanConscript = ((iMaxConscript = kPlayer.getMaxConscript()) > 0 && (eConscriptUnit = getConscriptUnit()) != NO_UNIT);
+ if (bCanConscript)
+ {
+ int iConscriptProduction = kPlayer.getProductionNeeded(eConscriptUnit);
+ int iConscriptPopulation = getConscriptPopulation();
+ int iConscriptValue = iGovernor_ProductionWeight * iConscriptProduction;
+ iConscriptValue *= bIsNoUnhappiness ? 3 : range(iNumCities, 1, 12);
+ iConscriptValue /= iConscriptPopulation * iFoodToGrow * iConsumtionPerPop;
+ iConscriptValue /= bIsNoUnhappiness ? 1 : (iMaxConscript * 2);
+
+ governorValues.yieldValues.aiYield[YIELD_FOOD] += iConscriptValue;
+ governorValues.bGrowPastHappy = ((iHappyLevel + iConscriptPopulation) > 0);
+ }
+ }
+ }
+ }
+
+ governorValues.yieldValues.aiYield[YIELD_FOOD] *= 100 + AI_specialYieldMultiplier(YIELD_FOOD);
+ governorValues.yieldValues.aiYield[YIELD_FOOD] /= kPlayer.AI_averageYieldMultiplier(YIELD_FOOD);
+
+ // production
+ governorValues.yieldValues.aiYield[YIELD_PRODUCTION] *= 100 + AI_specialYieldMultiplier(YIELD_PRODUCTION);
+
+ if (!bFoodIsProduction)
+ {
+ // normalize the production... this allows the system to account for rounding
+ // and such while preventing an "out to lunch smoking weed" scenario with
+ // unusually high transient production modifiers.
+ // Other yields don't have transient bonuses in quite the same way.
+
+ governorValues.yieldValues.aiYield[YIELD_PRODUCTION] *= iBaseProductionModifier;
+ governorValues.yieldValues.aiYield[YIELD_PRODUCTION] /= (iBaseProductionModifier + iExtraProductionModifier);
+ }
+
+ if (bEmphasizeProduction)
+ {
+ governorValues.yieldValues.aiYield[YIELD_PRODUCTION] *= iGovernor_EmphasizeMultiplier;
+ }
+
+ governorValues.yieldValues.aiYield[YIELD_PRODUCTION] /= kPlayer.AI_averageYieldMultiplier(YIELD_PRODUCTION);
+
+ // if food is production, then food is worth the same as production
+ if (bFoodIsProduction)
+ {
+ governorValues.yieldValues.aiYield[YIELD_FOOD] = governorValues.yieldValues.aiYield[YIELD_PRODUCTION];
+ }
+
+ // commerce
+
+ // commerce is split into its components, so value overall commerce as zero (should not be used anyway)
+ governorValues.yieldValues.aiYield[YIELD_COMMERCE] = 0;
+
+ // do each commerce component
+ bool bEmphasizeCommerce = AI_isEmphasizeYield(YIELD_COMMERCE);
+ for (int iCommerce = 0; iCommerce < NUM_COMMERCE_TYPES; iCommerce++)
+ {
+ // special culture value
+ if (iCommerce == COMMERCE_CULTURE)
+ {
+ int iCultureRateRank = findCommerceRateRank(COMMERCE_CULTURE);
+ int iCulturalVictoryNumCultureCities = GC.getGameINLINE().culturalVictoryNumCultureCities();
+ bool bTopCultureCity = (iCultureRateRank <= iCulturalVictoryNumCultureCities + 1);
+
+ if (kPlayer.AI_isDoStrategy(AI_STRATEGY_CULTURE3))
+ {
+ governorValues.yieldValues.aiCommerce[COMMERCE_CULTURE] *= bTopCultureCity ? 6 : 3;
+ }
+ else if (kPlayer.AI_isDoStrategy(AI_STRATEGY_CULTURE2))
+ {
+ governorValues.yieldValues.aiCommerce[COMMERCE_CULTURE] *= bTopCultureCity ? 3 : 2;
+ }
+ else
+ {
+ // do we have culture pressure?
+ int iCulturePercent = calculateCulturePercent(ePlayer);
+ if (iCulturePercent < 55)
+ {
+ governorValues.yieldValues.aiCommerce[COMMERCE_CULTURE] *= (2 * (105 - iCulturePercent));
+ governorValues.yieldValues.aiCommerce[COMMERCE_CULTURE] /= 100;
+ }
+ else if (iCulturePercent > 95 && !bTopCultureCity)
+ {
+ governorValues.yieldValues.aiCommerce[COMMERCE_CULTURE] /= 2;
+ }
+ }
+ }
+
+
+ bool bEmphasizeThisCommerce = AI_isEmphasizeCommerce((CommerceTypes) iCommerce);
+ if (bEmphasizeCommerce || bEmphasizeThisCommerce)
+ {
+ governorValues.yieldValues.aiCommerce[iCommerce] *= (bEmphasizeCommerce && bEmphasizeThisCommerce) ? ((3 * iGovernor_EmphasizeMultiplier) / 2) : iGovernor_EmphasizeMultiplier;
+ }
+
+ governorValues.yieldValues.aiCommerce[iCommerce] *= kPlayer.AI_commerceWeight((CommerceTypes) iCommerce);
+ governorValues.yieldValues.aiCommerce[iCommerce] *= kPlayer.AI_averageCommerceExchange((CommerceTypes) iCommerce);
+ governorValues.yieldValues.aiCommerce[iCommerce] *= 100 + AI_specialYieldMultiplier(YIELD_COMMERCE);
+ governorValues.yieldValues.aiCommerce[iCommerce] /= (100 * 100 * kPlayer.AI_averageYieldMultiplier(YIELD_COMMERCE));
+ }
+
+ // GPP
+
+ if (AI_isEmphasizeGreatPeople())
+ {
+ governorValues.yieldValues.iGPPRate *= iGovernor_EmphasizeMultiplier;
+ }
+
+ // if non-human, value based on this cities progress toward a GPP
+ if (!isHuman())
+ {
+ int iProgress = getGreatPeopleProgress();
+ if (iProgress > 0)
+ {
+ int iThreshold = kPlayer.greatPeopleThreshold();
+ governorValues.yieldValues.iGPPRate *= (iProgress * iProgress);
+ governorValues.yieldValues.iGPPRate /= (iThreshold * iThreshold);
+ }
+ }
+
+ // experience
+ int iHasMetCount = GET_TEAM(kPlayer.getTeam()).getHasMetCivCount(true);
+ if (iHasMetCount > 0)
+ {
+ governorValues.yieldValues.iBonusExperience *= 2;
+ }
+
+ int iProductionRank = findYieldRateRank(YIELD_PRODUCTION);
+ if (iProductionRank <= iNumCities/2 + 1)
+ {
+ governorValues.yieldValues.iBonusExperience += 4;
+ }
+
+ governorValues.yieldValues.iBonusExperience += (getMilitaryProductionModifier() * 8) / 100;
+
+ // we are intialized
+ governorValues.bInitialized = true;
+}
+
+void CvCityAI::AI_buildGovernorCitizens(bool bReassign, bool bCapWorkers, bool bCapSpecialists)
+{
+ // do calc if needed
+ if (!m_governorValues.bInitialized)
+ {
+ AI_calculateGovernorValues(m_governorValues);
+ }
+
+ AI_buildGovernorCitizens(m_governorCitizens, m_governorValues, bReassign, bCapWorkers, bCapSpecialists);
+}
+
+void CvCityAI::AI_buildGovernorCitizens(CvSortedCitizens& governorCitizens, CvGovernorValues& governorValues, bool bReassign, bool bCapWorkers, bool bCapSpecialists)
+{
+ PROFILE_FUNC();
+
+ PlayerTypes ePlayer = getOwnerINLINE();
+ CvPlayerAI& kPlayer = GET_PLAYER(ePlayer);
+
+ // clear the list of sorted citizens
+ governorCitizens.clear();
+
+ // template
+ int iValue;
+ CvGovernorCitizen citizen;
+
+ // plots
+ for (int iI = 0; iI < NUM_CITY_PLOTS; iI++)
+ {
+ CvPlot* pLoopPlot = getCityIndexPlot(iI);
+ if (pLoopPlot != NULL)
+ {
+ if (canWork(pLoopPlot))
+ {
+ // all plots have this citizen part in common
+ citizen.iFood = pLoopPlot->getYield(YIELD_FOOD);
+ citizen.iIndex = iI;
+ citizen.bIsSpecialist = false;
+
+ if (iI == CITY_HOME_PLOT)
+ {
+ // arbitrary value for home plot (no need to calculate it)
+ iValue = 1;
+
+ // finish citizen info
+ citizen.iInitialCount = 0;
+ citizen.iForcedCount = 0;
+ citizen.iCount = 0;
+ citizen.iMax = 0;
+ citizen.iFreeCount = (getPopulation() > 0) ? 1 : 0;
+ }
+ else
+ {
+ // get value
+ iValue = AI_plotValue(pLoopPlot, governorValues);
+
+ // finish citizen info
+ citizen.iInitialCount = (!bReassign && isWorkingPlot(iI)) ? 1 : 0;
+ citizen.iForcedCount = 0;
+ citizen.iCount = citizen.iInitialCount;
+ citizen.iMax = (bCapWorkers) ? citizen.iCount: 1;
+ citizen.iFreeCount = 0;
+ }
+
+ // add to the sorted citizens
+ governorCitizens.insert(std::make_pair(iValue, citizen));
+ }
+ // also add plots we were working but can no longer work (so they will be removed)
+ else if (isWorkingPlot(iI))
+ {
+ iValue = 0;
+
+ // set up citizen
+ citizen.iFood = 0;
+
+ citizen.iIndex = iI;
+ citizen.bIsSpecialist = false;
+
+ citizen.iInitialCount = 0;
+ citizen.iForcedCount = 0;
+ citizen.iCount = 0;
+ citizen.iMax = 0;
+
+ citizen.iFreeCount = 0;
+
+ // add to the sorted citizens
+ governorCitizens.insert(std::make_pair(iValue, citizen));
+ }
+ }
+ }
+
+ // specialists
+ SpecialistTypes eDefaultSpecialist = (SpecialistTypes) GC.getDefineINT("DEFAULT_SPECIALIST");
+ for (int iI = 0; iI < GC.getNumSpecialistInfos(); iI++)
+ {
+ SpecialistTypes eSpecialist = (SpecialistTypes) iI;
+
+ // get value
+ iValue = AI_specialistValue(eSpecialist, governorValues);
+
+ // set up citizen
+ citizen.iFood = kPlayer.specialistYield(eSpecialist, YIELD_FOOD);
+
+ citizen.iIndex = eSpecialist;
+ citizen.bIsSpecialist = true;
+
+ citizen.iInitialCount = bReassign ? 0 : getSpecialistCount(eSpecialist);
+ citizen.iForcedCount = getForceSpecialistCount(eSpecialist);
+ citizen.iCount = citizen.iInitialCount;
+ citizen.iMax = (!bCapSpecialists) ? ((eSpecialist == eDefaultSpecialist || kPlayer.isSpecialistValid(eSpecialist)) ? MAX_SHORT : getMaxSpecialistCount(eSpecialist)) : citizen.iCount;
+
+ citizen.iFreeCount = getFreeSpecialistCount(eSpecialist);
+
+ // add to the sorted citizens
+ governorCitizens.insert(std::make_pair(iValue, citizen));
+ }
+}
+
+void CvCityAI::AI_workGovernorCitizens(CvSortedCitizens& governorCitizens)
+{
+#ifdef USE_NEW_CITY_GOVERNOR
+ // actually work (or stop working) each citizen in our list
+ for (CvCitizensIterator it = governorCitizens.begin(); it != governorCitizens.end(); ++it)
+ {
+ CvGovernorCitizen* pCitizen = getCitizen(it);
+
+ // specialists
+ if (pCitizen->bIsSpecialist)
+ {
+ setSpecialistCount((SpecialistTypes) pCitizen->iIndex, pCitizen->iCount);
+ }
+ // non-specialists
+ else
+ {
+ setWorkingPlot(pCitizen->iIndex, (pCitizen->iCount > 0 || pCitizen->iFreeCount > 0));
+ }
+ }
+
+ if ((getOwnerINLINE() == GC.getGameINLINE().getActivePlayer()) && isCitySelected())
+ {
+ gDLL->getInterfaceIFace()->setDirty(CitizenButtons_DIRTY_BIT, true);
+ }
+#endif
+}
+
+
+int CvCityAI::AI_plotValue(CvPlot* pPlot)
+{
+ // do calc if needed
+ if (!m_governorValues.bInitialized)
+ {
+ AI_calculateGovernorValues(m_governorValues);
+ }
+
+ return AI_plotValue(pPlot, m_governorValues);
+}
+
+int CvCityAI::AI_plotValue(CvPlot* pPlot, CvGovernorValues& governorValues)
+{
+ PROFILE_FUNC();
+
+ // cache some stuff
+ PlayerTypes ePlayer = getOwnerINLINE();
+ CvPlayerAI& kPlayer = GET_PLAYER(ePlayer);
+
+ // return result
+ int iValue = 0;
+
+ // store all the yield info here
+ CvCompleteYield completeYield;
+ for (int iYield = 0; iYield < NUM_YIELD_TYPES; iYield++)
+ {
+ // value food in a non-linear fasion unless food is production (first 2 85%, 2nd two 100%, all after that 115%)
+ if (iYield == YIELD_FOOD && !isFoodProduction())
+ {
+ int iConsumtionPerPop = GC.getFOOD_CONSUMPTION_PER_POPULATION();
+
+ int iFoodYield = pPlot->getYield(YIELD_FOOD);
+ int iLowestTier = min(iFoodYield, iConsumtionPerPop);
+ int iMiddleTier = min(iFoodYield - iLowestTier, iConsumtionPerPop);
+ int iHighestTier = max(0, iFoodYield - iMiddleTier - iLowestTier);
+
+ completeYield.aiYield[YIELD_FOOD] = (i...
[truncated message content] |