Rompersenor
Chieftain
- Joined
- Aug 28, 2022
- Messages
- 17
Disclaimer: This proposal alters only AI behavior when there is at least one team of 2+ players in the game. If you play without using teams, nothing will change for you. Original thread with some discussion can be found here: https://forums.civfanatics.com/thre...ce-offers-to-some-distant-team-members.689011
Proposal:
Alter the AI behavior so that when the AI computes its willingness to make peace (and potential concessions) with a player in a team of more players, it is the same for all such players. Technical details follow, but the main point is that:
1) The AI will be willing to make peace with either every or none player in the same team, nothing between. (Side note - When the AI wants concessions you cannot fulfill, I consider the AI be willing to make peace, although it will not be possible to make one)
2) When willing to make peace, the AI will be willing to make peace under similar concession settings for all such players.
Rationale:
Current system is annoying and can be abused. Consider this situation under current conditions:
1) You play multiplayer in a team of 2 players vs AIs (you and your real life human friend are both Team 1, each AI has its own team).
2) Hungry Shaka declares war on one of you (you both enter war automatically).
3) One of you (player A) is not neighboring Shaka and thus doing OK in the war; the second one (Player B) is having a hard time, with a besieged city and heavy loses.
4) This AI Shaka of course has no interest in Peace treaty with the losing Player B (which is sound)... but after several turns makes white peace with the player A happily (...thus the war ends for the whole team) although he would get massive gains from continuing the war against the losing player.
Why? Because the AI currently does not consider teams at all.
The system is also annoying because the AI offers different peace treaties to each player so the optimal play is for the players to compare this peace treaties before accepting one. It should not matter which player is chosen as a team representative (the one who has nothing else to do in that moment most likely )
Technical details of the proposal (big thanks to Recursive for his help inspecting the code):
Function to determine peace willingness is displayed here:
No other function is expected to be changed, unless it is technicaly necessary (that is up to sponsor's judgement).
Changes (might need loop rewrite or structure rewrite of the function):
1) The AI should compute its bConsiderPeace against all players in that team. If it is false at least for one player, AI will not consider peace with that team at all.
2) The AI should compute its iPeaceScore against all players in that team (Note: Proximity is currently taken into action! AI is more likely to accept peace with distant player - will be adjusted in further steps).
3) The AI should make an aggregation (see below) of these iPeaceScore. The resulting value is then compared to the iThreshold and if (and only if) it is sufficient then AI will be willing to sign peace with any player of that team.
4) The AI should compute iWillingToOfferScore and iWillingToAcceptScore and make aggregation (see below) of these values and use resulting value for every player in the same team.
Values iPeaceScore, iWillingToOfferScore and iWillingToAcceptScore should be aggregated (all in the same manner) as a weighted average of the values for individual players in a team, considering the distance of the player from the AI as a weight. There are four possible values for distances (PLAYER_PROXIMITY_x) already used - NEIGHBORS, CLOSE, FAR or DISTANT. To make close players more important, the weights should be set as weight(NEIGHBORS) = 4, weight(CLOSE) = 3, weight(FAR) = 2, weight(DISTANT) = 1.
Example of computation of iPeaceScore:
Player A is FAR from Shaka and somehow AI Shaka computes its iPeaceScore against this player as 50. Player B is NEIGHBOR of Shaka and Shaka somehow computes iPeaceScore against this player as 5. Resulting iPeaceScore for that team is (50*2 + 5*4)/(2+4)=20
NOTE: Other parts of the computation should be changed as little as possible. While implementing this, some small problems might have to be resolved. Recursive has pointed out one:
Proposal:
Alter the AI behavior so that when the AI computes its willingness to make peace (and potential concessions) with a player in a team of more players, it is the same for all such players. Technical details follow, but the main point is that:
1) The AI will be willing to make peace with either every or none player in the same team, nothing between. (Side note - When the AI wants concessions you cannot fulfill, I consider the AI be willing to make peace, although it will not be possible to make one)
2) When willing to make peace, the AI will be willing to make peace under similar concession settings for all such players.
Rationale:
Current system is annoying and can be abused. Consider this situation under current conditions:
1) You play multiplayer in a team of 2 players vs AIs (you and your real life human friend are both Team 1, each AI has its own team).
2) Hungry Shaka declares war on one of you (you both enter war automatically).
3) One of you (player A) is not neighboring Shaka and thus doing OK in the war; the second one (Player B) is having a hard time, with a besieged city and heavy loses.
4) This AI Shaka of course has no interest in Peace treaty with the losing Player B (which is sound)... but after several turns makes white peace with the player A happily (...thus the war ends for the whole team) although he would get massive gains from continuing the war against the losing player.
Why? Because the AI currently does not consider teams at all.
The system is also annoying because the AI offers different peace treaties to each player so the optimal play is for the players to compare this peace treaties before accepting one. It should not matter which player is chosen as a team representative (the one who has nothing else to do in that moment most likely )
Technical details of the proposal (big thanks to Recursive for his help inspecting the code):
Function to determine peace willingness is displayed here:
Code:
void CvDiplomacyAI::DoUpdatePeaceTreatyWillingness(bool bMyTurn)
{
CvCity* pCapital = GetPlayer()->getCapitalCity();
bool bLog = bMyTurn && GC.getLogging() && GC.getAILogging(); // Only log this once per turn to prevent log spam
int iLoop = 0;
if (GC.getGame().isOption(GAMEOPTION_ALWAYS_WAR) || GC.getGame().isOption(GAMEOPTION_NO_CHANGING_WAR_PEACE) || GetPlayer()->isHuman() || GetPlayer()->IsAITeammateOfHuman() || GetPlayer()->IsVassalOfSomeone())
{
for (int iPlayerLoop = 0; iPlayerLoop < MAX_MAJOR_CIVS; iPlayerLoop++)
{
PlayerTypes ePlayer = (PlayerTypes) iPlayerLoop;
SetTreatyWillingToOffer(ePlayer, NO_PEACE_TREATY_TYPE);
SetTreatyWillingToAccept(ePlayer, NO_PEACE_TREATY_TYPE);
}
if (bLog && !GetPlayer()->isHuman())
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString strLogName;
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
strBaseString += playerName + " VS. Everyone";
strOutBuf.Format("Not allowed to change war/peace status with anyone!");
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
return;
}
// STEP 1: See if we're in a critical state and check which players are valid
bool bCriticalState = GetPlayer()->IsEmpireSuperUnhappy() || !pCapital || pCapital->isInDangerOfFalling(true) || pCapital->getDamage() >= (pCapital->GetMaxHitPoints()/2);
bool bMakePeaceWithAllMinors = false;
bool bWorldConquest = IsGoingForWorldConquest() || IsCloseToWorldConquest();
bool bDiplomatic = IsGoingForDiploVictory() || IsDiplomat() || GetPlayer()->GetPlayerTraits()->IsDiplomat();
vector<PlayerTypes> vValidPlayers;
for (int iPlayerLoop = 0; iPlayerLoop < MAX_CIV_PLAYERS; iPlayerLoop++)
{
PlayerTypes eLoopPlayer = (PlayerTypes) iPlayerLoop;
if (GET_PLAYER(eLoopPlayer).isAlive() && IsAtWar(eLoopPlayer))
{
if (GET_PLAYER(eLoopPlayer).isMajorCiv())
{
if (IsPeaceBlocked(eLoopPlayer))
{
SetTreatyWillingToOffer(eLoopPlayer, NO_PEACE_TREATY_TYPE);
SetTreatyWillingToAccept(eLoopPlayer, NO_PEACE_TREATY_TYPE);
if (bLog)
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString otherPlayerName;
CvString strLogName;
CvString strPeaceBlockReason;
FmtPeaceBlockReasonLogStr(strPeaceBlockReason, GetID(), eLoopPlayer, GetPeaceBlockReason(eLoopPlayer));
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
otherPlayerName = GET_PLAYER(eLoopPlayer).getCivilizationShortDescription();
strBaseString += playerName + " VS. " + otherPlayerName;
strOutBuf.Format("PEACE BLOCKED! ");
strOutBuf += strPeaceBlockReason;
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
}
else
{
vValidPlayers.push_back(eLoopPlayer);
}
}
if (!bCriticalState)
{
WarStateTypes eWarState = GetWarState(eLoopPlayer);
if (eWarState == WAR_STATE_NEARLY_DEFEATED)
{
bCriticalState = true;
}
else if (eWarState <= WAR_STATE_TROUBLED)
{
if (GetPlayer()->IsNoNewWars() || !GET_PLAYER(eLoopPlayer).isMajorCiv() || !IsPhonyWar(eLoopPlayer) || GetWarScore(eLoopPlayer) <= WARSCORE_THRESHOLD_NEGATIVE)
bMakePeaceWithAllMinors = true;
}
}
}
else if (GET_PLAYER(eLoopPlayer).isMajorCiv())
{
SetTreatyWillingToOffer(eLoopPlayer, NO_PEACE_TREATY_TYPE);
SetTreatyWillingToAccept(eLoopPlayer, NO_PEACE_TREATY_TYPE);
}
}
if (bCriticalState)
bMakePeaceWithAllMinors = true;
// STEP 2: Make peace with any City-States here - this happens instantly!
if (bMyTurn)
{
for (int iPlayerLoop = MAX_MAJOR_CIVS; iPlayerLoop < MAX_CIV_PLAYERS; iPlayerLoop++)
{
PlayerTypes eMinor = (PlayerTypes) iPlayerLoop;
if (IsAtWar(eMinor))
{
if (IsPeaceBlocked(eMinor))
{
if (bLog)
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString otherPlayerName;
CvString strLogName;
CvString strPeaceBlockReason;
FmtPeaceBlockReasonLogStr(strPeaceBlockReason, GetID(), eMinor, GetPeaceBlockReason(eMinor));
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
otherPlayerName = GET_PLAYER(eMinor).getCivilizationShortDescription();
strBaseString += playerName + " VS. " + otherPlayerName;
strOutBuf.Format("PEACE BLOCKED! ");
strOutBuf += strPeaceBlockReason;
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
}
else
{
if (bMakePeaceWithAllMinors || GetCSWarTargetPlayer() != eMinor)
{
if (!bCriticalState)
{
// Exception if making peace with this City-State would prevent us from achieving Domination Victory
if (GC.getGame().WouldMakingPeacePreventDominationVictory(GetID(), eMinor))
{
if (IsGoingForWorldConquest() || IsCloseToWorldConquest())
continue;
}
// Exception under two circumstances: the minor's capital or a city they captured from us are in danger from us
for (CvCity* pLoopCity = GET_PLAYER(eMinor).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER(eMinor).nextCity(&iLoop))
{
if (pLoopCity->isCapital() || GET_PLAYER(pLoopCity->getOriginalOwner()).getTeam() == GetTeam())
{
if (pLoopCity->IsInDanger(GetID()) && (pLoopCity->isUnderSiege() || pLoopCity->IsBlockadedWaterAndLand()))
{
continue;
}
}
}
}
GET_TEAM(GetTeam()).makePeace(GET_PLAYER(eMinor).getTeam(), true, false, GetID());
LogPeaceMade(eMinor);
if (bLog)
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString otherPlayerName;
CvString strLogName;
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
otherPlayerName = GET_PLAYER(eMinor).getCivilizationShortDescription();
strBaseString += playerName + " VS. " + otherPlayerName;
if (bCriticalState)
strOutBuf.Format("Making peace with all possible City-States because we're in a critical state!");
else if (bMakePeaceWithAllMinors)
strOutBuf.Format("Making peace with all possible City-States because we're doing poorly in war!");
else
strOutBuf.Format("This City-State is no longer our CS war target!");
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
}
}
}
}
}
// STEP 3: Determine whether we're willing to make peace with any majors
vector<PlayerTypes> vMakePeacePlayers;
bool bInTerribleShape = false;
bool bUABonusesFromCityConquest = false;
if (!bCriticalState)
{
bInTerribleShape = GetPlayer()->IsInTerribleShapeForWar();
CvPlayerTraits* pTraits = GetPlayer()->GetPlayerTraits();
if (!bInTerribleShape)
{
// Assyria, Japan, Indonesia, The Aztecs, Nuclear Gandhi, and anyone who keeps conquered buildings will prolong wars longer if they are winning and the enemy's cities are in danger from them.
bUABonusesFromCityConquest = pTraits->IsTechFromCityConquer();
bUABonusesFromCityConquest |= pTraits->GetCityConquestGWAM() > 0;
bUABonusesFromCityConquest |= pTraits->GetUniqueLuxuryQuantity() > 0;
bUABonusesFromCityConquest |= pTraits->GetGoldenAgeFromVictory() > 0;
bUABonusesFromCityConquest |= pTraits->IsKeepConqueredBuildings() || GetPlayer()->IsKeepConqueredBuildings();
bUABonusesFromCityConquest |= pTraits->GetCultureBonusModifierConquest() > 0;
bUABonusesFromCityConquest |= pTraits->GetProductionBonusModifierConquest() > 0;
bUABonusesFromCityConquest |= pTraits->IsFreeGreatWorkOnConquest();
bUABonusesFromCityConquest |= pTraits->IsConquestOfTheWorld();
bUABonusesFromCityConquest |= IsNuclearGandhi();
}
}
for (std::vector<PlayerTypes>::iterator it = vValidPlayers.begin(); it != vValidPlayers.end(); it++)
{
if (bCriticalState)
{
vMakePeacePlayers.push_back(*it);
if (bLog)
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString otherPlayerName;
CvString strLogName;
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
otherPlayerName = GET_PLAYER(*it).getCivilizationShortDescription();
strBaseString += playerName + " VS. " + otherPlayerName;
strOutBuf.Format("Making peace with all possible civilizations because we're in a critical state!");
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
continue;
}
// Let's see how much danger each of us are currently in ...
int iWarDuration = min(GetPlayer()->GetPlayerNumTurnsAtWar(*it), GetPlayer()->GetPlayerNumTurnsSinceCityCapture(*it));
int iWarScore = GetWarScore(*it);
int iOurDanger = 0;
int iTheirDanger = 0;
bool bSeriousDangerUs = false;
bool bSeriousDangerThem = false;
ReligionTypes eMyReligion = GetPlayer()->GetReligions()->GetOwnedReligion();
ReligionTypes eTheirReligion = GET_PLAYER(*it).GetReligions()->GetOwnedReligion();
vector<PlayerTypes> vOurWarAllies = GetOffensiveWarAllies(*it, /*bIncludeMinors*/ true, /*bReverseMode*/ false);
vector<PlayerTypes> vTheirWarAllies = GetOffensiveWarAllies(*it, /*bIncludeMinors*/ true, /*bReverseMode*/ true);
vOurWarAllies.push_back(GetID());
vTheirWarAllies.push_back(*it);
vector<PlayerTypes> vMyTeam = GET_TEAM(GetTeam()).getPlayers();
vector<int> vEnemyCitiesEndangered;
vector<int> vEnemyCitiesEndangeredByUs;
for (CvCity* pLoopCity = m_pPlayer->firstCity(&iLoop); pLoopCity != NULL; pLoopCity = m_pPlayer->nextCity(&iLoop))
{
if (pLoopCity->IsInDangerFromPlayers(vTheirWarAllies)) // Only care if we're in danger from them!
{
int iDangerMod = 1;
//look at the tactical map (is it up to date?)
CvTacticalDominanceZone* pLandZone = m_pPlayer->GetTacticalAI()->GetTacticalAnalysisMap()->GetZoneByCity(pLoopCity,false);
CvTacticalDominanceZone* pWaterZone = m_pPlayer->GetTacticalAI()->GetTacticalAnalysisMap()->GetZoneByCity(pLoopCity,true);
if (pLandZone && pLandZone->GetOverallDominanceFlag()==TACTICAL_DOMINANCE_ENEMY)
iDangerMod++;
if (pWaterZone && pWaterZone->GetOverallDominanceFlag()==TACTICAL_DOMINANCE_ENEMY)
iDangerMod++;
if (pLoopCity->isUnderSiege())
{
if (pLoopCity->isInDangerOfFalling(true))
iDangerMod += 3;
else
iDangerMod++;
if (pLoopCity->isInDangerOfFalling(true) || pLoopCity->getDamage() >= (pLoopCity->GetMaxHitPoints()/2))
{
if (pLoopCity->getOriginalOwner() == GetID())
{
bSeriousDangerUs = true;
}
else if (pLoopCity->isCapital() || pLoopCity->IsOriginalMajorCapital() || (eMyReligion != NO_RELIGION && pLoopCity->GetCityReligions()->IsHolyCityForReligion(eMyReligion)) || (eTheirReligion != NO_RELIGION && pLoopCity->GetCityReligions()->IsHolyCityForReligion(eTheirReligion))
|| pLoopCity->HasAnyWonder() || pLoopCity->getNumNationalWonders() > 0)
{
bSeriousDangerUs = true;
}
}
}
if (iDangerMod > 1 && pLoopCity->IsInDanger(*it) && GetPlayer()->GetMilitaryAI()->IsExposedToEnemy(pLoopCity, *it))
{
iDangerMod++;
}
if (pLoopCity->isCapital())
iDangerMod *= 3;
else if (pLoopCity->IsOriginalMajorCapital() || (eMyReligion != NO_RELIGION && pLoopCity->GetCityReligions()->IsHolyCityForReligion(eMyReligion)) || (eTheirReligion != NO_RELIGION && pLoopCity->GetCityReligions()->IsHolyCityForReligion(eTheirReligion)) || pLoopCity->HasAnyWonder())
iDangerMod *= 2;
else if (pLoopCity->GetCityReligions()->IsHolyCityAnyReligion() || pLoopCity->getNumNationalWonders() > 0)
iDangerMod++;
iOurDanger += iDangerMod;
}
}
for (CvCity* pLoopCity = GET_PLAYER(*it).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER(*it).nextCity(&iLoop))
{
if (pLoopCity->IsInDangerFromPlayers(vOurWarAllies)) // Only care if they're in danger from us!
{
int iDangerMod = 1;
//look at the tactical map (is it up to date?)
CvTacticalDominanceZone* pLandZone = GET_PLAYER(*it).GetTacticalAI()->GetTacticalAnalysisMap()->GetZoneByCity(pLoopCity,false);
CvTacticalDominanceZone* pWaterZone = GET_PLAYER(*it).GetTacticalAI()->GetTacticalAnalysisMap()->GetZoneByCity(pLoopCity,true);
if (pLandZone && pLandZone->GetOverallDominanceFlag()==TACTICAL_DOMINANCE_ENEMY)
iDangerMod++;
if (pWaterZone && pWaterZone->GetOverallDominanceFlag()==TACTICAL_DOMINANCE_ENEMY)
iDangerMod++;
if (pLoopCity->isUnderSiege())
{
if (pLoopCity->isInDangerOfFalling(true))
iDangerMod += 3;
else
iDangerMod++;
if (pLoopCity->isInDangerOfFalling(true) || pLoopCity->getDamage() >= (pLoopCity->GetMaxHitPoints()/2))
{
bSeriousDangerThem = true;
}
}
if (iDangerMod > 1)
{
vEnemyCitiesEndangered.push_back(pLoopCity->GetID());
if (pLoopCity->IsInDangerFromPlayers(vMyTeam))
{
vEnemyCitiesEndangeredByUs.push_back(pLoopCity->GetID());
if (pLoopCity->IsInDanger(GetID()) && GetPlayer()->GetMilitaryAI()->IsPreferredAttackTarget(pLoopCity))
{
iDangerMod++;
}
}
}
if (pLoopCity->isCapital())
iDangerMod *= 3;
else if (pLoopCity->IsOriginalMajorCapital() || (eMyReligion != NO_RELIGION && pLoopCity->GetCityReligions()->IsHolyCityForReligion(eMyReligion)) || (eTheirReligion != NO_RELIGION && pLoopCity->GetCityReligions()->IsHolyCityForReligion(eTheirReligion)) || pLoopCity->HasAnyWonder())
iDangerMod *= 2;
else if (pLoopCity->GetCityReligions()->IsHolyCityAnyReligion())
iDangerMod++;
iTheirDanger += iDangerMod;
}
}
bool bCapturedKeyCity = IsCapitalCapturedBy(*it, true, true) || IsHolyCityCapturedBy(*it, true, true);
bool bPhonyWar = IsPhonyWar(*it);
bool bCapturedAnyCityFromUs = bCapturedKeyCity;
bool bCapturedAnyCityWeWantToLiberate = false;
bool bReadyForVassalage = iWarScore >= 75 && !bCapturedKeyCity && !IsEndgameAggressiveTo(*it) && GET_TEAM(GET_PLAYER(*it).getTeam()).canBecomeVassal(GetTeam()) && !IsUntrustworthy(*it);
if (!bInTerribleShape)
{
// Let's check if they have any of our cities. If they do, no vassalage!
for (CvCity* pLoopCity = GET_PLAYER(*it).firstCity(&iLoop); pLoopCity != NULL; pLoopCity = GET_PLAYER(*it).nextCity(&iLoop))
{
PlayerTypes eOriginalOwner = pLoopCity->getOriginalOwner();
if (GET_PLAYER(eOriginalOwner).getTeam() == GET_PLAYER(*it).getTeam())
continue;
CvPlot* pCityPlot = pLoopCity->plot();
if (!pCityPlot)
continue;
bool bEndangeredPeriod = std::find(vEnemyCitiesEndangered.begin(), vEnemyCitiesEndangered.end(), pLoopCity->GetID()) != vEnemyCitiesEndangered.end();
bool bEndangeredByOurTeam = bEndangeredPeriod && std::find(vEnemyCitiesEndangeredByUs.begin(), vEnemyCitiesEndangeredByUs.end(), pLoopCity->GetID()) != vEnemyCitiesEndangeredByUs.end();
// Ignore cities that are too far away from us (unless endangered).
if (GetPlayer()->GetCityDistanceInPlots(pCityPlot) > 12)
{
if (!bEndangeredByOurTeam && (!bEndangeredPeriod || GET_PLAYER(eOriginalOwner).getTeam() != GetTeam()))
continue;
}
if (GET_PLAYER(eOriginalOwner).getTeam() == GetTeam())
{
bReadyForVassalage = false;
bCapturedAnyCityFromUs = true;
}
// Is this a city that we want to liberate?
else if (IsTryingToLiberate(pLoopCity))
{
bReadyForVassalage = false;
bCapturedAnyCityWeWantToLiberate = true;
}
if (bCapturedAnyCityFromUs && bCapturedAnyCityWeWantToLiberate)
break;
// Is this a valuable city endangered by our team?
// Alternatively, if we're nasty/hateful or get bonuses from conquering cities, we're rarely willing to vassalize if ANY city is vulnerable.
if (bEndangeredByOurTeam && bReadyForVassalage)
{
bool bProceed = bUABonusesFromCityConquest || IsBackstabber() || GetMeanness() >= 8 || GetDiploBalance() <= 2 || GetCivOpinion(*it) == CIV_OPINION_UNFORGIVABLE;
bProceed |= pLoopCity->IsOriginalMajorCapital() || pLoopCity->GetCityReligions()->IsHolyCityAnyReligion() || pLoopCity->HasAnyWonder();
if (bProceed)
{
if (GetPlayer()->GetMilitaryAI()->IsPreferredAttackTarget(pLoopCity))
{
bReadyForVassalage = false;
}
else if (pLoopCity->isUnderSiege())
{
bReadyForVassalage = false;
}
else
{
//look at the tactical map (is it up to date?)
CvTacticalDominanceZone* pLandZone = GET_PLAYER(*it).GetTacticalAI()->GetTacticalAnalysisMap()->GetZoneByCity(pLoopCity,false);
CvTacticalDominanceZone* pWaterZone = GET_PLAYER(*it).GetTacticalAI()->GetTacticalAnalysisMap()->GetZoneByCity(pLoopCity,true);
if (pLandZone && pLandZone->GetOverallDominanceFlag()==TACTICAL_DOMINANCE_ENEMY)
{
bReadyForVassalage = false;
}
else if (pWaterZone && pWaterZone->GetOverallDominanceFlag()==TACTICAL_DOMINANCE_ENEMY)
{
bReadyForVassalage = false;
}
}
}
}
}
}
// No peace if they're in danger of losing a city and we're not.
if (bSeriousDangerThem && !bSeriousDangerUs)
{
if (iWarDuration < 40) // Failsafe in case of a problem somewhere - limit the amount of time peace is impossible for
{
SetTreatyWillingToOffer(*it, NO_PEACE_TREATY_TYPE);
SetTreatyWillingToAccept(*it, NO_PEACE_TREATY_TYPE);
if (bLog)
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString otherPlayerName;
CvString strLogName;
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
otherPlayerName = GET_PLAYER(*it).getCivilizationShortDescription();
strBaseString += playerName + " VS. " + otherPlayerName;
strOutBuf.Format("No peace! They're in serious danger of losing a city and we're not!");
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
continue;
}
}
// If we're in serious danger, let's offer peace.
else if ((iWarScore <= -90 && iOurDanger > 0) || (iWarScore <= -50 && (bInTerribleShape || bSeriousDangerUs)))
{
vMakePeacePlayers.push_back(*it);
if (bLog)
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString otherPlayerName;
CvString strLogName;
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
otherPlayerName = GET_PLAYER(*it).getCivilizationShortDescription();
strBaseString += playerName + " VS. " + otherPlayerName;
strOutBuf.Format("Automatic peace offer: We're badly losing this war!");
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
continue;
}
// Let's offer peace if we're in danger of losing a city we originally founded (or a really important city) and they're not at risk of losing a city
else if (bSeriousDangerUs && !bSeriousDangerThem)
{
vMakePeacePlayers.push_back(*it);
if (bLog)
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString otherPlayerName;
CvString strLogName;
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
otherPlayerName = GET_PLAYER(*it).getCivilizationShortDescription();
strBaseString += playerName + " VS. " + otherPlayerName;
strOutBuf.Format("Automatic peace offer: We're in serious danger of losing an important city and they're not in serious danger!");
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
continue;
}
// Do not make peace if it would block us from achieving a Domination Victory we're going for (possible with DiploAIOptions.sql)
if (!bReadyForVassalage && GC.getGame().WouldMakingPeacePreventDominationVictory(GetID(), *it))
{
if (IsGoingForWorldConquest() || IsCloseToWorldConquest())
{
SetTreatyWillingToOffer(*it, NO_PEACE_TREATY_TYPE);
SetTreatyWillingToAccept(*it, NO_PEACE_TREATY_TYPE);
if (bLog)
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString otherPlayerName;
CvString strLogName;
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
otherPlayerName = GET_PLAYER(*it).getCivilizationShortDescription();
strBaseString += playerName + " VS. " + otherPlayerName;
strOutBuf.Format("No peace! We'll lose our shot at Domination Victory if we make peace!");
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
continue;
}
}
// If this is a phony war and they're not in danger, let's offer peace.
if (bPhonyWar && iTheirDanger == 0)
{
vMakePeacePlayers.push_back(*it);
if (bLog)
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString otherPlayerName;
CvString strLogName;
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
otherPlayerName = GET_PLAYER(*it).getCivilizationShortDescription();
strBaseString += playerName + " VS. " + otherPlayerName;
strOutBuf.Format("Automatic peace offer: This is a phony war and none of their cities are in danger!");
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
continue;
}
// Test war progress score: if sufficiently negative, AI will agree to make white peace (at minimum)
// But ignore this if opponent's cities are endangered, unless the war has gone on for too long or we're in serious danger
if (vEnemyCitiesEndangered.size() == 0 || bSeriousDangerUs || bInTerribleShape || iWarDuration >= 40)
{
int iWarProgress = GetWarProgressScore(*it);
// Adjust for unhappiness/war weariness
if (MOD_BALANCE_VP)
{
iWarProgress -= GetPlayer()->GetUnhappinessFromWarWeariness();
int iHappiness = GetPlayer()->GetExcessHappiness();
if (iHappiness < 50)
{
iWarProgress += (50 - iHappiness) * /*-2*/ GD_INT_GET(WAR_PROGRESS_PER_UNHAPPY);
}
}
else if (GetPlayer()->GetExcessHappiness() < 0)
iWarProgress -= GetPlayer()->GetExcessHappiness() * /*-4*/ GD_INT_GET(WAR_PROGRESS_PER_UNHAPPY);
// Adjust for strategic resource shortages
int iStrategicDeficit = 0;
int iMaxStrategicDeficit = -50;
for (int iResourceLoop = 0; iResourceLoop < GC.getNumResourceInfos(); iResourceLoop++)
{
ResourceTypes eResource = (ResourceTypes) iResourceLoop;
if (eResource == NO_RESOURCE)
continue;
const CvResourceInfo* pkResourceInfo = GC.getResourceInfo(eResource);
if (pkResourceInfo == NULL)
continue;
if (pkResourceInfo->getResourceUsage() != RESOURCEUSAGE_STRATEGIC)
continue;
if (GetPlayer()->getResourceShortageValue(eResource) > 0)
iStrategicDeficit += GetPlayer()->getResourceShortageValue(eResource) * /*-5*/ GD_INT_GET(WAR_PROGRESS_PER_STRATEGIC_DEFICIT);
if (iStrategicDeficit <= iMaxStrategicDeficit)
break;
}
iWarProgress += max(iStrategicDeficit, iMaxStrategicDeficit);
if (iWarProgress <= 0)
{
vMakePeacePlayers.push_back(*it);
if (bLog)
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString otherPlayerName;
CvString strLogName;
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
otherPlayerName = GET_PLAYER(*it).getCivilizationShortDescription();
strBaseString += playerName + " VS. " + otherPlayerName;
strOutBuf.Format("Automatic peace offer: We're making no progress in this war!");
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
continue;
}
}
// If not ready for vassalage with a neighbor and the war is nearly won, let's persevere.
if (!bReadyForVassalage && GetWarState(*it) == WAR_STATE_NEARLY_WON && GetPlayer()->GetProximityToPlayer(*it) >= PLAYER_PROXIMITY_CLOSE && iWarDuration < 30 && !bInTerribleShape)
{
SetTreatyWillingToOffer(*it, NO_PEACE_TREATY_TYPE);
SetTreatyWillingToAccept(*it, NO_PEACE_TREATY_TYPE);
if (bLog)
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString otherPlayerName;
CvString strLogName;
CvString strNoVassalReason;
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
otherPlayerName = GET_PLAYER(*it).getCivilizationShortDescription();
strBaseString += playerName + " VS. " + otherPlayerName;
strOutBuf.Format("No peace! We've almost won this war and ");
if (!GET_TEAM(GET_PLAYER(*it).getTeam()).canBecomeVassal(GetTeam()))
strNoVassalReason.Format("we're not able to make them a vassal!");
else if (bCapturedAnyCityFromUs)
strNoVassalReason.Format("they have a nearby city they captured from us!");
else if (IsEndgameAggressiveTo(*it))
strNoVassalReason.Format("they're in danger of winning the game!");
else if (bCapturedAnyCityWeWantToLiberate)
strNoVassalReason.Format("they have a nearby city we want to liberate!");
else if (iWarScore < 75)
strNoVassalReason.Format("war score is less than 75!");
else if (IsUntrustworthy(*it))
strNoVassalReason.Format("we don't trust them to be an obedient vassal!");
else
strNoVassalReason.Format("we still want to capture cities from them!");
strOutBuf += strNoVassalReason;
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
continue;
}
bool bWarWeary = false;
if (MOD_BALANCE_VP)
{
int iPercentOfPop = GetPlayer()->GetUnhappinessFromWarWeariness() * 100 / max(1, GetPlayer()->getTotalPopulation());
if (iPercentOfPop >= 20)
bWarWeary = true;
}
// Okay, so no automatic peace. But should we consider peace at all?
bool bConsiderPeace = bReadyForVassalage || bPhonyWar || bSeriousDangerUs || bInTerribleShape || bWarWeary;
bConsiderPeace |= GetCivApproach(*it) > CIV_APPROACH_HOSTILE;
bConsiderPeace |= GetWarState(*it) <= WAR_STATE_TROUBLED;
bConsiderPeace |= iWarDuration >= 30;
// Not worth making peace with. Proceed.
if (!bConsiderPeace)
{
SetTreatyWillingToOffer(*it, NO_PEACE_TREATY_TYPE);
SetTreatyWillingToAccept(*it, NO_PEACE_TREATY_TYPE);
if (bLog)
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString otherPlayerName;
CvString strLogName;
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
otherPlayerName = GET_PLAYER(*it).getCivilizationShortDescription();
strBaseString += playerName + " VS. " + otherPlayerName;
strOutBuf.Format("No peace! We don't have a good enough reason to consider peace yet!");
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
continue;
}
// If we've gotten this far, we need to at least consider the merits of peace.
int iPeaceScore = 0;
// War score is of course a significant factor
// If we get a tourism bonus from high warscore or special conquest bonuses, let's not end early!
bool bProlong = GetPlayer()->GetPositiveWarScoreTourismMod() > 0 && GET_PLAYER(GetHighestWarscorePlayer()).getTeam() == GET_PLAYER(*it).getTeam();
if (iWarScore <= 0)
{
iPeaceScore += iWarScore / -2;
bProlong = false;
}
else if (bInTerribleShape || bSeriousDangerUs)
{
iPeaceScore += iWarScore / 3;
bProlong = false;
}
else if (vEnemyCitiesEndangeredByUs.size() > 0)
{
bProlong |= bUABonusesFromCityConquest;
if (!bProlong)
iPeaceScore += iWarScore / 5;
}
else if (!bProlong)
{
iPeaceScore += iWarScore / 5;
}
int iMinimumWarDuration = max(0, /*10*/ GD_INT_GET(WAR_MAJOR_MINIMUM_TURNS));
int iTooLongWarThreshold = max(15, iMinimumWarDuration);
if (bProlong)
{
iTooLongWarThreshold *= 2;
iMinimumWarDuration *= 2;
}
int iDurationPenalty = iWarDuration - iMinimumWarDuration;
// Lack of progress in war increases desire for peace (moreso if far away).
if (iWarDuration > iTooLongWarThreshold)
{
switch (GetPlayer()->GetProximityToPlayer(*it))
{
case PLAYER_PROXIMITY_NEIGHBORS:
iPeaceScore += iDurationPenalty;
break;
case PLAYER_PROXIMITY_CLOSE:
iPeaceScore += (iDurationPenalty * 150) / 100;
break;
case PLAYER_PROXIMITY_FAR:
iPeaceScore += iDurationPenalty * 2;
break;
case NO_PLAYER_PROXIMITY:
case PLAYER_PROXIMITY_DISTANT:
iPeaceScore += iDurationPenalty * 3;
break;
}
}
// War weariness
if (MOD_BALANCE_VP)
{
int iWarWeariness = GetPlayer()->GetUnhappinessFromWarWeariness();
iPeaceScore += iWarWeariness / 4;
if (iWarWeariness > 0 && (bWarWeary || GetPlayer()->IsEmpireUnhappy()))
{
if (GetPlayer()->IsEmpireVeryUnhappy() || (GetPlayer()->IsEmpireUnhappy() && bWarWeary))
iPeaceScore += iWarWeariness / 2;
else
iPeaceScore += iWarWeariness / 3;
}
}
// Danger considerations
int iOurMultiplier = 1;
int iTheirMultiplier = 1;
if (!bInTerribleShape || GetWarState(*it) == WAR_STATE_NEARLY_WON)
{
if (GetMilitaryStrengthComparedToUs(*it) < STRENGTH_AVERAGE)
{
iOurMultiplier++;
}
if (IsEasyTarget(*it))
{
iOurMultiplier++;
}
if (GetPlayer()->GetMilitaryAI()->HavePreferredAttackTarget(*it))
{
iOurMultiplier++;
}
if (GetWarState(*it) == WAR_STATE_NEARLY_WON || GET_PLAYER(*it).IsInTerribleShapeForWar())
{
iOurMultiplier++;
}
}
if (!GET_PLAYER(*it).IsInTerribleShapeForWar())
{
if (GetMilitaryStrengthComparedToUs(*it) > STRENGTH_AVERAGE)
{
iTheirMultiplier++;
}
if (bInTerribleShape)
{
iTheirMultiplier++;
}
if (GET_PLAYER(*it).GetDiplomacyAI()->IsEasyTarget(GetID()))
{
iTheirMultiplier++;
}
if (GetPlayer()->GetMilitaryAI()->IsExposedToEnemy(NULL, *it))
{
iTheirMultiplier++;
}
}
// They're in danger and we're not? Let's hold out longer!
if (iOurDanger == 0 && iTheirDanger > 0)
{
if (iWarDuration < 20 || iOurMultiplier > 2 || bSeriousDangerThem)
{
iPeaceScore -= (5 * iTheirDanger * iOurMultiplier);
}
else
{
iPeaceScore -= (2 * iTheirDanger * iOurMultiplier);
}
}
// We're in danger and they're not? Let's hold out less!
else if (iOurDanger > 0 && iTheirDanger == 0)
{
if (iWarScore <= GetWarscoreThresholdNegative()/2 || iTheirMultiplier > 2 || bSeriousDangerUs)
{
iPeaceScore += (5 * iOurDanger * iTheirMultiplier);
}
else
{
iPeaceScore += (2 * iOurDanger * iTheirMultiplier);
}
}
// We're both in danger - how much?
else
{
iPeaceScore -= (2 * iTheirDanger * iOurMultiplier);
iPeaceScore += (2 * iOurDanger * iTheirMultiplier);
}
// Also consider the overall war state
bool bLosingOverall = GetStateAllWars() == STATE_ALL_WARS_LOSING;
switch (GetWarState(*it))
{
case NO_WAR_STATE_TYPE:
UNREACHABLE(); // Being here would indicate we aren't at a war with this player.
case WAR_STATE_NEARLY_WON:
iPeaceScore -= bReadyForVassalage ? 0 : 10 * iOurMultiplier;
break;
case WAR_STATE_OFFENSIVE:
iPeaceScore -= 5 * iOurMultiplier;
break;
case WAR_STATE_CALM:
iPeaceScore -= bLosingOverall ? -5 * iTheirMultiplier : 2 * iOurMultiplier;
break;
case WAR_STATE_STALEMATE:
case WAR_STATE_TROUBLED:
iPeaceScore += bLosingOverall ? 5 * iTheirMultiplier : 2 * iTheirMultiplier;
break;
case WAR_STATE_DEFENSIVE:
case WAR_STATE_NEARLY_DEFEATED:
iPeaceScore += bLosingOverall ? 10 * iTheirMultiplier : 5 * iTheirMultiplier;
break;
}
// Hold out for longer if we have military support from nearby allies
int iAlliesMod = 0;
if (!bInTerribleShape)
{
for (std::vector<PlayerTypes>::iterator iter = vOurWarAllies.begin(); iter != vOurWarAllies.end(); iter++)
{
if (GET_PLAYER(*iter).GetID() == GetID())
continue;
if (!GET_PLAYER(*iter).isMajorCiv())
continue;
if (GET_PLAYER(*iter).IsInTerribleShapeForWar())
continue;
if (GET_PLAYER(*iter).GetProximityToPlayer(GetID()) < PLAYER_PROXIMITY_CLOSE)
continue;
if (GET_PLAYER(*iter).GetProximityToPlayer(*it) < PLAYER_PROXIMITY_CLOSE)
continue;
if (GET_PLAYER(*iter).GetDiplomacyAI()->GetWarState(*it) <= WAR_STATE_TROUBLED)
continue;
switch (GET_PLAYER(*iter).GetDiplomacyAI()->GetMilitaryStrengthComparedToUs(*it))
{
case NO_STRENGTH_VALUE:
UNREACHABLE(); // Strengths are supposed to have been evaluated by this point.
case STRENGTH_IMMENSE:
case STRENGTH_POWERFUL:
case STRENGTH_STRONG:
break;
case STRENGTH_AVERAGE:
case STRENGTH_POOR:
iAlliesMod -= 5;
break;
case STRENGTH_WEAK:
iAlliesMod -= 10;
break;
case STRENGTH_PATHETIC:
iAlliesMod -= 15;
break;
}
}
// Ongoing coop war? Minimum -10 modifier.
if (iAlliesMod > -10 && GetGlobalCoopWarAgainstState(*it) == COOP_WAR_STATE_ONGOING)
{
iAlliesMod = -10;
}
}
if (iWarScore <= GetWarscoreThresholdNegative() && GetWarState(*it) < WAR_STATE_OFFENSIVE)
{
iAlliesMod /= 2;
}
iPeaceScore += iAlliesMod;
// If we want to conquer them, let's hold out longer.
if (!bReadyForVassalage && IsWantsToConquer(*it))
{
iPeaceScore -= 10;
}
// If they captured one of our key cities, let's hold out for significantly longer.
if (bCapturedKeyCity)
{
iPeaceScore -= 15;
}
// If they're about to win the game, let's hold out for a lot longer.
if (IsEndgameAggressiveTo(*it))
{
iPeaceScore -= 20;
}
bool bAztecException = false;
if (iPeaceScore > 0)
{
// If we're going for world conquest, we want to fight our wars until we get their capital or can vassalize them
// We should also be more reluctant to make peace if they've captured cities from us, or cities that we want to liberate
// However, do not factor this in when losing
if (!bInTerribleShape)
{
if (iWarScore > 0 || (iWarScore > GetWarscoreThresholdNegative()/2 && GetRawTargetValue(*it) >= TARGET_VALUE_AVERAGE && GetWarState(*it) > WAR_STATE_TROUBLED))
{
if (iWarScore < WARSCORE_THRESHOLD_POSITIVE && GetPlayer()->GetPlayerTraits()->GetGoldenAgeFromVictory() > 0)
{
bAztecException = true;
iPeaceScore /= 2;
}
else if (bCapturedAnyCityFromUs)
{
if (iWarScore > (iWarDuration / 2))
iPeaceScore /= 2;
else
{
iPeaceScore *= 75;
iPeaceScore /= 100;
}
}
else if (bCapturedAnyCityWeWantToLiberate)
{
if ((bDiplomatic && iWarScore > 0) || iWarScore > iWarDuration)
{
if (bDiplomatic && iWarScore > iWarDuration)
{
iPeaceScore /= 2;
}
else
{
iPeaceScore *= 75;
iPeaceScore /= 100;
}
}
}
else if (bWorldConquest)
{
if (bReadyForVassalage || (GET_PLAYER(*it).GetCapitalConqueror() != NO_PLAYER && GET_PLAYER(*it).GetNumCapitalCities() <= 0))
{
// If they're economically weak or a bad target, boost peace willingness
if (GetEconomicStrengthComparedToUs(*it) <= STRENGTH_WEAK || GetRawTargetValue(*it) <= TARGET_VALUE_BAD)
iPeaceScore *= 2;
else if (GetTargetValue(*it) == TARGET_VALUE_DIFFICULT)
{
iPeaceScore *= 3;
iPeaceScore /= 2;
}
}
else if (GET_PLAYER(*it).GetCapitalConqueror() == NO_PLAYER || GET_PLAYER(*it).GetNumCapitalCities() > 0)
{
iPeaceScore *= 75;
iPeaceScore /= 100;
}
}
}
}
// Overall state is bad and we're doing great in this war, or at least haven't lost any cities? Let's peace out!
else if (!bCapturedAnyCityFromUs || bReadyForVassalage || GetWarState(*it) == WAR_STATE_NEARLY_WON)
{
iPeaceScore *= 2;
// Both? Even more likely to make peace!
if (!bCapturedAnyCityFromUs && (bReadyForVassalage || GetWarState(*it) == WAR_STATE_NEARLY_WON))
iPeaceScore *= 2;
}
// Modify based on leader flavors
// High Meanness leaders will fight to the bitter end when losing, high Diplo Balance leaders like to return to status quo when winning
if (iWarScore > 0)
{
iPeaceScore = AdjustConditionalModifier(iPeaceScore, GetDiploBalance());
}
else if (iWarScore < 0)
{
iPeaceScore = AdjustConditionalModifier(iPeaceScore, GetMeanness(), true);
}
}
// Must be high enough to return a true desire for peace
int iWarCount = GetPlayer()->CountNumDangerousMajorsAtWarWith(true, false) - 1;
if (iWarCount <= 0)
iWarCount = 0;
bool bWantedPeaceLastTime = IsWantsPeaceWithPlayer(*it) && !bAztecException; // if we were willing to make peace last update, add some leeway here to prevent flip-flopping
int iThreshold = bWantedPeaceLastTime ? /*17*/ GD_INT_GET(REQUEST_PEACE_LEEWAY_THRESHOLD) : /*20*/ GD_INT_GET(REQUEST_PEACE_TURN_THRESHOLD);
int iThresholdReductionPerOtherWar = bWantedPeaceLastTime ? /*3*/ GD_INT_GET(REQUEST_PEACE_LEEWAY_THRESHOLD_REDUCTION_PER_WAR) : /*2*/ GD_INT_GET(REQUEST_PEACE_THRESHOLD_REDUCTION_PER_WAR);
iThreshold = max(iThreshold - max(iThresholdReductionPerOtherWar * iWarCount, 0), 1);
if (iPeaceScore >= iThreshold)
vMakePeacePlayers.push_back(*it);
else
{
SetTreatyWillingToOffer(*it, NO_PEACE_TREATY_TYPE);
SetTreatyWillingToAccept(*it, NO_PEACE_TREATY_TYPE);
}
if (bLog)
{
CvString strOutBuf;
CvString strBaseString;
CvString playerName;
CvString otherPlayerName;
CvString strLogName;
// Find the name of this civ and city
playerName = m_pPlayer->getCivilizationShortDescription();
// Open the log file
if (GC.getPlayerAndCityAILogSplit())
{
strLogName = "DiplomacyAI_Peace_Log" + playerName + ".csv";
}
else
{
strLogName = "DiplomacyAI_Peace_Log.csv";
}
FILogFile* pLog = NULL;
pLog = LOGFILEMGR.GetLog(strLogName, FILogFile::kDontTimeStamp);
// Get the leading info for this line
strBaseString.Format("%03d, ", GC.getGame().getElapsedGameTurns());
otherPlayerName = GET_PLAYER(*it).getCivilizationShortDescription();
strBaseString += playerName + " VS. " + otherPlayerName;
if (iPeaceScore >= iThreshold)
strOutBuf.Format("Willing to make peace, Score for Peace: %d, Required Score: %d", iPeaceScore, iThreshold);
else
strOutBuf.Format("Not willing to make peace, Score for Peace: %d, Required Score: %d", iPeaceScore, iThreshold);
strBaseString += strOutBuf;
pLog->Msg(strBaseString);
}
}
// STEP 4: Decide WHAT we're willing to accept/offer in exchange for peace.
for (std::vector<PlayerTypes>::iterator it = vMakePeacePlayers.begin(); it != vMakePeacePlayers.end(); it++)
{
PeaceTreatyTypes eTreatyWillingToOffer = PEACE_TREATY_WHITE_PEACE;
PeaceTreatyTypes eTreatyWillingToAccept = PEACE_TREATY_WHITE_PEACE;
int iWillingToOfferScore = 0;
int iWillingToAcceptScore = 0;
// What we're willing to offer/accept in a peace treaty.
int iWarScore = GetWarScore(*it);
// Negative Warscore? Offer more.
if (iWarScore < 0)
{
iWillingToOfferScore -= iWarScore;
}
// Positive Warscore? Accept more.
else
{
iWillingToAcceptScore += iWarScore;
}
// War Weary? We're more willing to make peace.
if (MOD_BALANCE_VP)
{
if (iWarScore <= 0)
{
iWillingToOfferScore += GetPlayer()->GetUnhappinessFromWarWeariness();
}
else
{
iWillingToAcceptScore -= GetPlayer()->GetUnhappinessFromWarWeariness();
}
}
// Do the final assessment
if (iWillingToOfferScore >= /*100*/ GD_INT_GET(PEACE_WILLINGNESS_OFFER_THRESHOLD_UN_SURRENDER))
eTreatyWillingToOffer = PEACE_TREATY_UNCONDITIONAL_SURRENDER;
else if (iWillingToOfferScore >= /*90*/ GD_INT_GET(PEACE_WILLINGNESS_OFFER_THRESHOLD_CAPITULATION))
eTreatyWillingToOffer = PEACE_TREATY_CAPITULATION;
else if (iWillingToOfferScore >= /*80*/ GD_INT_GET(PEACE_WILLINGNESS_OFFER_THRESHOLD_CESSION))
eTreatyWillingToOffer = PEACE_TREATY_CESSION;
else if (iWillingToOfferScore >= /*70*/ GD_INT_GET(PEACE_WILLINGNESS_OFFER_THRESHOLD_SURRENDER))
eTreatyWillingToOffer = PEACE_TREATY_SURRENDER;
else if (iWillingToOfferScore >= /*60*/ GD_INT_GET(PEACE_WILLINGNESS_OFFER_THRESHOLD_SUBMISSION))
eTreatyWillingToOffer = PEACE_TREATY_SUBMISSION;
else if (iWillingToOfferScore >= /*40*/ GD_INT_GET(PEACE_WILLINGNESS_OFFER_THRESHOLD_BACKDOWN))
eTreatyWillingToOffer = PEACE_TREATY_BACKDOWN;
else if (iWillingToOfferScore >= /*30*/ GD_INT_GET(PEACE_WILLINGNESS_OFFER_THRESHOLD_SETTLEMENT))
eTreatyWillingToOffer = PEACE_TREATY_SETTLEMENT;
else if (iWillingToOfferScore >= /*15*/ GD_INT_GET(PEACE_WILLINGNESS_OFFER_THRESHOLD_ARMISTICE))
eTreatyWillingToOffer = PEACE_TREATY_ARMISTICE;
// Do the final assessment
if (iWillingToAcceptScore >= /*100*/ GD_INT_GET(PEACE_WILLINGNESS_ACCEPT_THRESHOLD_UN_SURRENDER))
eTreatyWillingToAccept = PEACE_TREATY_UNCONDITIONAL_SURRENDER;
else if (iWillingToAcceptScore >= /*90*/ GD_INT_GET(PEACE_WILLINGNESS_ACCEPT_THRESHOLD_CAPITULATION))
eTreatyWillingToAccept = PEACE_TREATY_CAPITULATION;
else if (iWillingToAcceptScore >= /*80*/ GD_INT_GET(PEACE_WILLINGNESS_ACCEPT_THRESHOLD_CESSION))
eTreatyWillingToAccept = PEACE_TREATY_CESSION;
else if (iWillingToAcceptScore >= /*70*/ GD_INT_GET(PEACE_WILLINGNESS_ACCEPT_THRESHOLD_SURRENDER))
eTreatyWillingToAccept = PEACE_TREATY_SURRENDER;
else if (iWillingToAcceptScore >= /*60*/ GD_INT_GET(PEACE_WILLINGNESS_ACCEPT_THRESHOLD_SUBMISSION))
eTreatyWillingToAccept = PEACE_TREATY_SUBMISSION;
else if (iWillingToAcceptScore >= /*40*/ GD_INT_GET(PEACE_WILLINGNESS_ACCEPT_THRESHOLD_BACKDOWN))
eTreatyWillingToAccept = PEACE_TREATY_BACKDOWN;
else if (iWillingToAcceptScore >= /*30*/ GD_INT_GET(PEACE_WILLINGNESS_ACCEPT_THRESHOLD_SETTLEMENT))
eTreatyWillingToAccept = PEACE_TREATY_SETTLEMENT;
else if (iWillingToAcceptScore >= /*15*/ GD_INT_GET(PEACE_WILLINGNESS_ACCEPT_THRESHOLD_ARMISTICE))
eTreatyWillingToAccept = PEACE_TREATY_ARMISTICE;
// If we're in a critical state, always accept a white peace.
if (bCriticalState)
{
if (eTreatyWillingToOffer <= PEACE_TREATY_WHITE_PEACE)
eTreatyWillingToOffer = PEACE_TREATY_WHITE_PEACE;
eTreatyWillingToAccept = PEACE_TREATY_WHITE_PEACE;
}
SetTreatyWillingToOffer(*it, eTreatyWillingToOffer);
SetTreatyWillingToAccept(*it, eTreatyWillingToAccept);
}
}
No other function is expected to be changed, unless it is technicaly necessary (that is up to sponsor's judgement).
Changes (might need loop rewrite or structure rewrite of the function):
1) The AI should compute its bConsiderPeace against all players in that team. If it is false at least for one player, AI will not consider peace with that team at all.
2) The AI should compute its iPeaceScore against all players in that team (Note: Proximity is currently taken into action! AI is more likely to accept peace with distant player - will be adjusted in further steps).
3) The AI should make an aggregation (see below) of these iPeaceScore. The resulting value is then compared to the iThreshold and if (and only if) it is sufficient then AI will be willing to sign peace with any player of that team.
4) The AI should compute iWillingToOfferScore and iWillingToAcceptScore and make aggregation (see below) of these values and use resulting value for every player in the same team.
Values iPeaceScore, iWillingToOfferScore and iWillingToAcceptScore should be aggregated (all in the same manner) as a weighted average of the values for individual players in a team, considering the distance of the player from the AI as a weight. There are four possible values for distances (PLAYER_PROXIMITY_x) already used - NEIGHBORS, CLOSE, FAR or DISTANT. To make close players more important, the weights should be set as weight(NEIGHBORS) = 4, weight(CLOSE) = 3, weight(FAR) = 2, weight(DISTANT) = 1.
Example of computation of iPeaceScore:
Player A is FAR from Shaka and somehow AI Shaka computes its iPeaceScore against this player as 50. Player B is NEIGHBOR of Shaka and Shaka somehow computes iPeaceScore against this player as 5. Resulting iPeaceScore for that team is (50*2 + 5*4)/(2+4)=20
NOTE: Other parts of the computation should be changed as little as possible. While implementing this, some small problems might have to be resolved. Recursive has pointed out one:
Also NO_PLAYER_PROXIMITY should have weight of 1 if needed during computation, though I do not know why and when is this value obtained.War duration (turns since war started / city captured) is stored separately for each player, which would have effects if some players on a team died / were resurrected mid conflict, but it'd be easy enough to use the highest value out of all team members.