diff --git a/(1) Community Patch/Core Files/Core Tables/DifficultyTables.sql b/(1) Community Patch/Core Files/Core Tables/DifficultyTables.sql index b0a7c2d20b..ee42fbf4e6 100644 --- a/(1) Community Patch/Core Files/Core Tables/DifficultyTables.sql +++ b/(1) Community Patch/Core Files/Core Tables/DifficultyTables.sql @@ -1,3 +1,7 @@ +-- Delete the data in the difficulty tables, replaced in DifficultyTables.xml +DELETE FROM HandicapInfo_FreeTechs; +DELETE FROM HandicapInfo_AIFreeTechs; +DELETE FROM HandicapInfo_Goodies; -- This one is replaced at the end of this file instead DELETE FROM HandicapInfos; -- This code is necessary to avoid a UI glitch where difficulty levels do not display properly on game creation, do not remove it @@ -226,3 +230,53 @@ VALUES ('GOODY_GOLDEN_AGE', 'TXT_KEY_GOODY_GOLDEN_AGE', 'TXT_KEY_GOODY_CHOOSE_GOLDEN_AGE', 0, 200, 0, 0), ('GOODY_TILES', 'TXT_KEY_GOODY_TILES', 'TXT_KEY_GOODY_CHOOSE_FREE_TILES', 0, 0, 4, 0), ('GOODY_SCIENCE', 'TXT_KEY_GOODY_SCIENCE', 'TXT_KEY_GOODY_CHOOSE_SCIENCE', 0, 0, 0, 35); + +-- Re-add Goody Hut rewards +CREATE TEMP TABLE HandicapsList ( + HandicapType text +); + +INSERT INTO HandicapsList + (HandicapType) +VALUES + ('HANDICAP_SETTLER'), + ('HANDICAP_CHIEFTAIN'), + ('HANDICAP_WARLORD'), + ('HANDICAP_PRINCE'), + ('HANDICAP_KING'), + ('HANDICAP_EMPEROR'), + ('HANDICAP_IMMORTAL'), + ('HANDICAP_DEITY'), + ('HANDICAP_AI_DEFAULT'); + +CREATE TEMP TABLE GoodyHutRewards ( + GoodyType text +); + +INSERT INTO GoodyHutRewards + (GoodyType) +VALUES + ('GOODY_POPULATION'), + ('GOODY_CULTURE'), + ('GOODY_PANTHEON_FAITH'), + ('GOODY_PROPHET_FAITH'), + ('GOODY_GOLD'), + ('GOODY_MAP'), + ('GOODY_TECH'), + ('GOODY_REVEAL_NEARBY_BARBS'), + ('GOODY_UPGRADE_UNIT'); + +-- Settler-exclusive rewards +INSERT INTO HandicapInfo_Goodies + ('HANDICAP_SETTLER', 'GOODY_WORKER'), + ('HANDICAP_SETTLER', 'GOODY_SETTLER'); + +-- General rewards +INSERT INTO HandicapInfo_Goodies + (HandicapType, GoodyType) +SELECT + a.HandicapType, b.GoodyType +FROM HandicapsList a, GoodyHutRewards b; + +DROP TABLE HandicapsList; +DROP TABLE GoodyHutRewards; \ No newline at end of file diff --git a/(1) Community Patch/Core Files/Core Tables/DifficultyTables.xml b/(1) Community Patch/Core Files/Core Tables/DifficultyTables.xml index f91da55ef2..a0b97c5ecf 100644 --- a/(1) Community Patch/Core Files/Core Tables/DifficultyTables.xml +++ b/(1) Community Patch/Core Files/Core Tables/DifficultyTables.xml @@ -1,6 +1,6 @@ - 0 @@ -1065,348 +847,224 @@ TECH_ARCHERY - - - - HANDICAP_SETTLER - GOODY_POPULATION - - - HANDICAP_SETTLER - GOODY_CULTURE - - - HANDICAP_SETTLER - GOODY_PANTHEON_FAITH - - - HANDICAP_SETTLER - GOODY_PROPHET_FAITH - - - HANDICAP_SETTLER - GOODY_GOLD - - - HANDICAP_SETTLER - GOODY_MAP - - - HANDICAP_SETTLER - GOODY_TECH - - - HANDICAP_SETTLER - GOODY_REVEAL_NEARBY_BARBS - - - HANDICAP_SETTLER - GOODY_UPGRADE_UNIT - - - - HANDICAP_SETTLER - GOODY_WORKER - - - HANDICAP_SETTLER - GOODY_SETTLER - - - - HANDICAP_CHIEFTAIN - GOODY_POPULATION - - - HANDICAP_CHIEFTAIN - GOODY_CULTURE - - - HANDICAP_CHIEFTAIN - GOODY_PANTHEON_FAITH - - - HANDICAP_CHIEFTAIN - GOODY_PROPHET_FAITH - - - HANDICAP_CHIEFTAIN - GOODY_GOLD - - - HANDICAP_CHIEFTAIN - GOODY_MAP - - - HANDICAP_CHIEFTAIN - GOODY_TECH - - - HANDICAP_CHIEFTAIN - GOODY_REVEAL_NEARBY_BARBS - - - HANDICAP_CHIEFTAIN - GOODY_UPGRADE_UNIT - - - - HANDICAP_WARLORD - GOODY_POPULATION - - - HANDICAP_WARLORD - GOODY_CULTURE - - - HANDICAP_WARLORD - GOODY_PANTHEON_FAITH - - - HANDICAP_WARLORD - GOODY_PROPHET_FAITH - - - HANDICAP_WARLORD - GOODY_GOLD - - - HANDICAP_WARLORD - GOODY_MAP - - - HANDICAP_WARLORD - GOODY_TECH - - - HANDICAP_WARLORD - GOODY_REVEAL_NEARBY_BARBS - - - HANDICAP_WARLORD - GOODY_UPGRADE_UNIT - - - - HANDICAP_PRINCE - GOODY_POPULATION - - - HANDICAP_PRINCE - GOODY_CULTURE - - - HANDICAP_PRINCE - GOODY_PANTHEON_FAITH - - - HANDICAP_PRINCE - GOODY_PROPHET_FAITH - - - HANDICAP_PRINCE - GOODY_GOLD - - - HANDICAP_PRINCE - GOODY_MAP - - - HANDICAP_PRINCE - GOODY_TECH - - - HANDICAP_PRINCE - GOODY_REVEAL_NEARBY_BARBS - - - HANDICAP_PRINCE - GOODY_UPGRADE_UNIT - - - - HANDICAP_KING - GOODY_POPULATION - - - HANDICAP_KING - GOODY_CULTURE - - - HANDICAP_KING - GOODY_PANTHEON_FAITH - - - HANDICAP_KING - GOODY_PROPHET_FAITH - - - HANDICAP_KING - GOODY_GOLD - - - HANDICAP_KING - GOODY_MAP - - - HANDICAP_KING - GOODY_TECH - - - HANDICAP_KING - GOODY_REVEAL_NEARBY_BARBS - - - HANDICAP_KING - GOODY_UPGRADE_UNIT - - - - HANDICAP_EMPEROR - GOODY_POPULATION - - - HANDICAP_EMPEROR - GOODY_CULTURE - - - HANDICAP_EMPEROR - GOODY_PANTHEON_FAITH - - - HANDICAP_EMPEROR - GOODY_PROPHET_FAITH - - - HANDICAP_EMPEROR - GOODY_GOLD - - - HANDICAP_EMPEROR - GOODY_MAP - - - HANDICAP_EMPEROR - GOODY_TECH - - - HANDICAP_EMPEROR - GOODY_REVEAL_NEARBY_BARBS - - - HANDICAP_EMPEROR - GOODY_UPGRADE_UNIT - - - - HANDICAP_IMMORTAL - GOODY_POPULATION - - - HANDICAP_IMMORTAL - GOODY_CULTURE - - - HANDICAP_IMMORTAL - GOODY_PANTHEON_FAITH - - - HANDICAP_IMMORTAL - GOODY_PROPHET_FAITH - - - HANDICAP_IMMORTAL - GOODY_GOLD - - - HANDICAP_IMMORTAL - GOODY_MAP - - - HANDICAP_IMMORTAL - GOODY_TECH - - - HANDICAP_IMMORTAL - GOODY_REVEAL_NEARBY_BARBS - - - HANDICAP_IMMORTAL - GOODY_UPGRADE_UNIT - - - - HANDICAP_DEITY - GOODY_POPULATION - - - HANDICAP_DEITY - GOODY_CULTURE - - - HANDICAP_DEITY - GOODY_PANTHEON_FAITH - - - HANDICAP_DEITY - GOODY_PROPHET_FAITH - - - HANDICAP_DEITY - GOODY_GOLD - - - HANDICAP_DEITY - GOODY_MAP - - - HANDICAP_DEITY - GOODY_TECH - - - HANDICAP_DEITY - GOODY_REVEAL_NEARBY_BARBS - - - HANDICAP_DEITY - GOODY_UPGRADE_UNIT - - - - HANDICAP_AI_DEFAULT - GOODY_POPULATION - - - HANDICAP_AI_DEFAULT - GOODY_CULTURE - - - HANDICAP_AI_DEFAULT - GOODY_PANTHEON_FAITH - - - HANDICAP_AI_DEFAULT - GOODY_PROPHET_FAITH - - - HANDICAP_AI_DEFAULT - GOODY_GOLD - - - HANDICAP_AI_DEFAULT - GOODY_MAP - - - HANDICAP_AI_DEFAULT - GOODY_TECH - - - HANDICAP_AI_DEFAULT - GOODY_REVEAL_NEARBY_BARBS - - - HANDICAP_AI_DEFAULT - GOODY_UPGRADE_UNIT - - - \ No newline at end of file + + \ No newline at end of file diff --git a/(1) Community Patch/Core Files/Core Values/CoreDefines.sql b/(1) Community Patch/Core Files/Core Values/CoreDefines.sql index 8e3af5643b..e57cfa3cfd 100644 --- a/(1) Community Patch/Core Files/Core Values/CoreDefines.sql +++ b/(1) Community Patch/Core Files/Core Values/CoreDefines.sql @@ -1497,6 +1497,7 @@ INSERT INTO Defines (Name, Value) SELECT 'VASSALAGE_VASSAL_MASTER_CITY_PERCENT_T INSERT INTO Defines (Name, Value) SELECT 'VASSALAGE_VASSAL_MASTER_POP_PERCENT_THRESHOLD', 60; INSERT INTO Defines (Name, Value) SELECT 'VASSALAGE_CAPITULATE_BASE_THRESHOLD', 100; -- How likely is a vassal to voluntarily capitulate? +INSERT INTO Defines (Name, Value) SELECT 'VASSALAGE_LIBERATE_BASE_THRESHOLD', 100; -- How likely is a master to voluntarily liberate a vassal? -- Border growth tile selection UPDATE Defines SET Value = 200 WHERE Name = 'PLOT_INFLUENCE_RING_COST'; diff --git a/(2) Vox Populi/Database Changes/Difficulty/DifficultyChanges.sql b/(2) Vox Populi/Database Changes/Difficulty/DifficultyChanges.sql index bf778a7ee2..bbba135c28 100644 --- a/(2) Vox Populi/Database Changes/Difficulty/DifficultyChanges.sql +++ b/(2) Vox Populi/Database Changes/Difficulty/DifficultyChanges.sql @@ -1,3 +1,57 @@ +-- New Goody Hut rewards +CREATE TEMP TABLE HandicapsList ( + HandicapType text +); + +INSERT INTO HandicapsList + (HandicapType) +VALUES + ('HANDICAP_SETTLER'), + ('HANDICAP_CHIEFTAIN'), + ('HANDICAP_WARLORD'), + ('HANDICAP_PRINCE'), + ('HANDICAP_KING'), + ('HANDICAP_EMPEROR'), + ('HANDICAP_IMMORTAL'), + ('HANDICAP_DEITY'), + ('HANDICAP_AI_DEFAULT'); + +CREATE TEMP TABLE GoodyHutRewards ( + GoodyType text +); + +INSERT INTO GoodyHutRewards + (GoodyType) +VALUES + ('GOODY_POPULATION'), + ('GOODY_CULTURE'), + ('GOODY_PANTHEON_FAITH'), + ('GOODY_PROPHET_FAITH'), + ('GOODY_GOLD'), + ('GOODY_MAP'), + ('GOODY_UPGRADE_UNIT'), + ('GOODY_PRODUCTION'), + ('GOODY_GOLDEN_AGE'), + ('GOODY_TILES'), + ('GOODY_SCIENCE'); + +-- Settler-exclusive rewards +INSERT INTO HandicapInfo_Goodies + ('HANDICAP_SETTLER', 'GOODY_WORKER'), + ('HANDICAP_SETTLER', 'GOODY_SETTLER'); + +-- General rewards +INSERT INTO HandicapInfo_Goodies + (HandicapType, GoodyType) +SELECT + a.HandicapType, b.GoodyType +FROM HandicapsList a, GoodyHutRewards b; + +DROP TABLE HandicapsList; +DROP TABLE GoodyHutRewards; + + + CREATE TEMP TABLE TriggerYields ( HistoricEventType text, YieldType text diff --git a/(2) Vox Populi/Database Changes/Difficulty/DifficultyChanges.xml b/(2) Vox Populi/Database Changes/Difficulty/DifficultyChanges.xml index 7a84d3a84b..18d12c898a 100644 --- a/(2) Vox Populi/Database Changes/Difficulty/DifficultyChanges.xml +++ b/(2) Vox Populi/Database Changes/Difficulty/DifficultyChanges.xml @@ -1,6 +1,6 @@ @@ -958,420 +739,226 @@ 100 - - - - HANDICAP_SETTLER - GOODY_POPULATION - - - HANDICAP_SETTLER - GOODY_CULTURE - - - HANDICAP_SETTLER - GOODY_PANTHEON_FAITH - - - HANDICAP_SETTLER - GOODY_PROPHET_FAITH - - - HANDICAP_SETTLER - GOODY_GOLD - - - HANDICAP_SETTLER - GOODY_MAP - - - HANDICAP_SETTLER - GOODY_UPGRADE_UNIT - - - HANDICAP_SETTLER - GOODY_PRODUCTION - - - HANDICAP_SETTLER - GOODY_GOLDEN_AGE - - - HANDICAP_SETTLER - GOODY_TILES - - - HANDICAP_SETTLER - GOODY_SCIENCE - - - - HANDICAP_SETTLER - GOODY_WORKER - - - HANDICAP_SETTLER - GOODY_SETTLER - - - - HANDICAP_CHIEFTAIN - GOODY_POPULATION - - - HANDICAP_CHIEFTAIN - GOODY_CULTURE - - - HANDICAP_CHIEFTAIN - GOODY_PANTHEON_FAITH - - - HANDICAP_CHIEFTAIN - GOODY_PROPHET_FAITH - - - HANDICAP_CHIEFTAIN - GOODY_GOLD - - - HANDICAP_CHIEFTAIN - GOODY_MAP - - - HANDICAP_CHIEFTAIN - GOODY_UPGRADE_UNIT - - - HANDICAP_CHIEFTAIN - GOODY_PRODUCTION - - - HANDICAP_CHIEFTAIN - GOODY_GOLDEN_AGE - - - HANDICAP_CHIEFTAIN - GOODY_TILES - - - HANDICAP_CHIEFTAIN - GOODY_SCIENCE - - - - HANDICAP_WARLORD - GOODY_POPULATION - - - HANDICAP_WARLORD - GOODY_CULTURE - - - HANDICAP_WARLORD - GOODY_PANTHEON_FAITH - - - HANDICAP_WARLORD - GOODY_PROPHET_FAITH - - - HANDICAP_WARLORD - GOODY_GOLD - - - HANDICAP_WARLORD - GOODY_MAP - - - HANDICAP_WARLORD - GOODY_UPGRADE_UNIT - - - HANDICAP_WARLORD - GOODY_PRODUCTION - - - HANDICAP_WARLORD - GOODY_GOLDEN_AGE - - - HANDICAP_WARLORD - GOODY_TILES - - - HANDICAP_WARLORD - GOODY_SCIENCE - - - - HANDICAP_PRINCE - GOODY_POPULATION - - - HANDICAP_PRINCE - GOODY_CULTURE - - - HANDICAP_PRINCE - GOODY_PANTHEON_FAITH - - - HANDICAP_PRINCE - GOODY_PROPHET_FAITH - - - HANDICAP_PRINCE - GOODY_GOLD - - - HANDICAP_PRINCE - GOODY_MAP - - - HANDICAP_PRINCE - GOODY_UPGRADE_UNIT - - - HANDICAP_PRINCE - GOODY_PRODUCTION - - - HANDICAP_PRINCE - GOODY_GOLDEN_AGE - - - HANDICAP_PRINCE - GOODY_TILES - - - HANDICAP_PRINCE - GOODY_SCIENCE - - - - HANDICAP_KING - GOODY_POPULATION - - - HANDICAP_KING - GOODY_CULTURE - - - HANDICAP_KING - GOODY_PANTHEON_FAITH - - - HANDICAP_KING - GOODY_PROPHET_FAITH - - - HANDICAP_KING - GOODY_GOLD - - - HANDICAP_KING - GOODY_MAP - - - HANDICAP_KING - GOODY_UPGRADE_UNIT - - - HANDICAP_KING - GOODY_PRODUCTION - - - HANDICAP_KING - GOODY_GOLDEN_AGE - - - HANDICAP_KING - GOODY_TILES - - - HANDICAP_KING - GOODY_SCIENCE - - - - HANDICAP_EMPEROR - GOODY_POPULATION - - - HANDICAP_EMPEROR - GOODY_CULTURE - - - HANDICAP_EMPEROR - GOODY_PANTHEON_FAITH - - - HANDICAP_EMPEROR - GOODY_PROPHET_FAITH - - - HANDICAP_EMPEROR - GOODY_GOLD - - - HANDICAP_EMPEROR - GOODY_MAP - - - HANDICAP_EMPEROR - GOODY_UPGRADE_UNIT - - - HANDICAP_EMPEROR - GOODY_PRODUCTION - - - HANDICAP_EMPEROR - GOODY_GOLDEN_AGE - - - HANDICAP_EMPEROR - GOODY_TILES - - - HANDICAP_EMPEROR - GOODY_SCIENCE - - - - HANDICAP_IMMORTAL - GOODY_POPULATION - - - HANDICAP_IMMORTAL - GOODY_CULTURE - - - HANDICAP_IMMORTAL - GOODY_PANTHEON_FAITH - - - HANDICAP_IMMORTAL - GOODY_PROPHET_FAITH - - - HANDICAP_IMMORTAL - GOODY_GOLD - - - HANDICAP_IMMORTAL - GOODY_MAP - - - HANDICAP_IMMORTAL - GOODY_UPGRADE_UNIT - - - HANDICAP_IMMORTAL - GOODY_PRODUCTION - - - HANDICAP_IMMORTAL - GOODY_GOLDEN_AGE - - - HANDICAP_IMMORTAL - GOODY_TILES - - - HANDICAP_IMMORTAL - GOODY_SCIENCE - - - - HANDICAP_DEITY - GOODY_POPULATION - - - HANDICAP_DEITY - GOODY_CULTURE - - - HANDICAP_DEITY - GOODY_PANTHEON_FAITH - - - HANDICAP_DEITY - GOODY_PROPHET_FAITH - - - HANDICAP_DEITY - GOODY_GOLD - - - HANDICAP_DEITY - GOODY_MAP - - - HANDICAP_DEITY - GOODY_UPGRADE_UNIT - - - HANDICAP_DEITY - GOODY_PRODUCTION - - - HANDICAP_DEITY - GOODY_GOLDEN_AGE - - - HANDICAP_DEITY - GOODY_TILES - - - HANDICAP_DEITY - GOODY_SCIENCE - - - - HANDICAP_AI_DEFAULT - GOODY_POPULATION - - - HANDICAP_AI_DEFAULT - GOODY_CULTURE - - - HANDICAP_AI_DEFAULT - GOODY_PANTHEON_FAITH - - - HANDICAP_AI_DEFAULT - GOODY_PROPHET_FAITH - - - HANDICAP_AI_DEFAULT - GOODY_GOLD - - - HANDICAP_AI_DEFAULT - GOODY_MAP - - - HANDICAP_AI_DEFAULT - GOODY_UPGRADE_UNIT - - - HANDICAP_AI_DEFAULT - GOODY_PRODUCTION - - - HANDICAP_AI_DEFAULT - GOODY_GOLDEN_AGE - - - HANDICAP_AI_DEFAULT - GOODY_TILES - - - HANDICAP_AI_DEFAULT - GOODY_SCIENCE - - - \ No newline at end of file + + \ No newline at end of file diff --git a/(2) Vox Populi/Database Changes/Difficulty/PreDifficultyChanges.sql b/(2) Vox Populi/Database Changes/Difficulty/PreDifficultyChanges.sql index f4395aa248..cab2e59c92 100644 --- a/(2) Vox Populi/Database Changes/Difficulty/PreDifficultyChanges.sql +++ b/(2) Vox Populi/Database Changes/Difficulty/PreDifficultyChanges.sql @@ -1,5 +1,7 @@ --- Delete the data in the difficulty tables, replaced in DifficultyChanges.xml +-- Delete the data in the difficulty tables, replaced in DifficultyChanges.xml and GoodyHutChanges.sql +DELETE FROM HandicapInfo_DifficultyBonus; +DELETE FROM HandicapInfo_AIDifficultyBonus; DELETE FROM HandicapInfo_FreeTechs; DELETE FROM HandicapInfo_AIFreeTechs; DELETE FROM HandicapInfo_Goodies; -DELETE FROM HandicapInfos; +DELETE FROM HandicapInfos; \ No newline at end of file diff --git a/CvGameCoreDLLUtil/include/CvDllInterfaces2.h b/CvGameCoreDLLUtil/include/CvDllInterfaces2.h index 732c810d30..c34822ad7a 100644 --- a/CvGameCoreDLLUtil/include/CvDllInterfaces2.h +++ b/CvGameCoreDLLUtil/include/CvDllInterfaces2.h @@ -103,8 +103,7 @@ class ICvNetMessageHandler3 : public ICvNetMessageHandler2 virtual void DLLCALL ResponseGoodyChoice(PlayerTypes ePlayer, int iPlotX, int iPlotY, GoodyTypes eGoody, int iUnitID) = 0; virtual void DLLCALL ResponseSetSwappableGreatWork(PlayerTypes ePlayer, int iWorkClass, int iWorkIndex) = 0; virtual void DLLCALL ResponseSwapGreatWorks(PlayerTypes ePlayer1, int iWorkIndex1, PlayerTypes PlayerTypes2, int iWorkIndex2) = 0; - virtual void DLLCALL ResponseMoveGreatWorks(PlayerTypes ePlayer, int iCity1, int iBuildingClass1, int iWorkIndex1, - int iCity2, int iBuildingClass2, int iWorkIndex2) = 0; + virtual void DLLCALL ResponseMoveGreatWorks(PlayerTypes ePlayer, int iCity1, int iBuildingClass1, int iWorkIndex1, int iCity2, int iBuildingClass2, int iWorkIndex2) = 0; virtual void DLLCALL ResponseChangeIdeology(PlayerTypes ePlayer) = 0; }; diff --git a/CvGameCoreDLL_Expansion2/CvDiplomacyAI.cpp b/CvGameCoreDLL_Expansion2/CvDiplomacyAI.cpp index d078484a83..aed1b9ebcc 100644 --- a/CvGameCoreDLL_Expansion2/CvDiplomacyAI.cpp +++ b/CvGameCoreDLL_Expansion2/CvDiplomacyAI.cpp @@ -398,6 +398,7 @@ void CvDiplomacyAI::Init(CvPlayer* pPlayer) m_bAvoidDeals = false; m_bIgnoreWarmonger = false; + m_eVassalPlayerToLiberate = NO_PLAYER; m_aGreetPlayers.clear(); @@ -8840,6 +8841,17 @@ void CvDiplomacyAI::SetIgnoreWarmonger(bool bValue) m_bIgnoreWarmonger = bValue; } +// Do we want to liberate this player on this turn? Temporary non-serialized value. +PlayerTypes CvDiplomacyAI::GetVassalPlayerToLiberate() const +{ + return m_eVassalPlayerToLiberate; +} + +void CvDiplomacyAI::SetVassalPlayerToLiberate(PlayerTypes ePlayer) +{ + m_eVassalPlayerToLiberate = ePlayer; +} + /// Who was the last Minor ePlayer bullied that we were protecting? PlayerTypes CvDiplomacyAI::GetOtherPlayerProtectedMinorBullied(PlayerTypes ePlayer) const { @@ -14232,7 +14244,7 @@ int CvDiplomacyAI::CalculateGoldPerTurnLostFromWar(PlayerTypes ePlayer) // Vassal taxes? if (IsMaster(ePlayer)) { - iGPT += GetPlayer()->GetTreasury()->GetVassalTaxContributionTimes100(ePlayer); + iGPT += GetPlayer()->GetTreasury()->GetMyShareOfVassalTaxes(GET_PLAYER(ePlayer).getTeam()); } vector vDefensiveWarAllies = GetDefensiveWarAllies(ePlayer, /*bIncludeMinors*/ true, /*bReverseMode*/ true, /*bNewWarsOnly*/ true); @@ -29119,13 +29131,14 @@ bool CvDiplomacyAI::IsPotentialMilitaryTargetOrThreat(PlayerTypes ePlayer, bool if (GET_PLAYER(*it).GetDiplomacyAI()->IsPlayerCapturedCapital(vMyTeam[i]) || GET_PLAYER(*it).GetDiplomacyAI()->IsPlayerCapturedHolyCity(vMyTeam[i])) return true; // Run these checks last because they're more expensive. - if (GET_PLAYER(*it).GetNumOurCitiesOwnedBy(vMyTeam[i]) > 0) - return true; if (GET_PLAYER(*it).GetDiplomacyAI()->IsUntrustworthy(vMyTeam[i])) return true; if (bVassal && pDiplo->GetVassalTreatmentLevel(*it) <= VASSAL_TREATMENT_MISTREATED) return true; } + + if (GET_PLAYER(*it).GetNumOurCitiesOwnedBy(GetID()) > 0) + return true; } } } @@ -32222,6 +32235,8 @@ void CvDiplomacyAI::DoMakePublicDeclaration(PublicDeclarationTypes eDeclaration, /// Any Major Civs we want to chat with? void CvDiplomacyAI::DoContactMajorCivs() { + DetermineVassalToLiberate(); + // NOTE: This function is broken up into two sections: AI contact opportunities, and then human contact opportunities // This is to prevent a nasty bug where the AI will continue making decisions as the diplo screen is firing up. Making humans // handled at the end prevents the Diplo AI from having this problem @@ -55400,224 +55415,386 @@ void CvDiplomacyAI::DoLiberateMyVassalStatement(PlayerTypes ePlayer, DiploStatem CvAssertMsg(ePlayer >= 0, "DIPLOMACY_AI: Invalid Player Index. Please send Jon this with your last 5 autosaves and what changelist # you're playing."); CvAssertMsg(ePlayer < MAX_MAJOR_CIVS, "DIPLOMACY_AI: Invalid Player Index. Please send Jon this with your last 5 autosaves and what changelist # you're playing."); - if(eStatement == NO_DIPLO_STATEMENT_TYPE) + if (eStatement == NO_DIPLO_STATEMENT_TYPE) { - // Has to be my vassal - if(GET_PLAYER(ePlayer).GetDiplomacyAI()->IsVassal(GetID())) + if (GetVassalPlayerToLiberate() == ePlayer) { DiploStatementTypes eTempStatement = DIPLO_STATEMENT_LIBERATE_VASSAL; - int iTurnsBetweenStatement = 25; + int iTurnsBetweenStatement = 1; - if(IsWantToLiberateVassal(ePlayer)) + if (GetNumTurnsSinceStatementSent(ePlayer, eTempStatement) >= iTurnsBetweenStatement) { - if(GetNumTurnsSinceStatementSent(ePlayer, eTempStatement) >= iTurnsBetweenStatement) + eStatement = eTempStatement; + } + } + } +} + +void CvDiplomacyAI::DetermineVassalToLiberate() +{ + if (GC.getGame().isOption(GAMEOPTION_ALWAYS_WAR)) + return; + + // Humans make all the decisions! + if (m_pPlayer->IsAITeammateOfHuman()) + { + SetVassalPlayerToLiberate(NO_PLAYER); + return; + } + + // Only run this check if we're the team leader + PlayerTypes eMasterTeamLeader = GET_TEAM(GetTeam()).getLeaderID(); + if (eMasterTeamLeader != GetID()) + { + SetVassalPlayerToLiberate(NO_PLAYER); + return; + } + + int iBestScoreForLiberate = INT_MIN; + PlayerTypes eBestCandidate = NO_PLAYER; + for (int iPlayerLoop = 0; iPlayerLoop < MAX_MAJOR_CIVS; iPlayerLoop++) + { + PlayerTypes eLoopPlayer = (PlayerTypes) iPlayerLoop; + if (!GET_PLAYER(eLoopPlayer).isAlive() || GET_PLAYER(eLoopPlayer).getNumCities() == 0) + continue; + + if (GET_PLAYER(eLoopPlayer).IsAITeammateOfHuman()) + continue; + + // Must be either human or the team leader + TeamTypes eTeam = GET_PLAYER(eLoopPlayer).getTeam(); + if (GET_TEAM(eTeam).getLeaderID() == eLoopPlayer || GET_PLAYER(eLoopPlayer).isHuman()) + { + // If human, must be contactable + if (GET_PLAYER(eLoopPlayer).isHuman()) + { + if (GC.getGame().IsAllDiploStatementsDisabled()) + continue; + + if (GC.getGame().isReallyNetworkMultiPlayer() && !MOD_ACTIVE_DIPLOMACY) + continue; + } + + int iScoreForLiberate = 0; + if (IsWantToLiberateVassal(eLoopPlayer, iScoreForLiberate)) + { + if (iScoreForLiberate > iBestScoreForLiberate) { - eStatement = eTempStatement; + eBestCandidate = eLoopPlayer; + iBestScoreForLiberate = iScoreForLiberate; } } } } + + SetVassalPlayerToLiberate(eBestCandidate); } /// Do we want to liberate ePlayer's team? -bool CvDiplomacyAI::IsWantToLiberateVassal(PlayerTypes ePlayer) const +bool CvDiplomacyAI::IsWantToLiberateVassal(PlayerTypes ePlayer, int& iScoreForLiberate) const { - CvAssertMsg(ePlayer >= 0, "DIPLOMACY_AI: Invalid Player Index. Please send Jon this with your last 5 autosaves and what changelist # you're playing."); - CvAssertMsg(ePlayer < MAX_MAJOR_CIVS, "DIPLOMACY_AI: Invalid Player Index. Please send Jon this with your last 5 autosaves and what changelist # you're playing."); - - TeamTypes eMyTeam = GetTeam(); - CvTeam& kMyTeam = GET_TEAM(eMyTeam); + if (ePlayer < 0 || ePlayer >= MAX_MAJOR_CIVS) UNREACHABLE(); + // Can't liberate? Abort! TeamTypes eVassalTeam = GET_PLAYER(ePlayer).getTeam(); - CvTeam& kVassalTeam = GET_TEAM(eVassalTeam); + if (!GET_TEAM(GetTeam()).CanLiberateVassal(eVassalTeam)) + return false; - // Can't liberate? Abort! - if(!kMyTeam.CanLiberateVassal(eVassalTeam)) + // Do not liberate if they have at least 90% of our population + int iNumPop = GET_TEAM(GetTeam()).getTotalPopulation(); + int iNumVassalPop = GET_TEAM(eVassalTeam).getTotalPopulation(); + if ((iNumVassalPop * 100) >= (iNumPop * 90)) return false; - // Shadow AI can't make this decision for teammate - if(kMyTeam.isHuman() && !m_pPlayer->isHuman()) + // Do not liberate if they own other players' capitals + if (GET_PLAYER(ePlayer).GetNumCapitalCities() > 0) return false; - //World conqueror and this guy lost his capital? He's a perma-vassal. - if (m_pPlayer->GetDiplomacyAI()->IsGoingForWorldConquest() && GET_PLAYER(ePlayer).IsHasLostCapital()) + // Do not liberate if they own any of our cities + if (m_pPlayer->GetNumOurCitiesOwnedBy(ePlayer) > 0) return false; - std::vector m_Masters; - std::vector m_Vassals; + // If someone else previously resurrected this vassal and that team is alive, don't liberate + // They will probably fly into the arms of their former protector and come after us for revenge! + TeamTypes eLiberatedByTeam = GET_TEAM(eVassalTeam).GetLiberatedByTeam(); + if (eLiberatedByTeam != NO_TEAM && eLiberatedByTeam != GetTeam() && GET_TEAM(eLiberatedByTeam).isAlive()) + return false; - for (int iPlayerLoop = 0; iPlayerLoop < MAX_MAJOR_CIVS; iPlayerLoop++) + // Refuse if they aren't following the one true ideology + if (GetPlayer()->GetPlayerPolicies()->GetLateGamePolicyTree() != NO_POLICY_BRANCH_TYPE && !IsPlayerSameIdeology(ePlayer)) + return false; + + vector vMasterTeam = GET_TEAM(GetTeam()).getPlayers(); + vector vVassalTeam = GET_TEAM(GET_PLAYER(ePlayer).getTeam()).getPlayers(); + + CivApproachTypes eWorstMasterApproach = NO_CIV_APPROACH; + int iWorstMasterOpinion = SHRT_MIN; + StrengthTypes eVassalStrength = NO_STRENGTH_VALUE; + StrengthTypes eVassalEcoStrength = NO_STRENGTH_VALUE; + InfluenceLevelTypes eMasterInfluence = NO_INFLUENCE_LEVEL; + InfluenceLevelTypes eVassalInfluence = NO_INFLUENCE_LEVEL; + + ReligionTypes eMasterReligion = NO_RELIGION; + int iMostFollowers = 0; + for (size_t i=0; iGetOwnedReligion(); + if (eReligion != NO_RELIGION) { - eLoopPlayer = (PlayerTypes) iPlayerLoop; - if(GET_PLAYER(eLoopPlayer).getTeam() == eMyTeam) - m_Masters.push_back(&GET_PLAYER(eLoopPlayer)); - if(GET_PLAYER(eLoopPlayer).getTeam() == eVassalTeam) - m_Vassals.push_back(&GET_PLAYER(eLoopPlayer)); + // Performance optimization - only consider followers if there's more than one person on the master team + if (iMostFollowers == 0 && i + 1 >= vMasterTeam.size()) + { + eMasterReligion = eReligion; + continue; + } + + int iFollowers = GC.getGame().GetGameReligions()->GetNumFollowers(eMasterReligion); + if (iFollowers > iMostFollowers) + { + eMasterReligion = eReligion; + iMostFollowers = iFollowers; + } } } - CvAssertMsg(m_Masters.size() > 0, "master team expected to be greater than size 0"); - CvAssertMsg(m_Vassals.size() > 0, "vassal team expected to be greater than size 0"); + for (size_t i=0; i::iterator it = m_Masters.begin(); it != m_Masters.end(); ++it) - { - CvPlayer* pMaster = (*it); + CvDiplomacyAI* pDiplo = GET_PLAYER(eMaster).GetDiplomacyAI(); - iTotalGPTTimes100 += pMaster->calculateGoldRateTimes100(); + // Do not liberate if we're going for world conquest + if (pDiplo->IsGoingForWorldConquest() || GET_PLAYER(eMaster).GetNumCapitalCities() > 0) + return false; - // How does one master see each vassal? - int iAverageApproachForOneMaster = 0; - int iAverageOpinionForOneMaster = 0; - int iAverageStrengthScoreForOneMaster = 0; - int iAverageEcoStrengthScoreForOneMaster = 0; + // If we're close to winning, now is not the time to be liberating vassals! + if (pDiplo->IsCloseToAnyVictoryCondition()) + return false; + + // Separate check for World Conquest if the game is already won, since IsCloseToAnyVictoryCondition() will return false + if (GC.getGame().getWinner() != NO_TEAM && pDiplo->IsCloseToWorldConquest()) + return false; - for(std::vector::iterator vIt = m_Vassals.begin(); vIt != m_Vassals.end(); vIt++) + for (size_t j=0; jGetDiplomacyAI()->IsDenouncedPlayer(pMaster->GetID())) + // Check opinion and approach - care only about the worst + // Only liberate if everyone on both teams has neutral and positive values towards each other + // If vassal hates the master (due to mistreatment), master will continue mistreating them + int iCachedOpinionWeight = pDiplo->GetCachedOpinionWeight(eVassal) - pDiplo->GetMasterScore(eVassal); // exclude the diplo bonus they have for just being our vassal + CivApproachTypes eApproach = pDiplo->GetCivApproach(eVassal); + CivApproachTypes eVisibleApproach = GET_PLAYER(eVassal).isHuman() ? CIV_APPROACH_NEUTRAL : pDiplo->GetVisibleApproachTowardsUs(eVassal); + if (eApproach <= CIV_APPROACH_AFRAID || eVisibleApproach <= CIV_APPROACH_GUARDED || iCachedOpinionWeight >= /*30*/ GD_INT_GET(OPINION_THRESHOLD_COMPETITOR)) + return false; + if (eApproach < eWorstMasterApproach) + eWorstMasterApproach = eApproach; + if (iCachedOpinionWeight > iWorstMasterOpinion) + iWorstMasterOpinion = iCachedOpinionWeight; + + // Check strength compared to us - care only about the highest + // Do not liberate if stronger than us - could be a threat + StrengthTypes eStrength = pDiplo->GetRawMilitaryStrengthComparedToUs(eVassal); + if (eVassal > STRENGTH_AVERAGE) return false; + if (eStrength > eVassalStrength) + eVassalStrength = eStrength; - // Did we denounce them? - if(pMaster->GetDiplomacyAI()->IsDenouncedPlayer(pVassal->GetID())) + eStrength = pDiplo->GetEconomicStrengthComparedToUs(eVassal); + if (eVassal > STRENGTH_AVERAGE) return false; + if (eStrength > eVassalEcoStrength) + eVassalEcoStrength = eStrength; - iAverageApproachForOneMaster += (int) pMaster->GetDiplomacyAI()->GetCivApproach(pVassal->GetID()); - iAverageOpinionForOneMaster += (int) pMaster->GetDiplomacyAI()->GetCivOpinion(pVassal->GetID()); - iAverageStrengthScoreForOneMaster += (int) pMaster->GetDiplomacyAI()->GetMilitaryStrengthComparedToUs(pVassal->GetID()); - iAverageEcoStrengthScoreForOneMaster += (int) pMaster->GetDiplomacyAI()->GetEconomicStrengthComparedToUs(pVassal->GetID()); - - // Only care about the highest - InfluenceLevelTypes eMasterInfluenceOverVassal = pMaster->GetCulture()->GetInfluenceLevel(pVassal->GetID()); - if(eMasterInfluenceOverVassal > eMasterInfluence) - eMasterInfluence = eMasterInfluenceOverVassal; + // Do not liberate if endgame aggressive + if (pDiplo->IsEndgameAggressiveTo(eVassal)) + return false; + + // Denouncement in either direction? + if (pDiplo->IsDenouncedPlayer(eVassal) || pDiplo->IsDenouncedByPlayer(eVassal)) + return false; + + // Refuse if it says they're a HERETIC! + if (eMasterReligion != NO_RELIGION && GET_PLAYER(eVassal).GetReligions()->GetStateReligion(false) != eMasterReligion) + return false; - // Only care about the highest - InfluenceLevelTypes eVassalInfluenceOverMaster = pVassal->GetCulture()->GetInfluenceLevel(pMaster->GetID()); - if(eVassalInfluenceOverMaster > eVassalInfluence) + // Check cultural influence - only care about the highest / lowest + InfluenceLevelTypes eVassalInfluenceOverMaster = GET_PLAYER(eVassal).GetCulture()->GetInfluenceLevel(eMaster); + + // Don't liberate if influential or rising towards us - could be a threat + if (eVassalInfluenceOverMaster >= INFLUENCE_LEVEL_INFLUENTIAL || GET_PLAYER(eVassal).GetCulture()->GetInfluenceTrend(eMaster) == INFLUENCE_TREND_RISING) + return false; + if (eVassalInfluenceOverMaster > eVassalInfluence) eVassalInfluence = eVassalInfluenceOverMaster; - } - iApproachScore += iAverageApproachForOneMaster; - iApproachScore /= m_Vassals.size(); + InfluenceLevelTypes eMasterInfluenceOverVassal = GET_PLAYER(eMaster).GetCulture()->GetInfluenceLevel(eVassal); - iOpinionScore += iAverageOpinionForOneMaster; - iOpinionScore /= m_Vassals.size(); + // Don't liberate if not influential yet and going for Cultural Victory - we could use the Tourism boost + if (eMasterInfluenceOverVassal < INFLUENCE_LEVEL_INFLUENTIAL && (pDiplo->IsGoingForCultureVictory() || pDiplo->IsCloseToCultureVictory())) + return false; + if (eMasterInfluenceOverVassal < eMasterInfluence) + eMasterInfluence = eMasterInfluenceOverVassal; + } + } - iStrengthScore += iAverageStrengthScoreForOneMaster; - iStrengthScore /= m_Vassals.size(); + // Do not liberate if it would cause unhappiness or financial problems! + int iTotalHappinessBoost = 0; + bool bGoodRevenueSource = false; + bool bNuclearGandhiException = false; + int iNecessaryRevenueSource = 0; + int iFinancialLiability = 0; + for (size_t i=0; i 0) + return false; + if (GET_PLAYER(eVassal).GetDiplomacyAI()->IsNuclearGandhi(true)) + bNuclearGandhiException = true; + + iTotalHappinessBoost += GetPlayer()->GetHappinessFromVassal(eVassal); } + for (size_t i=0; i 0 && GET_PLAYER(eMaster).IsEmpireUnhappy()) + return false; - eMasterApproach = (CivApproachTypes) iApproachScore; - eMasterOpinion = (CivOpinionTypes) iOpinionScore; - eVassalStrength = (StrengthTypes) iStrengthScore; - eVassalEcoStrength = (StrengthTypes) iEcoStrengthScore; + int iUnhappy = GET_PLAYER(eMaster).GetUnhappinessFromCitizenNeeds(); + int iHappy = GET_PLAYER(eMaster).GetHappinessFromCitizenNeeds() - iTotalHappinessBoost; + if (((iHappy * 100) / max(1, iUnhappy) / 2) < /*50*/ GD_INT_GET(UNHAPPY_THRESHOLD)) + return false; - CvAssertMsg(eMasterApproach >= NO_CIV_APPROACH && eMasterApproach < NUM_CIV_APPROACHES, "Something went wrong with the evaluation for approaches."); - CvAssertMsg(eMasterOpinion >= NO_CIV_OPINION && eMasterOpinion < NUM_CIV_OPINIONS, "Something went wrong with the evaluation for opinions."); - CvAssertMsg(eVassalStrength >= NO_STRENGTH_VALUE && eVassalStrength < NUM_STRENGTH_VALUES, "Something went wrong with the evaluation for strengths."); - CvAssertMsg(eMasterInfluence >= NO_INFLUENCE_LEVEL && eMasterInfluence < /* hard-coded */ 6, "Something went wrong with the evaluation for opinions."); - CvAssertMsg(eVassalInfluence >= NO_INFLUENCE_LEVEL && eVassalInfluence < /* hard-coded */ 6, "Something went wrong with the evaluation for opinions."); + bool bSkip = false; + int iRevenue = GET_PLAYER(eMaster).GetTreasury()->GetMyShareOfVassalTaxes(eVassalTeam); + int iExpense = GET_PLAYER(eMaster).GetTreasury()->GetVassalGoldMaintenance(eVassalTeam); + if (iRevenue < iExpense) + { + // We're losing more than we're gaining from this vassal, and we're going bankrupt. Not good! + if (GET_PLAYER(eMaster).getTurnsToBankruptcy(0) != INT_MAX) + { + // Hmm...can we rectify the situation by raising taxes on them? + if (GET_TEAM(GetTeam()).GetVassalTax(ePlayer) < /*25*/ GD_INT_GET(VASSALAGE_VASSAL_TAX_PERCENT_MAXIMUM)) + { + int iNewRevenue = GET_PLAYER(eMaster).GetTreasury()->GetMyShareOfVassalTaxes(eVassalTeam, /*25*/ GD_INT_GET(VASSALAGE_VASSAL_TAX_PERCENT_MAXIMUM)); + int iDifference = iNewRevenue - iRevenue; + if (iNewRevenue >= iExpense || GET_PLAYER(eMaster).getTurnsToBankruptcy(-iDifference) != INT_MAX) + { + // If raising taxes would bring us out of bankruptcy or equalize the expenses, don't liberate + return false; + } + // No? Then this vassal's bad to keep. + else + { + iFinancialLiability++; + bSkip = true; + } + } + else + { + iFinancialLiability++; + bSkip = true; + } + } + } + if (!bSkip) + { + // Good revenue source? + int iGoldRate = GET_PLAYER(eMaster).calculateGoldRateTimes100(); + if (iRevenue * 100 > iGoldRate * 20) + bGoodRevenueSource = true; - // If team doesn't like them, don't consider it. - if (eMasterApproach <= CIV_APPROACH_GUARDED) - { - return false; + // Necessary revenue source? + if (iGoldRate - iRevenue <= 0) + iNecessaryRevenueSource++; + } } - // Bad opinion? - if (eMasterOpinion <= CIV_OPINION_COMPETITOR) - { + if (iNecessaryRevenueSource > iFinancialLiability) return false; - } - int iScoreForLiberate = 0; - - // Initial score based on remaining approach - switch(eMasterApproach) - { - case CIV_APPROACH_AFRAID: - iScoreForLiberate = 100; - break; - case CIV_APPROACH_FRIENDLY: - iScoreForLiberate = 50; - break; - case CIV_APPROACH_NEUTRAL: - iScoreForLiberate = 20; - break; - default: - CvAssertMsg(false, "IsWantToLiberateVassal(): Something went terribly wrong"); - } + // Base liberation score is determined by approach - either FRIENDLY or NEUTRAL + iScoreForLiberate = eWorstMasterApproach == CIV_APPROACH_FRIENDLY ? 10 : 0; + + // Mod based on opinion - usually the most important factor + CivOpinionTypes eWorstMasterOpinion = CIV_OPINION_ALLY; + if (iWorstMasterOpinion > /*-30*/ GD_INT_GET(OPINION_THRESHOLD_FAVORABLE)) + eWorstMasterOpinion = CIV_OPINION_NEUTRAL; + else if (iWorstMasterOpinion > /*-80*/ GD_INT_GET(OPINION_THRESHOLD_FRIEND)) + eWorstMasterOpinion = CIV_OPINION_FAVORABLE; + else if (iWorstMasterOpinion > /*-160*/ GD_INT_GET(OPINION_THRESHOLD_ALLY)) + eWorstMasterOpinion = CIV_OPINION_FRIEND; - // mod based on opinion - switch(eMasterOpinion) + switch (eWorstMasterOpinion) { case CIV_OPINION_NEUTRAL: iScoreForLiberate += 0; break; case CIV_OPINION_FAVORABLE: - iScoreForLiberate += 10; + iScoreForLiberate += 5; break; case CIV_OPINION_FRIEND: - iScoreForLiberate += 15; + iScoreForLiberate += 20; break; case CIV_OPINION_ALLY: - iScoreForLiberate += 25; + iScoreForLiberate += 40; break; default: - CvAssertMsg(false, "IsWantToLiberateVassal(): Something went terribly wrong"); + UNREACHABLE(); } - int iGoldFromTaxesTimes100 = 0; - // Good source of revenue for us - not so likely to break off - for(std::vector::iterator it = m_Vassals.begin(); it != m_Vassals.end(); ++it) + // Financial liability - go away + if (iFinancialLiability > 0 && iNecessaryRevenueSource == 0) { - iGoldFromTaxesTimes100 += (*it)->GetTreasury()->GetExpensePerTurnFromVassalTaxesTimes100(); + iScoreForLiberate += 1000; } - - // more than 20 percent of our net GPT - less likely - if(iGoldFromTaxesTimes100 * 100 > iTotalGPTTimes100 * 20) + // But also a necessary revenue source? More likely, but not guaranteed. + else if (iFinancialLiability > 0 && iFinancialLiability >= iNecessaryRevenueSource) { - iScoreForLiberate *= 50; + iScoreForLiberate *= 200; iScoreForLiberate /= 100; } - - // don't liberate a strong vassal - he could be a threat - if(eVassalStrength > STRENGTH_AVERAGE || - eVassalEcoStrength > STRENGTH_AVERAGE) + // Don't liberate voluntary vassals unless they're a financial liability - they can choose to leave on their own if they don't like us + // Also don't liberate Gandhi unless we need to or he can't go nuclear on our ass + else if (bNuclearGandhiException || IsVoluntaryVassalage(ePlayer)) + { + iScoreForLiberate = 0; return false; + } + // Good source of revenue for us - not so likely to break off + else if (bGoodRevenueSource) + { + iScoreForLiberate *= 50; + iScoreForLiberate /= 100; + } - switch(eVassalStrength) + switch (eVassalStrength) { - case NO_STRENGTH_VALUE: - UNREACHABLE(); // Strengths are supposed to have been evaluated by this point. case STRENGTH_PATHETIC: iScoreForLiberate *= 125; iScoreForLiberate /= 100; @@ -55634,16 +55811,12 @@ bool CvDiplomacyAI::IsWantToLiberateVassal(PlayerTypes ePlayer) const iScoreForLiberate *= 90; iScoreForLiberate /= 100; break; - case STRENGTH_STRONG: - case STRENGTH_POWERFUL: - case STRENGTH_IMMENSE: - break; // Not applicable. + default: + UNREACHABLE(); } - switch(eVassalEcoStrength) + switch (eVassalEcoStrength) { - case NO_STRENGTH_VALUE: - UNREACHABLE(); // Strengths are supposed to have been evaluated by this point. case STRENGTH_PATHETIC: iScoreForLiberate *= 125; iScoreForLiberate /= 100; @@ -55660,14 +55833,12 @@ bool CvDiplomacyAI::IsWantToLiberateVassal(PlayerTypes ePlayer) const iScoreForLiberate *= 90; iScoreForLiberate /= 100; break; - case STRENGTH_STRONG: - case STRENGTH_POWERFUL: - case STRENGTH_IMMENSE: - break; // Not applicable. + default: + UNREACHABLE(); } // Mod based on proximity - switch(m_pPlayer->GetProximityToPlayer(ePlayer)) + switch (m_pPlayer->GetProximityToPlayer(ePlayer)) { case NO_PLAYER_PROXIMITY: case PLAYER_PROXIMITY_DISTANT: @@ -55689,23 +55860,17 @@ bool CvDiplomacyAI::IsWantToLiberateVassal(PlayerTypes ePlayer) const } // City comparison modifier - int iNumPop = kMyTeam.getTotalPopulation(); - int iNumVassalPop = kVassalTeam.getTotalPopulation(); - - if(iNumVassalPop >= iNumPop) - return false; - - if(iNumVassalPop * 100 > iNumPop * 75) + if (iNumVassalPop * 100 > iNumPop * 75) { iScoreForLiberate *= 75; iScoreForLiberate /= 100; } - else if(iNumVassalPop * 100 > iNumPop * 50) + else if (iNumVassalPop * 100 > iNumPop * 50) { iScoreForLiberate *= 100; iScoreForLiberate /= 100; } - else if(iNumVassalPop * 100 > iNumPop * 33) + else if (iNumVassalPop * 100 > iNumPop * 33) { iScoreForLiberate *= 125; iScoreForLiberate /= 100; @@ -55719,35 +55884,29 @@ bool CvDiplomacyAI::IsWantToLiberateVassal(PlayerTypes ePlayer) const int iDominanceOverVassal = eMasterInfluence - eVassalInfluence; // someone is pretty dominant over vassal (not too much of a modifier, but helps our chances of liberation) - if(iDominanceOverVassal > 0) + if (iDominanceOverVassal > 0) { iScoreForLiberate *= 120; iScoreForLiberate /= 100; } - else if(iDominanceOverVassal < 0) + else if (iDominanceOverVassal < 0) { iScoreForLiberate *= 50; iScoreForLiberate /= 100; } - // Someone influential over one of our vassals - if(eMasterInfluence >= INFLUENCE_LEVEL_INFLUENTIAL) + // We're influential over them + if (eMasterInfluence >= INFLUENCE_LEVEL_INFLUENTIAL) { iScoreForLiberate *= 150; iScoreForLiberate /= 100; } - // A vassal is influential over us!!! Them being a vassal will be good for us - if(eVassalInfluence >= INFLUENCE_LEVEL_INFLUENTIAL) - { - iScoreForLiberate *= 50; - iScoreForLiberate /= 100; - } - // The longer they've been our vassal, give a very small boost toward liberation - iScoreForLiberate += 3 * (kVassalTeam.GetNumTurnsIsVassal() / 50); // 3% per 50 turns + iScoreForLiberate *= 100 + (GET_TEAM(eVassalTeam).GetNumTurnsIsVassal() * GC.getGame().getGameSpeedInfo().getTrainPercent() / 1000); // 1% per 10 turns (standard speed) + iScoreForLiberate /= 100; - return iScoreForLiberate > 100; + return iScoreForLiberate > /*100*/ GD_INT_GET(VASSALAGE_LIBERATE_BASE_THRESHOLD); } /// Possible Contact Statement - Third-party offer for ePlayer to liberate their vassals diff --git a/CvGameCoreDLL_Expansion2/CvDiplomacyAI.h b/CvGameCoreDLL_Expansion2/CvDiplomacyAI.h index 28c8061914..823483ceb2 100644 --- a/CvGameCoreDLL_Expansion2/CvDiplomacyAI.h +++ b/CvGameCoreDLL_Expansion2/CvDiplomacyAI.h @@ -966,6 +966,9 @@ class CvDiplomacyAI bool IsIgnoreWarmonger() const; void SetIgnoreWarmonger(bool bValue); + PlayerTypes GetVassalPlayerToLiberate() const; + void SetVassalPlayerToLiberate(PlayerTypes ePlayer); + PlayerTypes GetOtherPlayerProtectedMinorBullied(PlayerTypes ePlayer) const; void SetOtherPlayerProtectedMinorBullied(PlayerTypes ePlayer, PlayerTypes eBulliedPlayer); @@ -1566,7 +1569,8 @@ class CvDiplomacyAI VassalTreatmentTypes GetVassalTreatmentLevel(PlayerTypes ePlayer); CvString GetVassalTreatmentToolTip(PlayerTypes ePlayer); - bool IsWantToLiberateVassal(PlayerTypes ePlayer) const; + void DetermineVassalToLiberate(); + bool IsWantToLiberateVassal(PlayerTypes ePlayer, int& iScoreForLiberate) const; bool IsVassalageAcceptable(PlayerTypes ePlayer, bool bMasterEvaluation); // can be called in either direction, for the master or the vassal bool IsCapitulationAcceptable(PlayerTypes ePlayer); // vassal only @@ -1908,6 +1912,7 @@ class CvDiplomacyAI // Other Global Memory bool m_bAvoidDeals; // Not serialized! bool m_bIgnoreWarmonger; // Not serialized! + PlayerTypes m_eVassalPlayerToLiberate; // Not serialized! bool m_bWasHumanLastTurn; bool m_bEndedFriendshipThisTurn; bool m_bUpdatedWarProgressThisTurn; diff --git a/CvGameCoreDLL_Expansion2/CvEspionageClasses.cpp b/CvGameCoreDLL_Expansion2/CvEspionageClasses.cpp index 00580668b1..8e89eca8ef 100644 --- a/CvGameCoreDLL_Expansion2/CvEspionageClasses.cpp +++ b/CvGameCoreDLL_Expansion2/CvEspionageClasses.cpp @@ -9296,6 +9296,23 @@ std::vector CvEspionageAI::BuildMinorCityList(bool bLogAllChoice if (pMinorCapital->IsRazing()) continue; + //Is there a proposal (not resolution) involving a Sphere of Influence or Open Door? + CvLeague* pLeague = GC.getGame().GetGameLeagues()->GetActiveLeague(); + bool bBlockingProposal = false; + if (pLeague != NULL) + { + for (EnactProposalList::iterator it = pLeague->m_vEnactProposals.begin(); it != pLeague->m_vEnactProposals.end(); ++it) + { + if ((it->GetEffects()->bSphereOfInfluence || it->GetEffects()->bOpenDoor) && it->GetProposerDecision()->GetDecision() == eTargetPlayer) + { + bBlockingProposal = true; + break; + } + } + } + if (bBlockingProposal) + continue; + CivApproachTypes eApproach = pDiploAI->GetCivApproach(eTargetPlayer); // how much influence for rigging an election (not taking into account streaks) @@ -9776,6 +9793,28 @@ void CvEspionageAI::EvaluateMinorCivSpies(void) pEspionage->LogEspionageMsg(strMsg); } } + + //Is there a proposal (not resolution) involving a Sphere of Influence or Open Door? + CvLeague* pLeague = GC.getGame().GetGameLeagues()->GetActiveLeague(); + if (pLeague != NULL) + { + for (EnactProposalList::iterator it = pLeague->m_vEnactProposals.begin(); it != pLeague->m_vEnactProposals.end(); ++it) + { + if ((it->GetEffects()->bSphereOfInfluence || it->GetEffects()->bOpenDoor) && it->GetProposerDecision()->GetDecision() == pCity->getOwner()) + { + CvEspionageSpy* pSpy = &(pEspionage->m_aSpyList[ui]); + pSpy->m_bEvaluateReassignment = true; + if (GC.getLogging()) + { + CvString strMsg; + strMsg.Format("Re-eval: SoI or Open Door resolution proposed, %d,", ui); + strMsg += GetLocalizedText(pCity->getNameKey()); + pEspionage->LogEspionageMsg(strMsg); + } + break; + } + } + } } } } diff --git a/CvGameCoreDLL_Expansion2/CvGame.cpp b/CvGameCoreDLL_Expansion2/CvGame.cpp index 0851d62542..18b72deaf9 100644 --- a/CvGameCoreDLL_Expansion2/CvGame.cpp +++ b/CvGameCoreDLL_Expansion2/CvGame.cpp @@ -6398,7 +6398,7 @@ bool CvGame::IsNuclearGandhiEnabled() const if (isNoNukes()) return false; - if (isOption(GAMEOPTION_RANDOM_PERSONALITIES) && GD_INT_GET(DIPLOAI_ENABLE_NUCLEAR_GANDHI) < 1) + if (isOption(GAMEOPTION_RANDOM_PERSONALITIES) && GD_INT_GET(DIPLOAI_ENABLE_NUCLEAR_GANDHI) < 2) return false; return GD_INT_GET(DIPLOAI_ENABLE_NUCLEAR_GANDHI) > 0; diff --git a/CvGameCoreDLL_Expansion2/CvGlobals.cpp b/CvGameCoreDLL_Expansion2/CvGlobals.cpp index 8dc78bcd79..9b9b6b4664 100644 --- a/CvGameCoreDLL_Expansion2/CvGlobals.cpp +++ b/CvGameCoreDLL_Expansion2/CvGlobals.cpp @@ -2240,6 +2240,7 @@ CvGlobals::CvGlobals() : GD_INT_INIT(VASSALAGE_VASSAL_MASTER_CITY_PERCENT_THRESHOLD, 60), GD_INT_INIT(VASSALAGE_VASSAL_MASTER_POP_PERCENT_THRESHOLD, 60), GD_INT_INIT(VASSALAGE_CAPITULATE_BASE_THRESHOLD, 100), + GD_INT_INIT(VASSALAGE_LIBERATE_BASE_THRESHOLD, 100), GD_INT_INIT(VASSALAGE_TREATMENT_THRESHOLD_DISAGREE, 1), GD_INT_INIT(VASSALAGE_TREATMENT_THRESHOLD_MISTREATED, 25), GD_INT_INIT(VASSALAGE_TREATMENT_THRESHOLD_UNHAPPY, 50), @@ -7073,6 +7074,7 @@ void CvGlobals::cacheGlobals() GD_INT_CACHE(VASSALAGE_VASSAL_MASTER_CITY_PERCENT_THRESHOLD); GD_INT_CACHE(VASSALAGE_VASSAL_MASTER_POP_PERCENT_THRESHOLD); GD_INT_CACHE(VASSALAGE_CAPITULATE_BASE_THRESHOLD); + GD_INT_CACHE(VASSALAGE_LIBERATE_BASE_THRESHOLD); GD_INT_CACHE(VASSALAGE_TREATMENT_THRESHOLD_DISAGREE); GD_INT_CACHE(VASSALAGE_TREATMENT_THRESHOLD_MISTREATED); GD_INT_CACHE(VASSALAGE_TREATMENT_THRESHOLD_UNHAPPY); diff --git a/CvGameCoreDLL_Expansion2/CvGlobals.h b/CvGameCoreDLL_Expansion2/CvGlobals.h index 59c170a0ee..29fc980ef9 100644 --- a/CvGameCoreDLL_Expansion2/CvGlobals.h +++ b/CvGameCoreDLL_Expansion2/CvGlobals.h @@ -2831,6 +2831,7 @@ class CvGlobals GD_INT_MEMBER(VASSALAGE_VASSAL_MASTER_CITY_PERCENT_THRESHOLD); // VP GD_INT_MEMBER(VASSALAGE_VASSAL_MASTER_POP_PERCENT_THRESHOLD); // VP GD_INT_MEMBER(VASSALAGE_CAPITULATE_BASE_THRESHOLD); // VP + GD_INT_MEMBER(VASSALAGE_LIBERATE_BASE_THRESHOLD); // VP GD_INT_MEMBER(VASSALAGE_TREATMENT_THRESHOLD_DISAGREE); // VP GD_INT_MEMBER(VASSALAGE_TREATMENT_THRESHOLD_MISTREATED); // VP GD_INT_MEMBER(VASSALAGE_TREATMENT_THRESHOLD_UNHAPPY); // VP diff --git a/CvGameCoreDLL_Expansion2/CvMinorCivAI.cpp b/CvGameCoreDLL_Expansion2/CvMinorCivAI.cpp index 9af2a6f6c1..29bb80f28a 100644 --- a/CvGameCoreDLL_Expansion2/CvMinorCivAI.cpp +++ b/CvGameCoreDLL_Expansion2/CvMinorCivAI.cpp @@ -3618,8 +3618,9 @@ bool CvMinorCivQuest::DoCancelQuest() bool bRevoked = IsRevoked(); bool bExpired = IsExpired(); - Localization::String strMessage; - Localization::String strSummary; + // General "Quest Expired" catch statement, overridden below + Localization::String strMessage = Localization::Lookup("TXT_KEY_NOTIFICATION_QUEST_ENDED_OTHER"); + Localization::String strSummary = Localization::Lookup("TXT_KEY_NOTIFICATION_SUMMARY_QUEST_ENDED_OTHER"); CivsList veNamesToShow; // If quest was revoked due to bullying, notification is handled elsewhere (to allow condensing) @@ -3762,12 +3763,17 @@ bool CvMinorCivQuest::DoCancelQuest() case MINOR_CIV_QUEST_LIBERATION: { CvPlot* pPlot = GC.getMap().plot(m_iData1, m_iData2); - const char* strTargetNameKey = pPlot->getPlotCity()->getNameKey(); + PlayerTypes eTargetPlayer = (PlayerTypes)m_iData3; + CvCity* pCity = pPlot->getPlotCity(); + if (pCity && pCity->getOriginalOwner() == eTargetPlayer) + { + const char* strTargetNameKey = pCity->getNameKey(); - strMessage = Localization::Lookup("TXT_KEY_NOTIFICATION_QUEST_ENDED_LIBERATION"); - strMessage << strTargetNameKey; - strSummary = Localization::Lookup("TXT_KEY_NOTIFICATION_SUMMARY_QUEST_ENDED_LIBERATION"); - strSummary << strTargetNameKey; + strMessage = Localization::Lookup("TXT_KEY_NOTIFICATION_QUEST_ENDED_LIBERATION"); + strMessage << strTargetNameKey; + strSummary = Localization::Lookup("TXT_KEY_NOTIFICATION_SUMMARY_QUEST_ENDED_LIBERATION"); + strSummary << strTargetNameKey; + } break; } case MINOR_CIV_QUEST_HORDE: @@ -3853,13 +3859,8 @@ bool CvMinorCivQuest::DoCancelQuest() break; } default: - { - // General "Quest Expired" catch statement - strMessage = Localization::Lookup("TXT_KEY_NOTIFICATION_QUEST_ENDED_OTHER"); - strSummary = Localization::Lookup("TXT_KEY_NOTIFICATION_SUMMARY_QUEST_ENDED_OTHER"); break; } - } strMessage << pMinor->getNameKey(); strSummary << pMinor->getNameKey(); diff --git a/CvGameCoreDLL_Expansion2/CvPlayer.cpp b/CvGameCoreDLL_Expansion2/CvPlayer.cpp index fd58dc0b15..064d2ba006 100644 --- a/CvGameCoreDLL_Expansion2/CvPlayer.cpp +++ b/CvGameCoreDLL_Expansion2/CvPlayer.cpp @@ -10297,73 +10297,105 @@ void CvPlayer::doTurnPostDiplomacy() // Compute the cost of policies for this turn DoUpdateNextPolicyCost(); - // if this is the human player, have the popup come up so that he can choose a new policy - if(isAlive() && isHuman() && getNumCities() > 0) + // Anarchy counter + if (GetAnarchyNumTurns() > 0) + ChangeAnarchyNumTurns(-1); + + if (isAlive() && isMajorCiv() && getNumCities() > 0) { - if(!GC.GetEngineUserInterface()->IsPolicyNotificationSeen()) + if (!isHuman()) { - if(getNextPolicyCost() <= getJONSCulture() && GetPlayerPolicies()->GetNumPoliciesCanBeAdopted() > 0) + if (GetPlayerPolicies()->IsTimeToChooseIdeology() && GetPlayerPolicies()->GetLateGamePolicyTree() == NO_POLICY_BRANCH_TYPE) { - CvNotifications* pNotifications = GetNotifications(); - if(pNotifications) + if (GetPlayerTraits()->IsAdoptionFreeTech()) { - CvString strBuffer; - - if(kGame.isOption(GAMEOPTION_POLICY_SAVING)) - strBuffer = GetLocalizedText("TXT_KEY_NOTIFICATION_ENOUGH_CULTURE_FOR_POLICY_DISMISS"); - else - strBuffer = GetLocalizedText("TXT_KEY_NOTIFICATION_ENOUGH_CULTURE_FOR_POLICY"); - - CvString strSummary = GetLocalizedText("TXT_KEY_NOTIFICATION_SUMMARY_ENOUGH_CULTURE_FOR_POLICY"); - pNotifications->Add(NOTIFICATION_POLICY, strBuffer, strSummary, -1, -1, -1); + AI_chooseFreeTech(); } + + GetPlayerPolicies()->DoChooseIdeology(); } - } - if (GetPlayerPolicies()->IsTimeToChooseIdeology() && GetPlayerPolicies()->GetLateGamePolicyTree() == NO_POLICY_BRANCH_TYPE) + GetPlayerPolicies()->DoPolicyAI(); + } + // if this is the human player, have the popup come up so that he can choose a new policy + else { - if(GetPlayerTraits()->IsAdoptionFreeTech()) + if (!GC.GetEngineUserInterface()->IsPolicyNotificationSeen()) { - CvString strBuffer = GetLocalizedText("TXT_KEY_MISC_CHOSE_IDEOLOGY_UA_CHOOSE_TECH"); - chooseTech(1, strBuffer.GetCString()); + if (getNextPolicyCost() <= getJONSCulture() && GetPlayerPolicies()->GetNumPoliciesCanBeAdopted() > 0) + { + CvNotifications* pNotifications = GetNotifications(); + if (pNotifications) + { + CvString strBuffer; + + if (kGame.isOption(GAMEOPTION_POLICY_SAVING)) + strBuffer = GetLocalizedText("TXT_KEY_NOTIFICATION_ENOUGH_CULTURE_FOR_POLICY_DISMISS"); + else + strBuffer = GetLocalizedText("TXT_KEY_NOTIFICATION_ENOUGH_CULTURE_FOR_POLICY"); + + CvString strSummary = GetLocalizedText("TXT_KEY_NOTIFICATION_SUMMARY_ENOUGH_CULTURE_FOR_POLICY"); + pNotifications->Add(NOTIFICATION_POLICY, strBuffer, strSummary, -1, -1, -1); + } + } } - CvNotifications* pNotifications = GetNotifications(); - if(pNotifications) + if (GetPlayerPolicies()->IsTimeToChooseIdeology() && GetPlayerPolicies()->GetLateGamePolicyTree() == NO_POLICY_BRANCH_TYPE) { - CvString strBuffer; - if (GetCurrentEra() > /*INDUSTRIAL IN CP, MODERN IN VP*/ GD_INT_GET(IDEOLOGY_START_ERA)) + // Vassals are forced to choose the master's ideology + bool bForcedIdeology = false; + TeamTypes eMasterTeam = GET_TEAM(getTeam()).GetMaster(); + if (eMasterTeam != NO_TEAM) { - strBuffer = GetLocalizedText("TXT_KEY_NOTIFICATION_CHOOSE_IDEOLOGY_ERA"); + vector vMasterTeam = GET_TEAM(eMasterTeam).getPlayers(); + for (size_t i=0; i 0 cities is the one that counts + if (GET_PLAYER(eMaster).isAlive() && GET_PLAYER(eMaster).getNumCities() > 0) + { + if (GET_PLAYER(eMaster).GetPlayerPolicies()->GetLateGamePolicyTree() != NO_POLICY_BRANCH_TYPE) + { + GetPlayerPolicies()->SetPolicyBranchUnlocked(GET_PLAYER(eMaster).GetPlayerPolicies()->GetLateGamePolicyTree(), true, false); + bForcedIdeology = true; + break; + } + } + } } - else + + if (GetPlayerTraits()->IsAdoptionFreeTech()) { - strBuffer = GetLocalizedText("TXT_KEY_NOTIFICATION_CHOOSE_IDEOLOGY_FACTORIES"); + CvString strBuffer = GetLocalizedText("TXT_KEY_MISC_CHOSE_IDEOLOGY_UA_CHOOSE_TECH"); + chooseTech(1, strBuffer.GetCString()); } - CvString strSummary = GetLocalizedText("TXT_KEY_NOTIFICATION_SUMMARY_CHOOSE_IDEOLOGY"); - pNotifications->Add(NOTIFICATION_CHOOSE_IDEOLOGY, strBuffer, strSummary, -1, -1, GetID()); - } - } - } - if (isAlive() && getNumCities() > 0 && !isHuman() && !isMinorCiv()) - { - if (GetPlayerPolicies()->IsTimeToChooseIdeology() && GetPlayerPolicies()->GetLateGamePolicyTree() == NO_POLICY_BRANCH_TYPE) - { - if(GetPlayerTraits()->IsAdoptionFreeTech()) - { - AI_chooseFreeTech(); + if (!bForcedIdeology) + { + CvNotifications* pNotifications = GetNotifications(); + if (pNotifications) + { + CvString strBuffer; + if (GetCurrentEra() > /*INDUSTRIAL IN CP, MODERN IN VP*/ GD_INT_GET(IDEOLOGY_START_ERA)) + { + strBuffer = GetLocalizedText("TXT_KEY_NOTIFICATION_CHOOSE_IDEOLOGY_ERA"); + } + else + { + strBuffer = GetLocalizedText("TXT_KEY_NOTIFICATION_CHOOSE_IDEOLOGY_FACTORIES"); + } + CvString strSummary = GetLocalizedText("TXT_KEY_NOTIFICATION_SUMMARY_CHOOSE_IDEOLOGY"); + pNotifications->Add(NOTIFICATION_CHOOSE_IDEOLOGY, strBuffer, strSummary, -1, -1, GetID()); + } + } } - GetPlayerPolicies()->DoChooseIdeology(); + // Force an ideology update for human vassals, if applicable + GetPlayerPolicies()->DoPolicyAI(); } } - if (!isBarbarian() && !isHuman() && !isMinorCiv()) - { - GetPlayerPolicies()->DoPolicyAI(); - } - // Science doResearch(); @@ -10378,10 +10410,6 @@ void CvPlayer::doTurnPostDiplomacy() // Leagues CvGameLeagues* pGameLeagues = kGame.GetGameLeagues(); pGameLeagues->DoPlayerTurn(*this); - - // Anarchy counter - if(GetAnarchyNumTurns() > 0) - ChangeAnarchyNumTurns(-1); } const int iGameTurn = kGame.getGameTurn(); @@ -31339,7 +31367,8 @@ int CvPlayer::GetNumOurCitiesOwnedBy(PlayerTypes ePlayer) int iCount = 0; for (CvCity* pLoopCity = GET_PLAYER(ePlayer).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER(ePlayer).nextCity(&iLoop)) { - if (pLoopCity->getOriginalOwner() == m_eID) + PlayerTypes eOriginalOwner = pLoopCity->getOriginalOwner(); + if (GET_PLAYER(eOriginalOwner).getTeam() == getTeam()) iCount++; } diff --git a/CvGameCoreDLL_Expansion2/CvPolicyAI.cpp b/CvGameCoreDLL_Expansion2/CvPolicyAI.cpp index 7135fc0aac..f348a5cd40 100644 --- a/CvGameCoreDLL_Expansion2/CvPolicyAI.cpp +++ b/CvGameCoreDLL_Expansion2/CvPolicyAI.cpp @@ -290,30 +290,78 @@ void CvPolicyAI::DoChooseIdeology(CvPlayer *pPlayer) return; } - if (GET_TEAM(pPlayer->getTeam()).IsVassalOfSomeone()) + // Vassals are forced to choose the master's ideology + TeamTypes eMasterTeam = GET_TEAM(pPlayer->getTeam()).GetMaster(); + if (eMasterTeam != NO_TEAM) { - TeamTypes eMasterTeam = GET_TEAM(pPlayer->getTeam()).GetMaster(); - if (eMasterTeam != NO_TEAM) + vector vMasterTeam = GET_TEAM(eMasterTeam).getPlayers(); + for (size_t i=0; i 0 cities is the one that counts + if (GET_PLAYER(eMaster).isAlive() && GET_PLAYER(eMaster).getNumCities() > 0) + { + if (GET_PLAYER(eMaster).GetPlayerPolicies()->GetLateGamePolicyTree() != NO_POLICY_BRANCH_TYPE) { - if (GET_PLAYER(eMaster).GetPlayerPolicies()->GetLateGamePolicyTree() != NO_POLICY_BRANCH_TYPE) - { - pPlayer->GetPlayerPolicies()->SetPolicyBranchUnlocked(GET_PLAYER(eMaster).GetPlayerPolicies()->GetLateGamePolicyTree(), true, false); - LogBranchChoice(GET_PLAYER(eMaster).GetPlayerPolicies()->GetLateGamePolicyTree()); - return; - } + pPlayer->GetPlayerPolicies()->SetPolicyBranchUnlocked(GET_PLAYER(eMaster).GetPlayerPolicies()->GetLateGamePolicyTree(), true, false); + LogBranchChoice(GET_PLAYER(eMaster).GetPlayerPolicies()->GetLateGamePolicyTree()); + return; } } } } + // Team Leader used FOLLOW ME! + TeamTypes eTeam = pPlayer->getTeam(); + PlayerTypes eTeamLeader = pPlayer->GetID(); + int iBestScore = 0; + int iMyScore = pPlayer->GetScore(); + vector vMyTeam = GET_TEAM(eTeam).getPlayers(); + for (size_t i=0; i iBestScore && iScore * 2 >= iMyScore * 3) // Must be at least 50% higher to justify this + { + eTeamLeader = eTeamMember; + iBestScore = iScore; + } + } + PolicyBranchTypes eLeaderIdeology = GET_PLAYER(eTeamLeader).GetPlayerPolicies()->GetLateGamePolicyTree(); + if (eTeamLeader != pPlayer->GetID() && eLeaderIdeology != NO_POLICY_BRANCH_TYPE) + { + // Sanity check - don't adopt the leader's ideology if it would result in us losing cities + bool bFollowTheLeader = false; + int iExtraUnhappiness = pPlayer->GetCulture()->ComputeHypotheticalPublicOpinionUnhappiness(eLeaderIdeology); + if (!MOD_BALANCE_VP) + { + bFollowTheLeader = pPlayer->GetExcessHappiness() - iExtraUnhappiness > /*-8*/ GD_INT_GET(VERY_UNHAPPY_THRESHOLD) + 2; // Add some margin of error + } + else + { + int iUnhappy = pPlayer->GetUnhappinessFromCitizenNeeds() + iExtraUnhappiness; + int iHappy = pPlayer->GetHappinessFromCitizenNeeds(); + bFollowTheLeader = ((iHappy * 100) / max(1, iUnhappy) / 2) >= /*40*/ GD_INT_GET(VERY_UNHAPPY_THRESHOLD) + 5; // Add some margin of error + } + + if (bFollowTheLeader) + { + pPlayer->GetPlayerPolicies()->SetPolicyBranchUnlocked(eLeaderIdeology, true, false); + LogBranchChoice(eLeaderIdeology); + return; + } + } + // == Grand Strategy == int iDiploInterest = 0; int iConquestInterest = 0; @@ -690,6 +738,7 @@ void CvPolicyAI::DoChooseIdeology(CvPlayer *pPlayer) } /// Should the AI look at switching ideology branches? +/// Humans also call this function to force a switch if they're a vassal. void CvPolicyAI::DoConsiderIdeologySwitch(CvPlayer* pPlayer) { // Gather basic Ideology info @@ -697,28 +746,32 @@ void CvPolicyAI::DoConsiderIdeologySwitch(CvPlayer* pPlayer) if (eCurrentIdeology == NO_POLICY_BRANCH_TYPE) return; - bool bVUnhappy = pPlayer->IsEmpireVeryUnhappy(); - bool bSUnhappy = pPlayer->IsEmpireSuperUnhappy(); - int iPublicOpinionUnhappiness = pPlayer->GetCulture()->GetPublicOpinionUnhappiness(); - PolicyBranchTypes ePreferredIdeology = pPlayer->GetCulture()->GetPublicOpinionPreferredIdeology(); - - if (GET_TEAM(pPlayer->getTeam()).IsVassalOfSomeone() && pPlayer->GetPlayerPolicies()->GetLateGamePolicyTree() != NO_POLICY_BRANCH_TYPE) + TeamTypes eMasterTeam = GET_TEAM(pPlayer->getTeam()).GetMaster(); + if (eMasterTeam != NO_TEAM) { - TeamTypes eMasterTeam = GET_TEAM(pPlayer->getTeam()).GetMaster(); - - // Loop through all players to see if they're on our team - for (int iPlayerLoop = 0; iPlayerLoop < MAX_MAJOR_CIVS; iPlayerLoop++) + vector vMasterTeam = GET_TEAM(eMasterTeam).getPlayers(); + for (size_t i=0; i 0 cities is the one that counts + if (GET_PLAYER(eMaster).isAlive() && GET_PLAYER(eMaster).getNumCities() > 0) { - if (GET_PLAYER(eMaster).GetPlayerPolicies()->GetLateGamePolicyTree() != NO_POLICY_BRANCH_TYPE && GET_PLAYER(eMaster).GetPlayerPolicies()->GetLateGamePolicyTree() != pPlayer->GetPlayerPolicies()->GetLateGamePolicyTree()) + PolicyBranchTypes eMasterIdeology = GET_PLAYER(eMaster).GetPlayerPolicies()->GetLateGamePolicyTree(); + if (eMasterIdeology != NO_POLICY_BRANCH_TYPE && eMasterIdeology != eCurrentIdeology) { + if (MOD_API_ACHIEVEMENTS && eMasterIdeology == GD_INT_GET(POLICY_BRANCH_FREEDOM) && eCurrentIdeology == GD_INT_GET(POLICY_BRANCH_ORDER)) + { + PlayerTypes eActivePlayer = GC.getGame().getActivePlayer(); + if (GET_PLAYER(eActivePlayer).isAlive() && GET_PLAYER(eActivePlayer).isHuman() && GET_PLAYER(eActivePlayer).getTeam() == eMasterTeam) + { + gDLL->UnlockAchievement(ACHIEVEMENT_XP2_39); + } + } + // Cleared all obstacles -- REVOLUTION! pPlayer->SetAnarchyNumTurns(/*2 in CP, 3 in VP*/ GD_INT_GET(SWITCH_POLICY_BRANCHES_ANARCHY_TURNS)); - pPlayer->GetPlayerPolicies()->DoSwitchIdeologies(GET_PLAYER(eMaster).GetPlayerPolicies()->GetLateGamePolicyTree()); + pPlayer->GetPlayerPolicies()->DoSwitchIdeologies(eMasterIdeology); Localization::String strSummary = Localization::Lookup("TXT_KEY_ANARCHY_BEGINS_SUMMARY"); Localization::String strMessage = Localization::Lookup("TXT_KEY_ANARCHY_BEGINS"); pPlayer->GetNotifications()->Add(NOTIFICATION_GENERIC, strMessage.toUTF8(), strSummary.toUTF8(), pPlayer->GetID(), /*2 in CP, 3 in VP*/ GD_INT_GET(SWITCH_POLICY_BRANCHES_ANARCHY_TURNS), -1); @@ -728,22 +781,148 @@ void CvPolicyAI::DoConsiderIdeologySwitch(CvPlayer* pPlayer) } } + // Humans halt here! + if (pPlayer->isHuman()) + return; + + int iPublicOpinionUnhappiness = pPlayer->GetCulture()->GetPublicOpinionUnhappiness(); + PolicyBranchTypes ePreferredIdeology = pPlayer->GetCulture()->GetPublicOpinionPreferredIdeology(); + + // Can't switch. + if (iPublicOpinionUnhappiness == 0 || ePreferredIdeology == NO_POLICY_BRANCH_TYPE || ePreferredIdeology == eCurrentIdeology) + return; + + // Would switching cure our happiness problems? + bool bVUnhappy = pPlayer->IsEmpireVeryUnhappy(); + bool bSUnhappy = pPlayer->IsEmpireSuperUnhappy(); + if (bSUnhappy) + { + int iNewUnhappiness = pPlayer->GetCulture()->ComputeHypotheticalPublicOpinionUnhappiness(ePreferredIdeology); + if (!MOD_BALANCE_VP) + { + bSUnhappy = pPlayer->GetExcessHappiness() + iPublicOpinionUnhappiness - iNewUnhappiness > /*-20*/ GD_INT_GET(SUPER_UNHAPPY_THRESHOLD); + } + else + { + int iUnhappy = pPlayer->GetUnhappinessFromCitizenNeeds() - iPublicOpinionUnhappiness + iNewUnhappiness; + int iHappy = pPlayer->GetHappinessFromCitizenNeeds(); + bSUnhappy = ((iHappy * 100) / max(1, iUnhappy) / 2) >= /*20*/ GD_INT_GET(SUPER_UNHAPPY_THRESHOLD); + } + } + else if (bVUnhappy) + { + int iNewUnhappiness = pPlayer->GetCulture()->ComputeHypotheticalPublicOpinionUnhappiness(ePreferredIdeology); + if (!MOD_BALANCE_VP) + { + bVUnhappy = pPlayer->GetExcessHappiness() + iPublicOpinionUnhappiness - iNewUnhappiness > /*-10*/ GD_INT_GET(VERY_UNHAPPY_THRESHOLD); + } + else + { + int iUnhappy = pPlayer->GetUnhappinessFromCitizenNeeds() - iPublicOpinionUnhappiness + iNewUnhappiness; + int iHappy = pPlayer->GetHappinessFromCitizenNeeds(); + bVUnhappy = ((iHappy * 100) / max(1, iUnhappy) / 2) >= /*35*/ GD_INT_GET(VERY_UNHAPPY_THRESHOLD); + } + } + + // Team Leader used FOLLOW ME! + TeamTypes eTeam = pPlayer->getTeam(); + PlayerTypes eTeamLeader = pPlayer->GetID(); + bool bTeamLeaderSwitchRequested = false; + bool bTeamLeaderSwitchAvoided = false; + if (!bSUnhappy) + { + int iBestScore = 0; + int iMyScore = pPlayer->GetScore(); + vector vMyTeam = GET_TEAM(eTeam).getPlayers(); + for (size_t i=0; i iBestScore && iScore * 2 >= iMyScore * 3) // Must be at least 50% higher to justify this + { + eTeamLeader = eTeamMember; + iBestScore = iScore; + } + } + if (eTeamLeader != pPlayer->GetID()) + { + PolicyBranchTypes eTeamLeaderIdeology = GET_PLAYER(eTeamLeader).GetPlayerPolicies()->GetLateGamePolicyTree(); + if (eTeamLeaderIdeology == ePreferredIdeology) + { + // Switch if we wouldn't lose cities to unhappiness + bTeamLeaderSwitchRequested = true; + } + else if (eTeamLeaderIdeology == eCurrentIdeology) + { + // Don't switch if we can avoid losing cities to unhappiness + bTeamLeaderSwitchAvoided = true; + } + } + } + // Possible enough that we need to look at this in detail? - if (bSUnhappy && iPublicOpinionUnhappiness >= /*-20 in CP, 20 in VP*/ GD_INT_GET(SUPER_UNHAPPY_THRESHOLD)) + if (bSUnhappy) { - //Sanity check - would a change to this branch simply make us unhappy in another way? If so, don't do it. - if(ePreferredIdeology != NO_POLICY_BRANCH_TYPE) + //Final sanity check - are we flip-flopping? + if (!bTeamLeaderSwitchRequested && GC.getGame().getGameTurn() - pPlayer->GetCulture()->GetTurnIdeologySwitch() <= 30) + { + return; + } + + if (MOD_API_ACHIEVEMENTS && ePreferredIdeology == GD_INT_GET(POLICY_BRANCH_FREEDOM) && eCurrentIdeology == GD_INT_GET(POLICY_BRANCH_ORDER)) { - int iUnhappiness = pPlayer->GetCulture()->ComputeHypotheticalPublicOpinionUnhappiness(ePreferredIdeology); - if(iUnhappiness >= iPublicOpinionUnhappiness) + PlayerTypes eMostPressure = pPlayer->GetCulture()->GetPublicOpinionBiggestInfluence(); + if (eMostPressure != NO_PLAYER && GET_PLAYER(eMostPressure).GetID() == GC.getGame().getActivePlayer()) { - return; + gDLL->UnlockAchievement(ACHIEVEMENT_XP2_39); } + } - //Final sanity check - are we flip-flopping? - if (GC.getGame().getGameTurn() - pPlayer->GetCulture()->GetTurnIdeologySwitch() <= 30) + // Cleared all obstacles -- REVOLUTION! + pPlayer->SetAnarchyNumTurns(/*2 in CP, 3 in VP*/ GD_INT_GET(SWITCH_POLICY_BRANCHES_ANARCHY_TURNS)); + pPlayer->GetPlayerPolicies()->DoSwitchIdeologies(ePreferredIdeology); + Localization::String strSummary = Localization::Lookup("TXT_KEY_ANARCHY_BEGINS_SUMMARY"); + Localization::String strMessage = Localization::Lookup("TXT_KEY_ANARCHY_BEGINS"); + pPlayer->GetNotifications()->Add(NOTIFICATION_GENERIC, strMessage.toUTF8(), strSummary.toUTF8(), pPlayer->GetID(), /*2 in CP, 3 in VP*/ GD_INT_GET(SWITCH_POLICY_BRANCHES_ANARCHY_TURNS), -1); + } + else if (bTeamLeaderSwitchAvoided) + { + return; + } + else if (bTeamLeaderSwitchRequested) + { + //Sanity check - would a change to this branch put us at risk of losing cities? If so, don't do it. + int iNewUnhappiness = pPlayer->GetCulture()->ComputeHypotheticalPublicOpinionUnhappiness(ePreferredIdeology); + bool bSwitch = false; + if (!MOD_BALANCE_VP) + { + bSwitch = pPlayer->GetExcessHappiness() + iPublicOpinionUnhappiness - iNewUnhappiness > /*-8*/ GD_INT_GET(VERY_UNHAPPY_THRESHOLD) + 2; // Add some margin of error + } + else + { + int iUnhappy = pPlayer->GetUnhappinessFromCitizenNeeds() - iPublicOpinionUnhappiness + iNewUnhappiness; + int iHappy = pPlayer->GetHappinessFromCitizenNeeds(); + bSwitch = ((iHappy * 100) / max(1, iUnhappy) / 2) >= /*40*/ GD_INT_GET(VERY_UNHAPPY_THRESHOLD) + 5; // Add some margin of error + } + + if (bSwitch) + { + if (MOD_API_ACHIEVEMENTS && ePreferredIdeology == GD_INT_GET(POLICY_BRANCH_FREEDOM) && eCurrentIdeology == GD_INT_GET(POLICY_BRANCH_ORDER)) { - return; + PlayerTypes eMostPressure = pPlayer->GetCulture()->GetPublicOpinionBiggestInfluence(); + if (eMostPressure != NO_PLAYER && GET_PLAYER(eMostPressure).GetID() == GC.getGame().getActivePlayer()) + { + gDLL->UnlockAchievement(ACHIEVEMENT_XP2_39); + } } // Cleared all obstacles -- REVOLUTION! @@ -754,8 +933,12 @@ void CvPolicyAI::DoConsiderIdeologySwitch(CvPlayer* pPlayer) pPlayer->GetNotifications()->Add(NOTIFICATION_GENERIC, strMessage.toUTF8(), strSummary.toUTF8(), pPlayer->GetID(), /*2 in CP, 3 in VP*/ GD_INT_GET(SWITCH_POLICY_BRANCHES_ANARCHY_TURNS), -1); } } - else if (bVUnhappy && iPublicOpinionUnhappiness >= /*-10 in CP, 35 in VP*/ GD_INT_GET(VERY_UNHAPPY_THRESHOLD)) + else if (bVUnhappy) { + // Only switch ideologies if we're about to lose a city. + if (pPlayer->GetCityRevoltCounter() <= 2 + /*2 in CP, 3 in VP*/ GD_INT_GET(SWITCH_POLICY_BRANCHES_ANARCHY_TURNS)) + return; + // Does the switch fight against our clearly preferred victory path? bool bDontSwitchFreedom = false; bool bDontSwitchOrder = false; @@ -784,66 +967,39 @@ void CvPolicyAI::DoConsiderIdeologySwitch(CvPlayer* pPlayer) bDontSwitchAutocracy = true; } - //Sanity check - would a change to this branch simply make us unhappy in another way? If so, don't do it. - if (ePreferredIdeology != NO_POLICY_BRANCH_TYPE) + if (bDontSwitchFreedom && ePreferredIdeology == GD_INT_GET(POLICY_BRANCH_FREEDOM)) { - int iUnhappiness = pPlayer->GetCulture()->ComputeHypotheticalPublicOpinionUnhappiness(ePreferredIdeology); - if (iUnhappiness >= iPublicOpinionUnhappiness) - { - return; - } - // Finally see what our friends (and enemies) have already chosen - for (int iPlayerLoop = 0; iPlayerLoop < MAX_MAJOR_CIVS; iPlayerLoop++) - { - PlayerTypes eLoopPlayer = (PlayerTypes) iPlayerLoop; - if (eLoopPlayer != pPlayer->GetID() && pPlayer->GetDiplomacyAI()->IsPlayerValid(eLoopPlayer)) - { - CvPlayer &kOtherPlayer = GET_PLAYER(eLoopPlayer); - PolicyBranchTypes eOtherPlayerIdeology; - eOtherPlayerIdeology = kOtherPlayer.GetPlayerPolicies()->GetLateGamePolicyTree(); - - if (pPlayer->GetDiplomacyAI()->GetCivApproach(eLoopPlayer) <= CIV_APPROACH_HOSTILE) - { - if (eOtherPlayerIdeology == ePreferredIdeology) - return; - } - } - } - - if (bDontSwitchFreedom && ePreferredIdeology == GD_INT_GET(POLICY_BRANCH_FREEDOM)) - { - return; - } - if (bDontSwitchAutocracy && ePreferredIdeology == GD_INT_GET(POLICY_BRANCH_AUTOCRACY)) - { - return; - } - if (bDontSwitchOrder && ePreferredIdeology == GD_INT_GET(POLICY_BRANCH_ORDER)) - { - return; - } - //Final sanity check - are we flip-flopping? - if(GC.getGame().getGameTurn() - pPlayer->GetCulture()->GetTurnIdeologySwitch() <= 30) - { - return; - } + return; + } + if (bDontSwitchAutocracy && ePreferredIdeology == GD_INT_GET(POLICY_BRANCH_AUTOCRACY)) + { + return; + } + if (bDontSwitchOrder && ePreferredIdeology == GD_INT_GET(POLICY_BRANCH_ORDER)) + { + return; + } + //Sanity check - are we flip-flopping? + if (GC.getGame().getGameTurn() - pPlayer->GetCulture()->GetTurnIdeologySwitch() <= 30) + { + return; + } - if (MOD_API_ACHIEVEMENTS && ePreferredIdeology == GD_INT_GET(POLICY_BRANCH_FREEDOM) && eCurrentIdeology == GD_INT_GET(POLICY_BRANCH_ORDER)) + if (MOD_API_ACHIEVEMENTS && ePreferredIdeology == GD_INT_GET(POLICY_BRANCH_FREEDOM) && eCurrentIdeology == GD_INT_GET(POLICY_BRANCH_ORDER)) + { + PlayerTypes eMostPressure = pPlayer->GetCulture()->GetPublicOpinionBiggestInfluence(); + if (eMostPressure != NO_PLAYER && GET_PLAYER(eMostPressure).GetID() == GC.getGame().getActivePlayer()) { - PlayerTypes eMostPressure = pPlayer->GetCulture()->GetPublicOpinionBiggestInfluence(); - if (eMostPressure != NO_PLAYER && GET_PLAYER(eMostPressure).GetID() == GC.getGame().getActivePlayer()) - { - gDLL->UnlockAchievement(ACHIEVEMENT_XP2_39); - } + gDLL->UnlockAchievement(ACHIEVEMENT_XP2_39); } - - // Cleared all obstacles -- REVOLUTION! - pPlayer->SetAnarchyNumTurns(/*2 in CP, 3 in VP*/ GD_INT_GET(SWITCH_POLICY_BRANCHES_ANARCHY_TURNS)); - pPlayer->GetPlayerPolicies()->DoSwitchIdeologies(ePreferredIdeology); - Localization::String strSummary = Localization::Lookup("TXT_KEY_ANARCHY_BEGINS_SUMMARY"); - Localization::String strMessage = Localization::Lookup("TXT_KEY_ANARCHY_BEGINS"); - pPlayer->GetNotifications()->Add(NOTIFICATION_GENERIC, strMessage.toUTF8(), strSummary.toUTF8(), pPlayer->GetID(), /*2 in CP, 3 in VP*/ GD_INT_GET(SWITCH_POLICY_BRANCHES_ANARCHY_TURNS), -1); } + + // Cleared all obstacles -- REVOLUTION! + pPlayer->SetAnarchyNumTurns(/*2 in CP, 3 in VP*/ GD_INT_GET(SWITCH_POLICY_BRANCHES_ANARCHY_TURNS)); + pPlayer->GetPlayerPolicies()->DoSwitchIdeologies(ePreferredIdeology); + Localization::String strSummary = Localization::Lookup("TXT_KEY_ANARCHY_BEGINS_SUMMARY"); + Localization::String strMessage = Localization::Lookup("TXT_KEY_ANARCHY_BEGINS"); + pPlayer->GetNotifications()->Add(NOTIFICATION_GENERIC, strMessage.toUTF8(), strSummary.toUTF8(), pPlayer->GetID(), /*2 in CP, 3 in VP*/ GD_INT_GET(SWITCH_POLICY_BRANCHES_ANARCHY_TURNS), -1); } } diff --git a/CvGameCoreDLL_Expansion2/CvPolicyClasses.cpp b/CvGameCoreDLL_Expansion2/CvPolicyClasses.cpp index f9bfb8ec4f..ff3ccbe713 100644 --- a/CvGameCoreDLL_Expansion2/CvPolicyClasses.cpp +++ b/CvGameCoreDLL_Expansion2/CvPolicyClasses.cpp @@ -6431,7 +6431,10 @@ void CvPlayerPolicies::DoPolicyAI() { CvString strBuffer; + // Force an ideology update for human vassals, if applicable m_pPolicyAI->DoConsiderIdeologySwitch(m_pPlayer); + if (m_pPlayer->isHuman()) + return; // Do we have enough points to buy a new policy? if (m_pPlayer->getNextPolicyCost() > 0 || m_pPlayer->GetNumFreePolicies() > 0 || m_pPlayer->GetNumFreeTenets() > 0) diff --git a/CvGameCoreDLL_Expansion2/CvReligionClasses.cpp b/CvGameCoreDLL_Expansion2/CvReligionClasses.cpp index ec28c476c8..9225a05ac6 100644 --- a/CvGameCoreDLL_Expansion2/CvReligionClasses.cpp +++ b/CvGameCoreDLL_Expansion2/CvReligionClasses.cpp @@ -1232,8 +1232,8 @@ void CvGameReligions::FoundReligion(PlayerTypes ePlayer, ReligionTypes eReligion if (pLoopUnit->getUnitInfo().IsFoundReligion()) { bool bSubtractOne = false; - // If player is India, subtract one charge from the prophet who founded - if (kPlayer.GetPlayerTraits()->IsProphetFervor() && pLoopUnit->GetReligionData() != NULL && pLoopUnit->GetReligionData()->GetSpreadsUsed() > 0) + // If player is India, subtract one charge from any prophets who used one (either by founding or by building a Holy Site) + if (kPlayer.GetPlayerTraits()->IsProphetFervor() && pLoopUnit->GetReligionData()->GetSpreadsUsed() > 0) bSubtractOne = true; pLoopUnit->GetReligionDataMutable()->SetFullStrength(kPlayer.GetID(), pLoopUnit->getUnitInfo(), eReligion); @@ -1241,7 +1241,7 @@ void CvGameReligions::FoundReligion(PlayerTypes ePlayer, ReligionTypes eReligion if (bSubtractOne) { pLoopUnit->GetReligionDataMutable()->IncrementSpreadsUsed(); - if (pLoopUnit->GetReligionData() != NULL && pLoopUnit->GetReligionData()->GetSpreadsLeft(pLoopUnit) <= 0) + if (pLoopUnit->GetReligionData()->GetSpreadsLeft(pLoopUnit) <= 0) { kPlayer.DoGreatPersonExpended(pLoopUnit->getUnitType(), pLoopUnit); pLoopUnit->kill(true); @@ -7011,8 +7011,8 @@ bool CvReligionAI::DoFaithPurchases() int iLoop = 0; for (CvUnit* pLoopUnit = m_pPlayer->firstUnit(&iLoop); pLoopUnit != NULL; pLoopUnit = m_pPlayer->nextUnit(&iLoop)) { - if (pLoopUnit->GetReligionData() != NULL && pLoopUnit->GetReligionData()->GetSpreadsLeft(pLoopUnit) > 0) - if ( pLoopUnit->GetReligionData()->GetReligion() == eReligionWeFounded || pLoopUnit->GetReligionData()->GetReligion() == eReligionToSpread) + if (pLoopUnit->GetReligionData()->GetSpreadsLeft(pLoopUnit) > 0) + if (pLoopUnit->GetReligionData()->GetReligion() == eReligionWeFounded || pLoopUnit->GetReligionData()->GetReligion() == eReligionToSpread) iNumMissionaries++; } @@ -8173,6 +8173,9 @@ int CvReligionAI::ScoreBeliefAtPlot(CvBeliefEntry* pEntry, CvPlot* pPlot, bool b int CvReligionAI::ScorePantheonBeliefAtCity(CvBeliefEntry* pEntry, CvCity* pCity) const { + if (m_pPlayer->getCapitalCity() == NULL) + return 0; + // the different yield types are valued using ScoreYieldForReligionTimes100 // happiness is valued with iHappinessNeedFactor (see below) // great person points are valued with iGPValue (see below) @@ -8637,6 +8640,9 @@ int CvReligionAI::ScorePantheonBeliefAtCity(CvBeliefEntry* pEntry, CvCity* pCity /// AI's evaluation of this belief's usefulness at this city int CvReligionAI::ScoreBeliefAtCity(CvBeliefEntry* pEntry, CvCity* pCity) const { + if (m_pPlayer->getCapitalCity() == NULL) + return 0; + int iRtnValue = 0; int iTempValue = 0; int iMinPop = 0; diff --git a/CvGameCoreDLL_Expansion2/CvTreasury.cpp b/CvGameCoreDLL_Expansion2/CvTreasury.cpp index ba6db88ee4..c2e81c0020 100644 --- a/CvGameCoreDLL_Expansion2/CvTreasury.cpp +++ b/CvGameCoreDLL_Expansion2/CvTreasury.cpp @@ -977,35 +977,64 @@ int CvTreasury::GetContractGoldMaintenance() #endif // What are our gold maintenance costs because of Vassals? -int CvTreasury::GetVassalGoldMaintenance() const +int CvTreasury::GetVassalGoldMaintenance(TeamTypes eTeam) const { - int iRtnValue = 0; - // We have a vassal - for(int iI = 0; iI < MAX_MAJOR_CIVS; iI++) + int iNumTeamMembers = GET_TEAM(m_pPlayer->getTeam()).getAliveCount(); + if (iNumTeamMembers == 0) + return 0; + + int iTotalExpense = 0; + int iTotalExpenseOtherVassals = 0; + for (int iPlayerLoop = 0; iPlayerLoop < MAX_MAJOR_CIVS; iPlayerLoop++) { - if(!GET_PLAYER((PlayerTypes)iI).isMinorCiv() - && !GET_PLAYER((PlayerTypes)iI).isBarbarian() - && GET_PLAYER((PlayerTypes)iI).isAlive()) + PlayerTypes eLoopPlayer = (PlayerTypes) iPlayerLoop; + TeamTypes eLoopTeam = GET_PLAYER(eLoopPlayer).getTeam(); + if (GET_TEAM(eLoopTeam).GetMaster() != m_pPlayer->getTeam()) + continue; + + int iLoop = 0; + if (eTeam == NO_TEAM || eTeam == eLoopTeam) { - int iLoop = 0; - int iCityPop = 0; - // This player is our vassal - if(GET_TEAM(GET_PLAYER((PlayerTypes)iI).getTeam()).IsVassal(m_pPlayer->getTeam())) + int iExpense = 0; + // Loop through our vassal's cities + for (CvCity* pLoopCity = GET_PLAYER(eLoopPlayer).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER(eLoopPlayer).nextCity(&iLoop)) { - // Loop through our vassal's cities - for(CvCity* pLoopCity = GET_PLAYER((PlayerTypes)iI).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER((PlayerTypes)iI).nextCity(&iLoop)) - { - iCityPop = pLoopCity->getPopulation(); - iRtnValue += std::max(0, (int)(pow((double)iCityPop, (double) /*0.8f*/ GD_FLOAT_GET(VASSALAGE_VASSAL_CITY_POP_EXPONENT)))); - } + int iCityPop = pLoopCity->getPopulation(); + iExpense += std::max(0, (int)(pow((double)iCityPop, (double) /*0.8f*/ GD_FLOAT_GET(VASSALAGE_VASSAL_CITY_POP_EXPONENT)))); + } - iRtnValue += std::max(0, (GET_PLAYER((PlayerTypes)iI).GetTreasury()->GetExpensePerTurnUnitMaintenance() * /*10*/ GD_INT_GET(VASSALAGE_VASSAL_UNIT_MAINT_COST_PERCENT) / 100)); + iExpense += std::max(0, GET_PLAYER(eLoopPlayer).GetTreasury()->GetExpensePerTurnUnitMaintenance() * /*10*/ GD_INT_GET(VASSALAGE_VASSAL_UNIT_MAINT_COST_PERCENT) / 100); + iTotalExpense += iExpense; + } + else + { + int iExpense = 0; + // Loop through our vassal's cities + for (CvCity* pLoopCity = GET_PLAYER(eLoopPlayer).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER(eLoopPlayer).nextCity(&iLoop)) + { + int iCityPop = pLoopCity->getPopulation(); + iExpense += std::max(0, (int)(pow((double)iCityPop, (double) /*0.8f*/ GD_FLOAT_GET(VASSALAGE_VASSAL_CITY_POP_EXPONENT)))); } + + iExpense += std::max(0, GET_PLAYER(eLoopPlayer).GetTreasury()->GetExpensePerTurnUnitMaintenance() * /*10*/ GD_INT_GET(VASSALAGE_VASSAL_UNIT_MAINT_COST_PERCENT) / 100); + iTotalExpenseOtherVassals += iExpense; } } + // What is my share of this maintenance? + int iRtnValue = iTotalExpense / iNumTeamMembers; + + // Team leader gets any remainder + if (GET_TEAM(m_pPlayer->getTeam()).getLeaderID() == m_pPlayer->GetID()) + { + if (eTeam == NO_TEAM) + iRtnValue += iTotalExpense % iNumTeamMembers; + else + iRtnValue += (iTotalExpense + iTotalExpenseOtherVassals) % iNumTeamMembers; + } + // Modifier for vassal maintenance? - iRtnValue *= (100 + m_pPlayer->GetVassalGoldMaintenanceMod()); + iRtnValue *= 100 + m_pPlayer->GetVassalGoldMaintenanceMod(); iRtnValue /= 100; return iRtnValue; @@ -1026,6 +1055,18 @@ void CvTreasury::CalculateExpensePerTurnFromVassalTaxes() SetExpensePerTurnFromVassalTaxesTimes100(iTax); } +/// How much would we owe if our tax rate increased? +int CvTreasury::CalculateProjectedExpensePerTurnFromVassalTaxes(int iProjectedTaxRate) +{ + TeamTypes eMaster = GET_TEAM(m_pPlayer->getTeam()).GetMaster(); + if (eMaster == NO_TEAM) + return 0; + + int iNet = CalculateGrossGoldTimes100(); + int iTax = iNet * iProjectedTaxRate / 100; + return iTax; +} + // Set how much we owe this turn due to taxes void CvTreasury::SetExpensePerTurnFromVassalTaxesTimes100(int iValue) { @@ -1045,42 +1086,66 @@ int CvTreasury::GetExpensePerTurnFromVassalTaxes() const } // What percent of vassal taxes am I owed? -int CvTreasury::GetMyShareOfVassalTaxes() const +int CvTreasury::GetMyShareOfVassalTaxes(TeamTypes eTeam, int iProjectedTaxRate) const { int iNumTeamMembers = GET_TEAM(m_pPlayer->getTeam()).getAliveCount(); - if(iNumTeamMembers == 0) + if (iNumTeamMembers == 0) return 0; int iTotalTaxes = 0; - PlayerTypes eLoopPlayer; - for(int iPlayerLoop = 0; iPlayerLoop < MAX_MAJOR_CIVS; iPlayerLoop++) + int iTotalTaxesOtherVassals = 0; + for (int iPlayerLoop = 0; iPlayerLoop < MAX_MAJOR_CIVS; iPlayerLoop++) { - eLoopPlayer = (PlayerTypes) iPlayerLoop; - if(GET_TEAM(GET_PLAYER(eLoopPlayer).getTeam()).GetMaster() == m_pPlayer->getTeam()) + PlayerTypes eLoopPlayer = (PlayerTypes) iPlayerLoop; + TeamTypes eLoopTeam = GET_PLAYER(eLoopPlayer).getTeam(); + if (GET_TEAM(eLoopTeam).GetMaster() != m_pPlayer->getTeam()) + continue; + + if (eTeam == NO_TEAM || eTeam == eLoopTeam) { - iTotalTaxes += GET_PLAYER(eLoopPlayer).GetTreasury()->GetExpensePerTurnFromVassalTaxesTimes100(); + if (iProjectedTaxRate == -1) + iTotalTaxes += GET_PLAYER(eLoopPlayer).GetTreasury()->GetExpensePerTurnFromVassalTaxesTimes100(); + else + iTotalTaxes += GET_PLAYER(eLoopPlayer).GetTreasury()->CalculateProjectedExpensePerTurnFromVassalTaxes(iProjectedTaxRate); + } + else + { + iTotalTaxesOtherVassals += GET_PLAYER(eLoopPlayer).GetTreasury()->GetExpensePerTurnFromVassalTaxesTimes100(); } } // What is my share of these taxes? - return (iTotalTaxes / iNumTeamMembers); + int iRtnValue = iTotalTaxes / iNumTeamMembers; + + // Team leader gets any remainder + if (GET_TEAM(m_pPlayer->getTeam()).getLeaderID() == m_pPlayer->GetID()) + { + if (eTeam == NO_TEAM) + iRtnValue += iTotalTaxes % iNumTeamMembers; + else + iRtnValue += (iTotalTaxes + iTotalTaxesOtherVassals) % iNumTeamMembers; + } + + return iRtnValue; } // How much is ePlayer contributing to my vassal tax revenue (note: this doesn't actually set anything, for pure UI purposes) int CvTreasury::GetVassalTaxContributionTimes100(PlayerTypes ePlayer) const { - int iNumTeamMembers = GET_TEAM(m_pPlayer->getTeam()).getAliveCount(); - if(iNumTeamMembers == 0) + TeamTypes eTeam = GET_PLAYER(ePlayer).getTeam(); + int iNumTeamMembers = GET_TEAM(eTeam).getAliveCount(); + if (iNumTeamMembers == 0) return 0; - int iAmount = 0; - - if(GET_TEAM(GET_PLAYER(ePlayer).getTeam()).GetMaster() == m_pPlayer->getTeam()) - { - iAmount += GET_PLAYER(ePlayer).GetTreasury()->GetExpensePerTurnFromVassalTaxesTimes100(); - } + int iMyShare = GetMyShareOfVassalTaxes(eTeam); + int iRtnValue = iMyShare / iNumTeamMembers; - return iAmount / iNumTeamMembers; + // If they're the team leader, attribute any remainder to them + // Not a perfect solution, but good enough + if (GET_TEAM(eTeam).getLeaderID() == ePlayer) + iRtnValue += iMyShare % iNumTeamMembers; + + return iRtnValue; } int CvTreasury::GetVassalTaxContribution(PlayerTypes ePlayer) const diff --git a/CvGameCoreDLL_Expansion2/CvTreasury.h b/CvGameCoreDLL_Expansion2/CvTreasury.h index d845038483..08c822c89a 100644 --- a/CvGameCoreDLL_Expansion2/CvTreasury.h +++ b/CvGameCoreDLL_Expansion2/CvTreasury.h @@ -103,13 +103,14 @@ class CvTreasury int AverageIncome100(int iTurns); void LogExpenditure(const CvString& strExpenditure, int iAmount, int iColumn); - int GetVassalGoldMaintenance() const; + int GetVassalGoldMaintenance(TeamTypes eTeam = NO_TEAM) const; - int GetMyShareOfVassalTaxes() const; + int GetMyShareOfVassalTaxes(TeamTypes eTeam = NO_TEAM, int iProjectedTaxRate = -1) const; int GetVassalTaxContributionTimes100(PlayerTypes ePlayer) const; int GetVassalTaxContribution(PlayerTypes ePlayer) const; void CalculateExpensePerTurnFromVassalTaxes(); + int CalculateProjectedExpensePerTurnFromVassalTaxes(int iProjectedTaxRate); int GetExpensePerTurnFromVassalTaxesTimes100() const; int GetExpensePerTurnFromVassalTaxes() const; diff --git a/CvGameCoreDLL_Expansion2/CvUnit.cpp b/CvGameCoreDLL_Expansion2/CvUnit.cpp index b77735a1af..8ba0ec767c 100644 --- a/CvGameCoreDLL_Expansion2/CvUnit.cpp +++ b/CvGameCoreDLL_Expansion2/CvUnit.cpp @@ -10737,6 +10737,12 @@ bool CvUnit::CanFoundReligion(const CvPlot* pPlot) const return false; } + // If prophet has started spreading religion, can't do other functions (possible with India) + if (m_pUnitInfo->IsSpreadReligion() && GetReligionData()->GetSpreadsUsed()>0) + { + return false; + } + if(GET_PLAYER(getOwner()).GetReligions()->IsFoundingReligion()) { return false; @@ -10916,7 +10922,7 @@ bool CvUnit::CanEnhanceReligion(const CvPlot* pPlot) const } // If prophet has started spreading religion, can't do other functions - if(m_pUnitInfo->IsSpreadReligion() && GetReligionData()->GetSpreadsUsed()>0) + if (m_pUnitInfo->IsSpreadReligion() && GetReligionData()->GetSpreadsUsed()>0) { return false; } @@ -12810,8 +12816,8 @@ bool CvUnit::canGoldenAge(const CvPlot* pPlot, bool bTestVisible) const return false; } - // If prophet has started spreading religion, can't do other functions - if(m_pUnitInfo->IsSpreadReligion() && GetReligionData()->GetSpreadsUsed()>0) + // If prophet has started spreading religion, can't do other functions + if (m_pUnitInfo->IsSpreadReligion() && GetReligionData()->GetSpreadsUsed()>0) { return false; } @@ -12974,8 +12980,8 @@ bool CvUnit::canGivePolicies(const CvPlot* /*pPlot*/, bool /*bTestVisible*/) con return false; } - // If prophet has started spreading religion, can't do other functions - if(m_pUnitInfo->IsSpreadReligion() && GetReligionData()->GetSpreadsUsed()>0) + // If prophet has started spreading religion, can't do other functions + if (m_pUnitInfo->IsSpreadReligion() && GetReligionData()->GetSpreadsUsed()>0) { return false; } @@ -13366,13 +13372,8 @@ bool CvUnit::canBuild(const CvPlot* pPlot, BuildTypes eBuild, bool bTestVisible, return false; // If prophet has started spreading religion, can't do other functions - if (m_pUnitInfo->IsSpreadReligion()) - { - if (GetReligionData()->GetReligion() != NO_RELIGION && GetReligionData()->GetSpreadsUsed() > 0) - { - return false; - } - } + if (m_pUnitInfo->IsSpreadReligion() && GetReligionData()->GetSpreadsUsed() > 0) + return false; if (!pPlot) return true; @@ -13576,11 +13577,12 @@ bool CvUnit::build(BuildTypes eBuild) } bool bIndiaException = false; - ImprovementTypes eHolySite = (ImprovementTypes)GC.getInfoTypeForString("IMPROVEMENT_HOLY_SITE"); - if (eImprovement == eHolySite && GET_PLAYER(getOwner()).GetPlayerTraits()->IsProphetFervor()) + if (m_pUnitInfo->IsFoundReligion() && GET_PLAYER(getOwner()).GetPlayerTraits()->IsProphetFervor()) { GetReligionDataMutable()->IncrementSpreadsUsed(); - if (GetReligionData()->GetSpreadsLeft(this) > 0) + + // Test for > 1 total spreads here, not spreads left, because it's possible to create a Holy Site before founding a religion + if (getUnitInfo().GetReligionSpreads() > 1) bIndiaException = true; if (bIndiaException && pPlot->isActiveVisible()) diff --git a/CvGameCoreDLL_Expansion2/Lua/CvLuaPlayer.cpp b/CvGameCoreDLL_Expansion2/Lua/CvLuaPlayer.cpp index 9e960d0af6..b8b5d6e9c4 100644 --- a/CvGameCoreDLL_Expansion2/Lua/CvLuaPlayer.cpp +++ b/CvGameCoreDLL_Expansion2/Lua/CvLuaPlayer.cpp @@ -16613,7 +16613,7 @@ int CvLuaPlayer::lGetVassalGoldMaintenance(lua_State* L) { CvPlayerAI* pkPlayer = GetInstance(L); - const int iResult = pkPlayer->GetTreasury()->GetVassalGoldMaintenance(); + const int iResult = pkPlayer->GetTreasury()->GetVassalGoldMaintenance(NO_TEAM); lua_pushinteger(L, iResult); return 1; } @@ -16683,7 +16683,7 @@ int CvLuaPlayer::lGetMyShareOfVassalTaxes(lua_State* L) { CvPlayerAI* pkPlayer = GetInstance(L); - lua_pushinteger(L, pkPlayer->GetTreasury()->GetMyShareOfVassalTaxes()); + lua_pushinteger(L, pkPlayer->GetTreasury()->GetMyShareOfVassalTaxes(NO_TEAM, -1)); return 1; }