From 86d1f143d946968ad07f20d8a84c1b88b7afba26 Mon Sep 17 00:00:00 2001 From: Xander Lenstra <71121390+xlenstra@users.noreply.github.com> Date: Thu, 26 Aug 2021 16:09:55 +0200 Subject: [PATCH] Conquering a city destroys buildings inside the city (#4995) * When conquering a city, some buildings are now destroyed * Added missing translation for uniques --- .../jsons/Civ V - Vanilla/Buildings.json | 39 +++-- .../jsons/translations/template.properties | 1 + .../logic/city/CityInfoConquestFunctions.kt | 145 ++++++++++-------- .../src/com/unciv/logic/city/IConstruction.kt | 3 + 4 files changed, 112 insertions(+), 76 deletions(-) diff --git a/android/assets/jsons/Civ V - Vanilla/Buildings.json b/android/assets/jsons/Civ V - Vanilla/Buildings.json index 6bc756742f..d1fde199f6 100644 --- a/android/assets/jsons/Civ V - Vanilla/Buildings.json +++ b/android/assets/jsons/Civ V - Vanilla/Buildings.json @@ -18,7 +18,8 @@ "culture": 2, "cost": 40, "hurryCostModifier": 40, - "maintenance": 1 + "maintenance": 1, + "uniques": ["Destroyed when the city is captured"] }, // Column 1 { @@ -128,7 +129,8 @@ "cityStrength": 5, "cityHealth": 50, "hurryCostModifier": 25, - "requiredTech": "Masonry" + "requiredTech": "Masonry", + "uniques": ["Destroyed when the city is captured"] }, { "name": "Walls of Babylon", @@ -138,7 +140,8 @@ "cityStrength": 6, "cityHealth": 100, "hurryCostModifier": 25, - "requiredTech": "Masonry" + "requiredTech": "Masonry", + "uniques": ["Destroyed when the city is captured"] }, { "name": "The Pyramids", @@ -163,7 +166,7 @@ "name": "Barracks", "hurryCostModifier": 25, "maintenance": 1, - "uniques": ["New [Military] units start with [15] Experience [in this city]"] + "uniques": ["New [Military] units start with [15] Experience [in this city]", "Destroyed when the city is captured"], "requiredTech": "Bronze Working" }, { @@ -173,7 +176,7 @@ "hurryCostModifier": 25, "maintenance": 1, "uniques": ["-[25]% Culture cost of acquiring tiles [in this city]","-[25]% Gold cost of acquiring tiles [in this city]", - "New [Military] units start with [15] Experience [in this city]"], + "New [Military] units start with [15] Experience [in this city]", "Destroyed when the city is captured"], "requiredTech": "Bronze Working" }, { @@ -270,7 +273,7 @@ "maintenance": 2, "hurryCostModifier": 25, "requiredTech": "Philosophy", - "uniques": ["Hidden when religion is disabled"] + "uniques": ["Hidden when religion is disabled", "Destroyed when the city is captured"] }, { "name": "National College", @@ -570,8 +573,8 @@ "cityHealth": 25, "hurryCostModifier": 25, "requiredBuilding": "Walls", - "requiredTech": "Chivalry" - // 4 cityStrength in vanilla, no ExtraCityHitPoints in vanilla + "requiredTech": "Chivalry", + "uniques": ["Destroyed when the city is captured"] }, { "name": "Mughal Fort", @@ -583,7 +586,7 @@ "culture": 2, "hurryCostModifier": 25, "requiredBuilding": "Walls", - "uniques": ["[+1 Gold] once [Flight] is discovered"], + "uniques": ["[+1 Gold] once [Flight] is discovered", "Destroyed when the city is captured"], "requiredTech": "Chivalry" }, { @@ -620,7 +623,7 @@ "hurryCostModifier": 25, "maintenance": 1, "requiredBuilding": "Barracks", - "uniques": ["New [Military] units start with [15] Experience [in this city]"] + "uniques": ["New [Military] units start with [15] Experience [in this city]", "Destroyed when the city is captured"], "requiredTech": "Steel" }, @@ -643,7 +646,8 @@ "hurryCostModifier": 10, "requiredBuilding": "Amphitheater", "maintenance": 2, - "requiredTech": "Acoustics" + "requiredTech": "Acoustics", + "uniques": ["Destroyed when the city is captured"] }, { "name": "Sistine Chapel", @@ -800,7 +804,8 @@ "requiredBuilding": "Opera House", "maintenance": 3, "hurryCostModifier": 0, - "requiredTech": "Archaeology" + "requiredTech": "Archaeology", + "uniques": ["Destroyed when the city is captured"] }, { "name": "The Louvre", @@ -848,7 +853,7 @@ "hurryCostModifier": 0, "maintenance": 1, "requiredBuilding": "Armory", - "uniques": ["New [Military] units start with [15] Experience [in this city]"], + "uniques": ["New [Military] units start with [15] Experience [in this city]", "Destroyed when the city is captured"], "requiredTech": "Military Science" }, { @@ -905,7 +910,8 @@ "percentStatBonus": {"culture": 33}, "requiredBuilding": "Museum", "maintenance": 3, - "requiredTech": "Radio" + "requiredTech": "Radio", + "uniques": ["Destroyed when the city is captured"] }, { "name": "Eiffel Tower", @@ -923,7 +929,8 @@ "cityHealth": 25, "hurryCostModifier": 25, "requiredBuilding": "Arsenal", - "requiredTech": "Replaceable Parts" + "requiredTech": "Replaceable Parts", + "uniques": ["Destroyed when the city is captured"] }, { "name": "Statue of Liberty", @@ -1134,7 +1141,7 @@ "faith": 2, "uniques": ["[+1 Culture, +1 Faith] from [Wine] tiles [in this city]", "[+1 Culture, +1 Faith] from [Incense] tiles [in this city]", - "Unbuildable", "Hidden when religion is disabled"] + "Unbuildable", "Hidden when religion is disabled", "Destroyed when the city is captured"] }, { "name": "Mosque", diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 95e50a1264..a6c0462606 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -1166,3 +1166,4 @@ in all cities with a garrison = Only available after [] turns = This Unit upgrades for free = [stats] when a city adopts this religion for the first time = +Never destroyed when the city is captured = \ No newline at end of file diff --git a/core/src/com/unciv/logic/city/CityInfoConquestFunctions.kt b/core/src/com/unciv/logic/city/CityInfoConquestFunctions.kt index c8c6e6a8f1..cb2fa108dd 100644 --- a/core/src/com/unciv/logic/city/CityInfoConquestFunctions.kt +++ b/core/src/com/unciv/logic/city/CityInfoConquestFunctions.kt @@ -15,15 +15,66 @@ import java.util.* import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt +import kotlin.random.Random /** Helper class for containing 200 lines of "how to move cities between civs" */ class CityInfoConquestFunctions(val city: CityInfo){ - fun annexCity() { - city.isPuppet = false - city.cityConstructions.inProgressConstructions.clear() // undo all progress of the previous civ on units etc. - city.cityStats.update() - if (!UncivGame.Current.consoleMode) - UncivGame.Current.worldScreen.shouldUpdate = true + private val tileBasedRandom = Random(city.getCenterTile().position.toString().hashCode()) + + private fun getGoldForCapturingCity(conqueringCiv: CivilizationInfo): Int { + val baseGold = 20 + 10 * city.population.population + tileBasedRandom.nextInt(40) + val turnModifier = max(0, min(50, city.civInfo.gameInfo.turns - city.turnAcquired)) / 50f + val cityModifier = if (city.containsBuildingUnique("Doubles Gold given to enemy if city is captured")) 2f else 1f + val conqueringCivModifier = if (conqueringCiv.hasUnique("Receive triple Gold from Barbarian encampments and pillaging Cities")) 3f else 1f + + val goldPlundered = baseGold * turnModifier * cityModifier * conqueringCivModifier + return goldPlundered.toInt() + } + + private fun destroyBuildingsOnCapture() { + city.apply { + // Remove all national wonders (must come after the palace relocation because that's a national wonder too!) + for (building in cityConstructions.getBuiltBuildings()) { + when { + building.hasUnique("Never destroyed when the city is captured") -> continue + building.hasUnique("Destroyed when the city is captured") -> + cityConstructions.removeBuilding(building.name) + else -> { + if (tileBasedRandom.nextInt(100) < 34) { + cityConstructions.removeBuilding(building.name) + } + } + } + } + } + } + + /** Function for stuff that should happen on any capture, be it puppet, annex or liberate. + * Stuff that should happen any time a city is moved between civs, so also when trading, + * should go in `this.moveToCiv()`, which this function also calls. + */ + private fun conquerCity(conqueringCiv: CivilizationInfo, conqueredCiv: CivilizationInfo, receivingCiv: CivilizationInfo) { + val goldPlundered = getGoldForCapturingCity(conqueringCiv) + city.apply { + conqueringCiv.addGold(goldPlundered) + conqueringCiv.addNotification("Received [$goldPlundered] Gold for capturing [$name]", getCenterTile().position, NotificationIcon.Gold) + + val reconqueredCityWhileStillInResistance = previousOwner == conqueringCiv.civName && resistanceCounter != 0 + + this@CityInfoConquestFunctions.moveToCiv(receivingCiv) + + destroyBuildingsOnCapture() + + Battle.destroyIfDefeated(conqueredCiv, conqueringCiv) + + health = getMaxHealth() / 2 // I think that cities recover to half health when conquered? + if (population.population > 1) population.addPopulation(-1 - population.population / 4) // so from 2-4 population, remove 1, from 5-8, remove 2, etc. + reassignPopulation() + + resistanceCounter = + if (reconqueredCityWhileStillInResistance || foundingCiv == receivingCiv.civName) 0 + else population.population // I checked, and even if you puppet there's resistance for conquering + } } @@ -32,29 +83,16 @@ class CityInfoConquestFunctions(val city: CityInfo){ // Gain gold for plundering city val goldPlundered = getGoldForCapturingCity(conqueringCiv) city.apply { - conqueringCiv.addGold(goldPlundered) - conqueringCiv.addNotification("Received [$goldPlundered] Gold for capturing [$name]", getCenterTile().position, NotificationIcon.Gold) - + val oldCiv = civInfo - val reconqueredCityWhileStillInResistance = previousOwner == conqueringCiv.civName && resistanceCounter != 0 - - previousOwner = oldCiv.civName // must be before moving the city to the conquering civ, // so the repercussions are properly checked diplomaticRepercussionsForConqueringCity(oldCiv, conqueringCiv) - moveToCiv(conqueringCiv) - Battle.destroyIfDefeated(oldCiv, conqueringCiv) - - if (population.population > 1) population.addPopulation(-1 - population.population / 4) // so from 2-4 population, remove 1, from 5-8, remove 2, etc. - reassignPopulation() - - if (reconqueredCityWhileStillInResistance || foundingCiv == conqueringCiv.civName) - resistanceCounter = 0 - else resistanceCounter = population.population // I checked, and even if you puppet there's resistance for conquering + conquerCity(conqueringCiv, oldCiv, conqueringCiv) + isPuppet = true - health = getMaxHealth() / 2 // I think that cities recover to half health when conquered? cityStats.update() // The city could be producing something that puppets shouldn't, like units cityConstructions.currentConstructionIsUserSet = false @@ -63,18 +101,14 @@ class CityInfoConquestFunctions(val city: CityInfo){ } } - - fun getGoldForCapturingCity(conqueringCiv: CivilizationInfo): Int { - val baseGold = 20 + 10 * city.population.population + Random().nextInt(40) - val turnModifier = max(0, min(50, city.civInfo.gameInfo.turns - city.turnAcquired)) / 50f - val cityModifier = if (city.containsBuildingUnique("Doubles Gold given to enemy if city is captured")) 2f else 1f - val conqueringCivModifier = if (conqueringCiv.hasUnique("Receive triple Gold from Barbarian encampments and pillaging Cities")) 3f else 1f - - val goldPlundered = baseGold * turnModifier * cityModifier * conqueringCivModifier - return goldPlundered.toInt() + fun annexCity() { + city.isPuppet = false + city.cityConstructions.inProgressConstructions.clear() // undo all progress of the previous civ on units etc. + city.cityStats.update() + if (!UncivGame.Current.consoleMode) + UncivGame.Current.worldScreen.shouldUpdate = true } - private fun diplomaticRepercussionsForConqueringCity(oldCiv: CivilizationInfo, conqueringCiv: CivilizationInfo) { val currentPopulation = city.population.population val percentageOfCivPopulationInThatCity = currentPopulation * 100f / @@ -100,32 +134,24 @@ class CityInfoConquestFunctions(val city: CityInfo){ } fun liberateCity(conqueringCiv: CivilizationInfo) { - val goldPlundered = getGoldForCapturingCity(conqueringCiv) city.apply { if (foundingCiv == "") { // this should never happen but just in case... - puppetCity(conqueringCiv) - annexCity() + this@CityInfoConquestFunctions.puppetCity(conqueringCiv) + this@CityInfoConquestFunctions.annexCity() return } - conqueringCiv.addGold(goldPlundered) - conqueringCiv.addNotification("Received [$goldPlundered] Gold for capturing [$name]", getCenterTile().position, NotificationIcon.Gold) - - val oldCiv = civInfo - val foundingCiv = civInfo.gameInfo.civilizations.first { it.civName == foundingCiv } if (foundingCiv.isDefeated()) // resurrected civ for (diploManager in foundingCiv.diplomacy.values) if (diploManager.diplomaticStatus == DiplomaticStatus.War) diploManager.makePeace() - diplomaticRepercussionsForLiberatingCity(conqueringCiv) - moveToCiv(foundingCiv) - Battle.destroyIfDefeated(oldCiv, conqueringCiv) - - health = getMaxHealth() / 2 // I think that cities recover to half health when conquered? - if (population.population > 1) population.addPopulation(-1 - population.population / 4) // so from 2-4 population, remove 1, from 5-8, remove 2, etc. - reassignPopulation() + val oldCiv = civInfo + + diplomaticRepercussionsForLiberatingCity(conqueringCiv, oldCiv) + + conquerCity(conqueringCiv, oldCiv, foundingCiv) if (foundingCiv.cities.size == 1) cityConstructions.addBuilding(capitalCityIndicator()) // Resurrection! isPuppet = false @@ -141,12 +167,11 @@ class CityInfoConquestFunctions(val city: CityInfo){ } - private fun diplomaticRepercussionsForLiberatingCity(conqueringCiv: CivilizationInfo) { - val oldOwningCiv = city.civInfo - val foundingCiv = oldOwningCiv.gameInfo.civilizations.first { it.civName == city.foundingCiv } + private fun diplomaticRepercussionsForLiberatingCity(conqueringCiv: CivilizationInfo, conqueredCiv: CivilizationInfo) { + val foundingCiv = conqueredCiv.gameInfo.civilizations.first { it.civName == city.foundingCiv } val percentageOfCivPopulationInThatCity = city.population.population * 100f / (foundingCiv.cities.sumBy { it.population.population } + city.population.population) - val respecForLiberatingOurCity = 10f + percentageOfCivPopulationInThatCity.roundToInt() + val respectForLiberatingOurCity = 10f + percentageOfCivPopulationInThatCity.roundToInt() // In order to get "plus points" in Diplomacy, you have to establish diplomatic relations if you haven't yet if (!conqueringCiv.knows(foundingCiv)) @@ -154,7 +179,7 @@ class CityInfoConquestFunctions(val city: CityInfo){ if (foundingCiv.isMajorCiv()) { foundingCiv.getDiplomacyManager(conqueringCiv) - .addModifier(DiplomaticModifiers.CapturedOurCities, respecForLiberatingOurCity) + .addModifier(DiplomaticModifiers.CapturedOurCities, respectForLiberatingOurCity) } else { //Liberating a city state gives a large amount of influence, and peace foundingCiv.getDiplomacyManager(conqueringCiv).influence = 90f @@ -166,8 +191,8 @@ class CityInfoConquestFunctions(val city: CityInfo){ } } - val otherCivsRespecForLiberating = (respecForLiberatingOurCity / 10).roundToInt().toFloat() - for (thirdPartyCiv in conqueringCiv.getKnownCivs().filter { it.isMajorCiv() && it != oldOwningCiv }) { + val otherCivsRespecForLiberating = (respectForLiberatingOurCity / 10).roundToInt().toFloat() + for (thirdPartyCiv in conqueringCiv.getKnownCivs().filter { it.isMajorCiv() && it != conqueredCiv }) { thirdPartyCiv.getDiplomacyManager(conqueringCiv) .addModifier(DiplomaticModifiers.LiberatedCity, otherCivsRespecForLiberating) // Cool, keep at at! =D } @@ -183,6 +208,7 @@ class CityInfoConquestFunctions(val city: CityInfo){ if (isOriginalCapital) civInfo.hasEverOwnedOriginalCapital = true hasJustBeenConquered = false turnAcquired = civInfo.gameInfo.turns + previousOwner = oldCiv.civName // now that the tiles have changed, we need to reassign population for (it in workedTiles.filterNot { tiles.contains(it) }) { @@ -199,13 +225,12 @@ class CityInfoConquestFunctions(val city: CityInfo){ } } + for (building in cityConstructions.getBuiltBuildings()) { + if (building.isNationalWonder && !building.hasUnique("Never destroyed when the city is captured")) + cityConstructions.removeBuilding(building.name) + } - // Remove all national wonders (must come after the palace relocation because that's a national wonder too!) - for (building in cityConstructions.getBuiltBuildings().filter { it.isNationalWonder }) - cityConstructions.removeBuilding(building.name) - - - // Locate palace for newCiv if this is the only city they have + // Place palace for newCiv if this is the only city they have if (newCivInfo.cities.count() == 1) { cityConstructions.addBuilding(capitalCityIndicator) } diff --git a/core/src/com/unciv/logic/city/IConstruction.kt b/core/src/com/unciv/logic/city/IConstruction.kt index f251530ed4..f946b82757 100644 --- a/core/src/com/unciv/logic/city/IConstruction.kt +++ b/core/src/com/unciv/logic/city/IConstruction.kt @@ -27,6 +27,9 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques { fun getMatchingUniques(uniqueTemplate: String): Sequence { return uniqueObjects.asSequence().filter { it.placeholderText == uniqueTemplate } } + fun hasUnique(uniqueTemplate: String): Boolean { + return uniqueObjects.any { it.placeholderText == uniqueTemplate } + } fun canBePurchasedWithStat(cityInfo: CityInfo, stat: Stat, ignoreCityRequirements: Boolean = false): Boolean { if (stat in listOf(Stat.Production, Stat.Happiness)) return false