From 66feebae8d88bba1187015a19cfa9abdaa648126 Mon Sep 17 00:00:00 2001 From: Xander Lenstra <71121390+xlenstra@users.noreply.github.com> Date: Sun, 15 May 2022 16:56:26 +0200 Subject: [PATCH] Disabled CS buttons when at war; CS keep influence when at war with ally (#6813) * Disabled CS buttons when at war; CS keep influence when at war with ally This PR does two things: - When you are at war with a city state, you can no longer give them money, demand stuff, etc. - When at war with the ally of a city state, your influence is frozen, and after the end of the conflict set back to its original value. To implement this last thing, I had to split the getter of `influence` off of the underlying field, as otherwise when copying over the old value in `.clone()`, we would copy over -60 when at war, instead of the original value. Most of the changes in that PR are a result of that. Also resets the `simulateUntilTurns` variable after completing simulations * Updated comment --- core/src/com/unciv/logic/GameInfo.kt | 2 + .../logic/automation/NextTurnAutomation.kt | 6 +- .../logic/civilization/CityStateFunctions.kt | 10 +-- .../logic/civilization/CivilizationInfo.kt | 14 ++-- .../diplomacy/DiplomacyManager.kt | 67 ++++++++++++------- .../src/com/unciv/ui/tilegroups/CityButton.kt | 2 +- .../src/com/unciv/ui/trade/DiplomacyScreen.kt | 19 +++--- 7 files changed, 73 insertions(+), 47 deletions(-) diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index b5caf3fa8e..e7eeb57a9b 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -247,6 +247,8 @@ class GameInfo { } switchTurn() } + if (turns == UncivGame.Current.simulateUntilTurnForDebug) + UncivGame.Current.simulateUntilTurnForDebug = 0 currentTurnStartTime = System.currentTimeMillis() currentPlayer = thisPlayer.civName diff --git a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt index d44f494ba7..b291d816d1 100644 --- a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt @@ -244,7 +244,7 @@ object NextTurnAutomation { private fun tryGainInfluence(civInfo: CivilizationInfo, cityState: CivilizationInfo) { if (civInfo.gold < 250) return // save up - if (cityState.getDiplomacyManager(civInfo).influence < 20) { + if (cityState.getDiplomacyManager(civInfo).getInfluence() < 20) { cityState.receiveGoldGift(civInfo, 250) return } @@ -267,7 +267,7 @@ object NextTurnAutomation { for (cityState in civInfo.getKnownCivs() .filter { it.isCityState() && it.cityStateType == CityStateType.Cultured }) { val diploManager = cityState.getDiplomacyManager(civInfo) - if (diploManager.influence < 40) { // we want to gain influence with them + if (diploManager.getInfluence() < 40) { // we want to gain influence with them tryGainInfluence(civInfo, cityState) return } @@ -329,7 +329,7 @@ object NextTurnAutomation { } if (cityState.getAllyCiv() != null && cityState.getAllyCiv() != civInfo.civName) { // easier not to compete if a third civ has this locked down - val thirdCivInfluence = cityState.getDiplomacyManager(cityState.getAllyCiv()!!).influence.toInt() + val thirdCivInfluence = cityState.getDiplomacyManager(cityState.getAllyCiv()!!).getInfluence().toInt() value -= (thirdCivInfluence - 60) / 10 } diff --git a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt index 26013be6dd..b7c15a01f6 100644 --- a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt +++ b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt @@ -177,7 +177,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { } fun removeProtectorCiv(otherCiv: CivilizationInfo, forced: Boolean = false) { - if(!otherCivCanWithdrawProtection(otherCiv) && !forced) + if(!forced && !otherCivCanWithdrawProtection(otherCiv)) return val diplomacy = civInfo.getDiplomacyManager(otherCiv) @@ -195,7 +195,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { if (diplomacy.hasFlag(DiplomacyFlags.RecentlyWithdrewProtection)) return false // Must have at least 0 influence - if (diplomacy.influence < 0) + if (diplomacy.getInfluence() < 0) return false // can't be at war if (civInfo.isAtWarWith(otherCiv)) @@ -225,8 +225,8 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { if (!civInfo.isCityState()) return val maxInfluence = civInfo.diplomacy .filter { !it.value.otherCiv().isCityState() && !it.value.otherCiv().isDefeated() } - .maxByOrNull { it.value.influence } - if (maxInfluence != null && maxInfluence.value.influence >= 60) { + .maxByOrNull { it.value.getInfluence() } + if (maxInfluence != null && maxInfluence.value.getInfluence() >= 60) { newAllyName = maxInfluence.key } @@ -350,7 +350,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { modifiers["Very recently paid tribute"] = -300 else if (recentBullying != null) modifiers["Recently paid tribute"] = -40 - if (civInfo.getDiplomacyManager(demandingCiv).influence < -30) + if (civInfo.getDiplomacyManager(demandingCiv).getInfluence() < -30) modifiers["Influence below -30"] = -300 // Slight optimization, we don't do the expensive stuff if we have no chance of getting a >= 0 result diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index f9168bcf77..40ed36e25d 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -598,11 +598,15 @@ class CivilizationInfo { fun getEraNumber(): Int = getEra().eraNumber fun isAtWarWith(otherCiv: CivilizationInfo): Boolean { - if (otherCiv == this) return false // never at war with itself - if (otherCiv.isBarbarian() || isBarbarian()) return true - val diplomacyManager = diplomacy[otherCiv.civName] - ?: return false // not encountered yet - return diplomacyManager.diplomaticStatus == DiplomaticStatus.War + return when { + otherCiv == this -> false + otherCiv.isBarbarian() || isBarbarian() -> true + else -> { + val diplomacyManager = diplomacy[otherCiv.civName] + ?: return false // not encountered yet + return diplomacyManager.diplomaticStatus == DiplomaticStatus.War + } + } } fun isAtWar() = diplomacy.values.any { it.diplomaticStatus == DiplomaticStatus.War && !it.otherCiv().isDefeated() } diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt index 67d82d86e5..c6ddf1932c 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt @@ -115,10 +115,10 @@ class DiplomacyManager() { var diplomaticModifiers = HashMap() /** For city-states. Influence is saved in the CITY STATE -> major civ Diplomacy, NOT in the major civ -> city state diplomacy. - * Won't go below [MINIMUM_INFLUENCE]. Note this declaration leads to Major Civs getting MINIMUM_INFLUENCE serialized, but that is ignored. */ - var influence = 0f - private set - get() = if (civInfo.isAtWarWith(otherCiv())) MINIMUM_INFLUENCE else field + * Access via getInfluence() and setInfluence() unless you know what you're doing. + * Note that not using the setter skips recalculating the ally and bounds checks, + * and skipping the getter bypasses the modified value when at war */ + private var influence = 0f /** Total of each turn Science during Research Agreement */ private var totalOfScienceDuringRA = 0 @@ -168,11 +168,11 @@ class DiplomacyManager() { return otherCiv().getDiplomacyManager(civInfo).relationshipLevel() if (civInfo.isCityState()) return when { - influence >= 60 && civInfo.getAllyCiv() == otherCivName -> RelationshipLevel.Ally - influence >= 30 -> RelationshipLevel.Friend - influence <= -30 || civInfo.isAtWarWith(otherCiv()) -> RelationshipLevel.Unforgivable - influence < 30 && civInfo.getTributeWillingness(otherCiv()) > 0 -> RelationshipLevel.Afraid - influence < 0 -> RelationshipLevel.Enemy + getInfluence() <= -30 || civInfo.isAtWarWith(otherCiv()) -> RelationshipLevel.Unforgivable + getInfluence() < 0 -> RelationshipLevel.Enemy + getInfluence() < 30 && civInfo.getTributeWillingness(otherCiv()) > 0 -> RelationshipLevel.Afraid + getInfluence() >= 60 && civInfo.getAllyCiv() == otherCivName -> RelationshipLevel.Ally + getInfluence() >= 30 -> RelationshipLevel.Friend else -> RelationshipLevel.Neutral } @@ -201,8 +201,8 @@ class DiplomacyManager() { val dropPerTurn = getCityStateInfluenceDegrade() return when { dropPerTurn == 0f -> 0 - relationshipLevel() >= RelationshipLevel.Ally -> ceil((influence - 60f) / dropPerTurn).toInt() + 1 - relationshipLevel() >= RelationshipLevel.Friend -> ceil((influence - 30f) / dropPerTurn).toInt() + 1 + relationshipLevel() >= RelationshipLevel.Ally -> ceil((getInfluence() - 60f) / dropPerTurn).toInt() + 1 + relationshipLevel() >= RelationshipLevel.Friend -> ceil((getInfluence() - 30f) / dropPerTurn).toInt() + 1 else -> 0 } } @@ -230,6 +230,8 @@ class DiplomacyManager() { influence = max(amount, MINIMUM_INFLUENCE) civInfo.updateAllyCivForCityState() } + + fun getInfluence() = if (civInfo.isAtWarWith(otherCiv())) MINIMUM_INFLUENCE else influence // To be run from City-State DiplomacyManager, which holds the influence. Resting point for every major civ can be different. private fun getCityStateInfluenceRestingPoint(): Float { @@ -251,7 +253,7 @@ class DiplomacyManager() { } private fun getCityStateInfluenceDegrade(): Float { - if (influence < getCityStateInfluenceRestingPoint()) + if (getInfluence() < getCityStateInfluenceRestingPoint()) return 0f val decrement = when { @@ -279,7 +281,7 @@ class DiplomacyManager() { } private fun getCityStateInfluenceRecovery(): Float { - if (influence > getCityStateInfluenceRestingPoint()) + if (getInfluence() > getCityStateInfluenceRestingPoint()) return 0f val increment = 1f // sic: personality does not matter here @@ -424,7 +426,7 @@ class DiplomacyManager() { updateHasOpenBorders() nextTurnDiplomaticModifiers() nextTurnFlags() - if (civInfo.isCityState() && !otherCiv().isCityState()) + if (civInfo.isCityState() && otherCiv().isMajorCiv()) nextTurnCityStateInfluence() updateEverBeenFriends() } @@ -442,14 +444,16 @@ class DiplomacyManager() { val initialRelationshipLevel = relationshipLevel() val restingPoint = getCityStateInfluenceRestingPoint() + // We don't use `getInfluence()` here, as then during war with the ally of this CS, + // our influence would be set to -59, overwriting the old value, which we want to keep + // as it should be restored once the war ends (though we keep influence degradation from time during the war) if (influence > restingPoint) { val decrement = getCityStateInfluenceDegrade() - influence = max(restingPoint, influence - decrement) + setInfluence(max(restingPoint, influence - decrement)) } else if (influence < restingPoint) { val increment = getCityStateInfluenceRecovery() - influence = min(restingPoint, influence + increment) + setInfluence(min(restingPoint, influence + increment)) } - civInfo.updateAllyCivForCityState() if (!civInfo.isDefeated()) { // don't display city state relationship notifications when the city state is currently defeated val civCapitalLocation = if (civInfo.cities.isNotEmpty()) civInfo.getCapital().location else null @@ -466,7 +470,7 @@ class DiplomacyManager() { } // Potentially notify about afraid status - if (influence < 30 // We usually don't want to bully our friends + if (getInfluence() < 30 // We usually don't want to bully our friends && !hasFlag(DiplomacyFlags.NotifiedAfraid) && civInfo.getTributeWillingness(otherCiv()) > 0 && otherCiv().isMajorCiv() @@ -650,6 +654,10 @@ class DiplomacyManager() { trades.clear() updateHasOpenBorders() + if (civInfo.isCityState() && civInfo.getProtectorCivs().contains(otherCiv())) { + civInfo.removeProtectorCiv(otherCiv(), forced = true) + } + diplomaticStatus = DiplomaticStatus.War removeModifier(DiplomaticModifiers.YearsOfPeace) @@ -658,7 +666,15 @@ class DiplomacyManager() { removeFlag(DiplomacyFlags.BorderConflict) } - fun declareWar() { + /** Declares war with the other civ in this diplomacy manager. + * Handles all war effects and diplomatic changes with other civs and such. + * + * @param indirectCityStateAttack Influence with city states should only be set to -60 + * when they are attacked directly, not when their ally is attacked. + * When @indirectCityStateAttack is set to true, we thus don't reset the influence with this city state. + * Should only ever be set to true for calls originating from within this function. + */ + fun declareWar(indirectCityStateAttack: Boolean = false) { val otherCiv = otherCiv() val otherCivDiplomacy = otherCivDiplomacy() @@ -675,14 +691,15 @@ class DiplomacyManager() { otherCivDiplomacy.setModifier(DiplomaticModifiers.DeclaredWarOnUs, -20f) otherCivDiplomacy.removeModifier(DiplomaticModifiers.ReturnedCapturedUnits) if (otherCiv.isCityState()) { - otherCivDiplomacy.setInfluence(-60f) + if (!indirectCityStateAttack) + otherCivDiplomacy.setInfluence(-60f) civInfo.changeMinorCivsAttacked(1) otherCiv.cityStateFunctions.cityStateAttacked(civInfo) } for (thirdCiv in civInfo.getKnownCivs()) { if (thirdCiv.isAtWarWith(otherCiv)) { - if (thirdCiv.isCityState()) thirdCiv.getDiplomacyManager(civInfo).influence += 10 + if (thirdCiv.isCityState()) thirdCiv.getDiplomacyManager(civInfo).addInfluence(10f) else thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.SharedEnemy, 5f) } else thirdCiv.getDiplomacyManager(civInfo).addModifier(DiplomaticModifiers.WarMongerer, -5f) } @@ -710,11 +727,11 @@ class DiplomacyManager() { for (thirdCiv in civInfo.getKnownCivs()) { if (thirdCiv.isCityState() && thirdCiv.getAllyCiv() == civInfo.civName) { if (thirdCiv.knows(otherCiv) && !thirdCiv.isAtWarWith(otherCiv)) - thirdCiv.getDiplomacyManager(otherCiv).declareWar() + thirdCiv.getDiplomacyManager(otherCiv).declareWar(true) else if (!thirdCiv.knows(otherCiv)) { // Our city state ally has not met them yet, so they have to meet first thirdCiv.makeCivilizationsMeet(otherCiv, warOnContact = true) - thirdCiv.getDiplomacyManager(otherCiv).declareWar() + thirdCiv.getDiplomacyManager(otherCiv).declareWar(true) } } } @@ -725,11 +742,11 @@ class DiplomacyManager() { for (thirdCiv in otherCiv.getKnownCivs()) { if (thirdCiv.isCityState() && thirdCiv.getAllyCiv() == otherCiv.civName) { if (thirdCiv.knows(civInfo) && !thirdCiv.isAtWarWith(civInfo)) - thirdCiv.getDiplomacyManager(civInfo).declareWar() + thirdCiv.getDiplomacyManager(civInfo).declareWar(true) else if (!thirdCiv.knows(civInfo)) { // Their city state ally has not met us yet, so we have to meet first thirdCiv.makeCivilizationsMeet(civInfo, warOnContact = true) - thirdCiv.getDiplomacyManager(civInfo).declareWar() + thirdCiv.getDiplomacyManager(civInfo).declareWar(true) } } } diff --git a/core/src/com/unciv/ui/tilegroups/CityButton.kt b/core/src/com/unciv/ui/tilegroups/CityButton.kt index 4d1eb568ce..e6fe71f18d 100644 --- a/core/src/com/unciv/ui/tilegroups/CityButton.kt +++ b/core/src/com/unciv/ui/tilegroups/CityButton.kt @@ -54,7 +54,7 @@ class CityButton(val city: CityInfo, private val tileGroup: WorldTileGroup): Tab if (city.civInfo.isCityState() && city.civInfo.knows(worldScreen.viewingCiv)) { val diplomacyManager = city.civInfo.getDiplomacyManager(worldScreen.viewingCiv) - val influenceBar = getInfluenceBar(diplomacyManager.influence, diplomacyManager.relationshipLevel()) + val influenceBar = getInfluenceBar(diplomacyManager.getInfluence(), diplomacyManager.relationshipLevel()) add(influenceBar).row() } diff --git a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt index 7e6bdc56dd..d7cdda92e0 100644 --- a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt +++ b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt @@ -202,7 +202,7 @@ class DiplomacyScreen( otherCiv.updateAllyCivForCityState() val ally = otherCiv.getAllyCiv() if (ally != null) { - val allyInfluence = otherCiv.getDiplomacyManager(ally).influence.toInt() + val allyInfluence = otherCiv.getDiplomacyManager(ally).getInfluence().toInt() diplomacyTable .add("Ally: [$ally] with [$allyInfluence] Influence".toLabel()) .row() @@ -214,8 +214,11 @@ class DiplomacyScreen( diplomacyTable.add(protectorString.toLabel()).row() } + val atWar = otherCiv.isAtWarWith(viewingCiv) + val nextLevelString = when { - otherCivDiplomacyManager.influence.toInt() < 30 -> "Reach 30 for friendship." + atWar -> "" + otherCivDiplomacyManager.getInfluence().toInt() < 30 -> "Reach 30 for friendship." ally == viewingCiv.civName -> "" else -> "Reach highest influence above 60 for alliance." } @@ -322,7 +325,7 @@ class DiplomacyScreen( rightSideTable.add(ScrollPane(getGoldGiftTable(otherCiv))) } diplomacyTable.add(giveGiftButton).row() - if (isNotPlayersTurn()) giveGiftButton.disable() + if (isNotPlayersTurn() || viewingCiv.isAtWarWith(otherCiv)) giveGiftButton.disable() val improveTileButton = getImproveTilesButton(otherCiv, otherCivDiplomacyManager) if (improveTileButton != null) diplomacyTable.add(improveTileButton).row() @@ -357,7 +360,7 @@ class DiplomacyScreen( rightSideTable.add(ScrollPane(getDemandTributeTable(otherCiv))) } diplomacyTable.add(demandTributeButton).row() - if (isNotPlayersTurn()) demandTributeButton.disable() + if (isNotPlayersTurn() || viewingCiv.isAtWarWith(otherCiv)) demandTributeButton.disable() val diplomacyManager = viewingCiv.getDiplomacyManager(otherCiv) if (!viewingCiv.gameInfo.ruleSet.modOptions.uniques.contains(ModOptionsConstants.diplomaticRelationshipsCannotChange)) { @@ -440,7 +443,7 @@ class DiplomacyScreen( } - if (isNotPlayersTurn() || otherCivDiplomacyManager.influence < 60 || !needsImprovements) + if (isNotPlayersTurn() || otherCivDiplomacyManager.getInfluence() < 60 || !needsImprovements) improveTileButton.disable() return improveTileButton } @@ -857,7 +860,7 @@ class DiplomacyScreen( val relationshipTable = Table() val opinionOfUs = - if (otherCivDiplomacyManager.civInfo.isCityState()) otherCivDiplomacyManager.influence.toInt() + if (otherCivDiplomacyManager.civInfo.isCityState()) otherCivDiplomacyManager.getInfluence().toInt() else otherCivDiplomacyManager.opinionOfOtherCiv().toInt() relationshipTable.add("{Our relationship}: ".toLabel()) @@ -875,8 +878,8 @@ class DiplomacyScreen( if (otherCivDiplomacyManager.civInfo.isCityState()) relationshipTable.add( CityButton.getInfluenceBar( - otherCivDiplomacyManager.influence, - otherCivDiplomacyManager.relationshipLevel(), + otherCivDiplomacyManager.getInfluence(), + relationshipLevel, 200f, 10f ) ).colspan(2).pad(5f)