diff --git a/CvGameCoreDLL_Expansion2/CvBuildingClasses.cpp b/CvGameCoreDLL_Expansion2/CvBuildingClasses.cpp index bce990eacd..52d27b23fd 100644 --- a/CvGameCoreDLL_Expansion2/CvBuildingClasses.cpp +++ b/CvGameCoreDLL_Expansion2/CvBuildingClasses.cpp @@ -52,6 +52,7 @@ CvBuildingEntry::CvBuildingEntry(void): m_iGrantsRandomResourceTerritory(0), m_bPuppetPurchaseOverride(false), m_bAllowsPuppetPurchase(false), + m_bNoStarvationNonSpecialist(false), m_iGetCooldown(0), m_bTradeRouteInvulnerable(false), m_iTRSpeedBoost(0), @@ -926,6 +927,7 @@ bool CvBuildingEntry::CacheResults(Database::Results& kResults, CvDatabaseUtilit m_iGrantsRandomResourceTerritory = kResults.GetInt("GrantsRandomResourceTerritory"); m_bPuppetPurchaseOverride = kResults.GetBool("PuppetPurchaseOverride"); m_bAllowsPuppetPurchase = kResults.GetBool("AllowsPuppetPurchase"); + m_bNoStarvationNonSpecialist = kResults.GetBool("NoStarvationNonSpecialist"); m_iGetCooldown = kResults.GetInt("PurchaseCooldown"); m_iNumPoliciesNeeded = kResults.GetInt("NumPoliciesNeeded"); #endif @@ -1832,11 +1834,16 @@ bool CvBuildingEntry::IsPuppetPurchaseOverride() const { return m_bPuppetPurchaseOverride; } -/// Dpes this building unlock purchasing in any city? +/// Does this building unlock purchasing in any city? bool CvBuildingEntry::IsAllowsPuppetPurchase() const { return m_bAllowsPuppetPurchase; } +/// Does this building prevent non-specialists to consume more food than the city's food production? +bool CvBuildingEntry::IsNoStarvationNonSpecialist() const +{ + return m_bNoStarvationNonSpecialist; +} /// Does this building have a cooldown cost when purchased? int CvBuildingEntry::GetCooldown() const { diff --git a/CvGameCoreDLL_Expansion2/CvBuildingClasses.h b/CvGameCoreDLL_Expansion2/CvBuildingClasses.h index 5d19fad101..442730386a 100644 --- a/CvGameCoreDLL_Expansion2/CvBuildingClasses.h +++ b/CvGameCoreDLL_Expansion2/CvBuildingClasses.h @@ -142,6 +142,7 @@ class CvBuildingEntry: public CvBaseInfo int GrantsRandomResourceTerritory() const; bool IsPuppetPurchaseOverride() const; bool IsAllowsPuppetPurchase() const; + bool IsNoStarvationNonSpecialist() const; int GetCooldown() const; bool IsTradeRouteInvulnerable() const; int GetTRSpeedBoost() const; @@ -711,6 +712,7 @@ class CvBuildingEntry: public CvBaseInfo int m_iGrantsRandomResourceTerritory; bool m_bPuppetPurchaseOverride; bool m_bAllowsPuppetPurchase; + bool m_bNoStarvationNonSpecialist; int m_iGetCooldown; bool m_bTradeRouteInvulnerable; int m_iTRSpeedBoost; diff --git a/CvGameCoreDLL_Expansion2/CvCity.cpp b/CvGameCoreDLL_Expansion2/CvCity.cpp index ebfcd29c1c..0f1f8c31a0 100644 --- a/CvGameCoreDLL_Expansion2/CvCity.cpp +++ b/CvGameCoreDLL_Expansion2/CvCity.cpp @@ -452,6 +452,7 @@ CvCity::CvCity() : , m_iResourceDiversityModifier() , m_iNoUnhappfromXSpecialists() , m_bNoWarmonger() + , m_iNoStarvationNonSpecialist() #endif #if defined(MOD_BALANCE_CORE) , m_abIsBestForWonder() @@ -1390,6 +1391,7 @@ void CvCity::reset(int iID, PlayerTypes eOwner, int iX, int iY, bool bConstructo m_iResourceDiversityModifier = 0; m_iNoUnhappfromXSpecialists = 0; m_bNoWarmonger = false; + m_iNoStarvationNonSpecialist = 0; #endif m_aiEconomicValue.resize(MAX_CIV_PLAYERS); for (iI = 0; iI < MAX_CIV_PLAYERS; iI++) @@ -14227,6 +14229,10 @@ void CvCity::processBuilding(BuildingTypes eBuilding, int iChange, bool bFirst, { SetAllowPuppetPurchase(pBuildingInfo->IsAllowsPuppetPurchase() * iChange > 0); } + if (pBuildingInfo->IsNoStarvationNonSpecialist()) + { + ChangeNoStarvationNonSpecialist(iChange); + } changeGreatPeopleRateModifier(pBuildingInfo->GetGreatPeopleRateModifier() * iChange); changeGPRateModPerMarriage(pBuildingInfo->GetGPRateModPerMarriage() * iChange); @@ -16260,14 +16266,19 @@ int CvCity::foodConsumption(bool bNoAngry, int iExtra) const return foodConsumptionTimes100(bNoAngry, iExtra * 100) / 100; } // -------------------------------------------------------------------------------- -int CvCity::foodConsumptionTimes100(bool /*bNoAngry*/, int iExtra) const +int CvCity::foodConsumptionTimes100(bool /*bNoAngry*/, int iExtra, bool bAssumeNoReductionForNonSpecialists) const { VALIDATE_OBJECT int iSpecialists = GetCityCitizens()->GetTotalSpecialistCount(); int iNonSpecialists = max(0, (getPopulation() - iSpecialists)) + iExtra; - return max(100, foodConsumptionNonSpecialistTimes100() * iNonSpecialists + foodConsumptionSpecialistTimes100() * iSpecialists); + int iConsumptionNonSpecialists = foodConsumptionNonSpecialistTimes100() * iNonSpecialists; + if (IsNoStarvationNonSpecialist() && !bAssumeNoReductionForNonSpecialists) + { + iConsumptionNonSpecialists = min(getYieldRateTimes100(YIELD_FOOD, false), iConsumptionNonSpecialists); + } + return max(100, iConsumptionNonSpecialists + foodConsumptionSpecialistTimes100() * iSpecialists); } @@ -22733,6 +22744,17 @@ bool CvCity::IsNoWarmongerYet() return m_bNoWarmonger; } +void CvCity::ChangeNoStarvationNonSpecialist(int iValue) +{ + VALIDATE_OBJECT + m_iNoStarvationNonSpecialist += iValue; +} +bool CvCity::IsNoStarvationNonSpecialist() const +{ + VALIDATE_OBJECT + return m_iNoStarvationNonSpecialist > 0; +} + int CvCity::GetNumTimesOwned(PlayerTypes ePlayer) const { VALIDATE_OBJECT @@ -31663,6 +31685,7 @@ void CvCity::Serialize(City& city, Visitor& visitor) visitor(city.m_iLocalUnhappinessMod); visitor(city.m_bNoWarmonger); visitor(city.m_iEmpireSizeModifierReduction); + visitor(city.m_iNoStarvationNonSpecialist); visitor(city.m_iDistressFlatReduction); visitor(city.m_iPovertyFlatReduction); visitor(city.m_iIlliteracyFlatReduction); diff --git a/CvGameCoreDLL_Expansion2/CvCity.h b/CvGameCoreDLL_Expansion2/CvCity.h index 0016b87908..52f2434858 100644 --- a/CvGameCoreDLL_Expansion2/CvCity.h +++ b/CvGameCoreDLL_Expansion2/CvCity.h @@ -533,7 +533,7 @@ class CvCity int foodConsumptionNonSpecialistTimes100() const; int foodConsumptionSpecialistTimes100() const; int foodConsumption(bool bNoAngry = false, int iExtra = 0) const; - int foodConsumptionTimes100(bool bNoAngry = false, int iExtra = 0) const; + int foodConsumptionTimes100(bool bNoAngry = false, int iExtra = 0, bool bAssumeNoReductionForNonSpecialists = false) const; int foodDifference(bool bJustCheckingStarve = false) const; int foodDifferenceTimes100(bool bJustCheckingStarve = false, CvString* toolTipSink = NULL) const; int growthThreshold() const; @@ -1012,6 +1012,9 @@ class CvCity void SetNoWarmonger(bool bValue); bool IsNoWarmongerYet(); + void ChangeNoStarvationNonSpecialist(int iValue); + bool IsNoStarvationNonSpecialist() const; + int GetNumTimesOwned(PlayerTypes ePlayer) const; void SetNumTimesOwned(PlayerTypes ePlayer, int iValue); void ChangeNumTimesOwned(PlayerTypes ePlayer, int iValue); @@ -2021,6 +2024,7 @@ class CvCity int m_iNumNearbyMountains; int m_iLocalUnhappinessMod; bool m_bNoWarmonger; + int m_iNoStarvationNonSpecialist; int m_iEmpireSizeModifierReduction; int m_iDistressFlatReduction; int m_iPovertyFlatReduction; @@ -2421,6 +2425,7 @@ SYNC_ARCHIVE_VAR(int, m_iDeepWaterTileDamage) SYNC_ARCHIVE_VAR(int, m_iNumNearbyMountains) SYNC_ARCHIVE_VAR(int, m_iLocalUnhappinessMod) SYNC_ARCHIVE_VAR(bool, m_bNoWarmonger) +SYNC_ARCHIVE_VAR(int, m_iNoStarvationNonSpecialist) SYNC_ARCHIVE_VAR(int, m_iEmpireSizeModifierReduction) SYNC_ARCHIVE_VAR(int, m_iDistressFlatReduction) SYNC_ARCHIVE_VAR(int, m_iPovertyFlatReduction) diff --git a/CvGameCoreDLL_Expansion2/CvCityCitizens.cpp b/CvGameCoreDLL_Expansion2/CvCityCitizens.cpp index a3717608fb..9363fbca52 100644 --- a/CvGameCoreDLL_Expansion2/CvCityCitizens.cpp +++ b/CvGameCoreDLL_Expansion2/CvCityCitizens.cpp @@ -921,8 +921,8 @@ int CvCityCitizens::GetBaseValuePerYield(YieldTypes eYield, SPrecomputedExpensiv switch (eYield) { case YIELD_FOOD: - // in small cities, treat being under the growth threshold like starvation - if (bAssumeStarving || (bAssumeBelowGrowthThreshold && m_pCity->getPopulation() <= 3)) + // in small cities, treat being under the growth threshold like starvation, unless we focus on a yield other than food + if (bAssumeStarving || (bAssumeBelowGrowthThreshold && m_pCity->getPopulation() <= 3 && (eFocus == NO_CITY_AI_FOCUS_TYPE || eFocus == CITY_AI_FOCUS_TYPE_FOOD || eFocus == CITY_AI_FOCUS_TYPE_GOLD_GROWTH || eFocus == CITY_AI_FOCUS_TYPE_PROD_GROWTH))) iYieldMod = /*500*/ GD_INT_GET(AI_CITIZEN_VALUE_FOOD_STARVING); else if (bAssumeBelowGrowthThreshold || eFocus == CITY_AI_FOCUS_TYPE_FOOD || (pkProcessInfo && pkProcessInfo->getProductionToYieldModifier(eYield) > 0)) iYieldMod = /*32*/ GD_INT_GET(AI_CITIZEN_VALUE_FOOD_NEED_GROWTH); @@ -1085,6 +1085,20 @@ int CvCityCitizens::ScoreYieldChangeQuick(YieldAndGPPList yieldChanges, SPrecomp YieldTypes eYield = (YieldTypes)iI; int iYield100 = yieldChanges.yield[iI]; + // Inca exception: if we're already under the non-specialist food consumption threshold, we can safely remove even more food ... + if (m_pCity->IsNoStarvationNonSpecialist() && eYield == YIELD_FOOD && iYield100 < 0 && cache.iFoodConsumptionTimes100 <= cache.iFoodConsumptionAssumeNoReductionNonSpecialistsTimes100) + { + // ... unless we're adding a specialist + int iNumSpecialistAdded = 0; + for (int iI = 0; iI < (int)yieldChanges.iNumSpecialists.size(); iI++) + { + iNumSpecialistAdded += yieldChanges.iNumSpecialists[iI]; + } + if (iNumSpecialistAdded <= 0) + continue; + + } + if (iYield100 != 0) { int iYieldMod = GetBaseValuePerYield(eYield, cache, bAssumeStarving, bAssumeBelowGrowthThreshold, bAssumeInDebt); @@ -1243,6 +1257,13 @@ int CvCityCitizens::ScoreYieldChange(YieldAndGPPList yieldChanges, SPrecomputedE int iNetFoodNow = cache.iFoodRateTimes100 - cache.iFoodConsumptionTimes100; // food consumption also depends on how many specialists are worked int iNetFoodThen = iNetFoodNow + iYield100 - iNumSpecialistsAdded * (m_pCity->foodConsumptionSpecialistTimes100() - m_pCity->foodConsumptionNonSpecialistTimes100()); + if (m_pCity->IsNoStarvationNonSpecialist()) + { + // if non-specialists can't cause starvation, the calculation is more complicated. we have to recalculate current and future food consumption by non-specialists manually + // iNetFoodThen = iNetFoodNow + (Change in Food Rate) - (Change in Food Consumption) + // with (Change in Food Consumption) = (Future Food Consumption Non-Specialists) - (Current Food Consumption Non-Specialists) + (Change in Food Consumption Specialists) + iNetFoodThen = iNetFoodNow + iYield100 - (min(cache.iFoodRateTimes100 + iYield100, cache.iFoodConsumptionAssumeNoReductionNonSpecialistsTimes100 - iNumSpecialistsAdded * m_pCity->foodConsumptionNonSpecialistTimes100()) - min(cache.iFoodRateTimes100, cache.iFoodConsumptionAssumeNoReductionNonSpecialistsTimes100) + iNumSpecialistsAdded * m_pCity->foodConsumptionSpecialistTimes100()); + } // special case of building a settler: all excess food is converted into production if (m_pCity->isFoodProduction()) @@ -2027,7 +2048,7 @@ void CvCityCitizens::OptimizeWorkedPlots(bool bLogging) const int iNumOptionsSpecialists = 5; //failsafe against switching back and forth, don't try this too often - while (iCount < max(1, m_pCity->getPopulation() / 2)) + while (iCount < max(5, m_pCity->getPopulation() / 2)) { //now the real check. unassigning a tile might cause the valuation of the other tiles to change, so we have to consider combinations int iNetFood100 = m_pCity->getYieldRateTimes100(YIELD_FOOD, false) - m_pCity->foodConsumptionTimes100(); @@ -3680,6 +3701,7 @@ SPrecomputedExpensiveNumbers::SPrecomputedExpensiveNumbers() : iOtherUnhappiness(0), iFoodRateTimes100(0), iFoodConsumptionTimes100(0), + iFoodConsumptionAssumeNoReductionNonSpecialistsTimes100(0), iFoodCorpMod(0), bWantArt(false), bWantScience(false), @@ -3695,6 +3717,7 @@ void SPrecomputedExpensiveNumbers::update(CvCity* pCity, bool bInsideLoop) iGrowthMod = pCity->getGrowthMods(); iFoodRateTimes100 = pCity->getYieldRateTimes100(YIELD_FOOD, false); iFoodConsumptionTimes100 = pCity->foodConsumptionTimes100(); + iFoodConsumptionAssumeNoReductionNonSpecialistsTimes100 = pCity->foodConsumptionTimes100(false, 0, true); if (MOD_BALANCE_VP) { diff --git a/CvGameCoreDLL_Expansion2/CvCityCitizens.h b/CvGameCoreDLL_Expansion2/CvCityCitizens.h index 871c00a35b..03be43aa11 100644 --- a/CvGameCoreDLL_Expansion2/CvCityCitizens.h +++ b/CvGameCoreDLL_Expansion2/CvCityCitizens.h @@ -19,6 +19,7 @@ struct SPrecomputedExpensiveNumbers bool bNeedUpdate; int iFoodRateTimes100; int iFoodConsumptionTimes100; + int iFoodConsumptionAssumeNoReductionNonSpecialistsTimes100; int iFoodCorpMod; int iFamine; int iDistress; diff --git a/CvGameCoreDLL_Expansion2/CvCityStrategyAI.cpp b/CvGameCoreDLL_Expansion2/CvCityStrategyAI.cpp index f177aa1677..3706e1b9f4 100644 --- a/CvGameCoreDLL_Expansion2/CvCityStrategyAI.cpp +++ b/CvGameCoreDLL_Expansion2/CvCityStrategyAI.cpp @@ -5240,6 +5240,15 @@ int CityStrategyAIHelpers::GetBuildingBasicValue(CvCity *pCity, BuildingTypes eB { iValue += pkBuildingInfo->GetFoodKept() * pCity->getPopulation(); } + if (pkBuildingInfo->IsNoStarvationNonSpecialist() && !pCity->IsNoStarvationNonSpecialist()) + { + iValue += 10 * pCity->getPopulation(); + if (pCity->foodDifferenceTimes100(false) < 0) + { + // higher value if we are starving + iValue += (-2) * pCity->foodDifferenceTimes100(false); + } + } if (pkBuildingInfo->AllowsFoodTradeRoutes()) {