Conquering a city destroys buildings inside the city (#4995)

* When conquering a city, some buildings are now destroyed

* Added missing translation for uniques
This commit is contained in:
Xander Lenstra 2021-08-26 16:09:55 +02:00 committed by GitHub
parent 59f8a0eebf
commit 86d1f143d9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 112 additions and 76 deletions

View File

@ -18,7 +18,8 @@
"culture": 2, "culture": 2,
"cost": 40, "cost": 40,
"hurryCostModifier": 40, "hurryCostModifier": 40,
"maintenance": 1 "maintenance": 1,
"uniques": ["Destroyed when the city is captured"]
}, },
// Column 1 // Column 1
{ {
@ -128,7 +129,8 @@
"cityStrength": 5, "cityStrength": 5,
"cityHealth": 50, "cityHealth": 50,
"hurryCostModifier": 25, "hurryCostModifier": 25,
"requiredTech": "Masonry" "requiredTech": "Masonry",
"uniques": ["Destroyed when the city is captured"]
}, },
{ {
"name": "Walls of Babylon", "name": "Walls of Babylon",
@ -138,7 +140,8 @@
"cityStrength": 6, "cityStrength": 6,
"cityHealth": 100, "cityHealth": 100,
"hurryCostModifier": 25, "hurryCostModifier": 25,
"requiredTech": "Masonry" "requiredTech": "Masonry",
"uniques": ["Destroyed when the city is captured"]
}, },
{ {
"name": "The Pyramids", "name": "The Pyramids",
@ -163,7 +166,7 @@
"name": "Barracks", "name": "Barracks",
"hurryCostModifier": 25, "hurryCostModifier": 25,
"maintenance": 1, "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" "requiredTech": "Bronze Working"
}, },
{ {
@ -173,7 +176,7 @@
"hurryCostModifier": 25, "hurryCostModifier": 25,
"maintenance": 1, "maintenance": 1,
"uniques": ["-[25]% Culture cost of acquiring tiles [in this city]","-[25]% Gold cost of acquiring tiles [in this city]", "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" "requiredTech": "Bronze Working"
}, },
{ {
@ -270,7 +273,7 @@
"maintenance": 2, "maintenance": 2,
"hurryCostModifier": 25, "hurryCostModifier": 25,
"requiredTech": "Philosophy", "requiredTech": "Philosophy",
"uniques": ["Hidden when religion is disabled"] "uniques": ["Hidden when religion is disabled", "Destroyed when the city is captured"]
}, },
{ {
"name": "National College", "name": "National College",
@ -570,8 +573,8 @@
"cityHealth": 25, "cityHealth": 25,
"hurryCostModifier": 25, "hurryCostModifier": 25,
"requiredBuilding": "Walls", "requiredBuilding": "Walls",
"requiredTech": "Chivalry" "requiredTech": "Chivalry",
// 4 cityStrength in vanilla, no ExtraCityHitPoints in vanilla "uniques": ["Destroyed when the city is captured"]
}, },
{ {
"name": "Mughal Fort", "name": "Mughal Fort",
@ -583,7 +586,7 @@
"culture": 2, "culture": 2,
"hurryCostModifier": 25, "hurryCostModifier": 25,
"requiredBuilding": "Walls", "requiredBuilding": "Walls",
"uniques": ["[+1 Gold] once [Flight] is discovered"], "uniques": ["[+1 Gold] once [Flight] is discovered", "Destroyed when the city is captured"],
"requiredTech": "Chivalry" "requiredTech": "Chivalry"
}, },
{ {
@ -620,7 +623,7 @@
"hurryCostModifier": 25, "hurryCostModifier": 25,
"maintenance": 1, "maintenance": 1,
"requiredBuilding": "Barracks", "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" "requiredTech": "Steel"
}, },
@ -643,7 +646,8 @@
"hurryCostModifier": 10, "hurryCostModifier": 10,
"requiredBuilding": "Amphitheater", "requiredBuilding": "Amphitheater",
"maintenance": 2, "maintenance": 2,
"requiredTech": "Acoustics" "requiredTech": "Acoustics",
"uniques": ["Destroyed when the city is captured"]
}, },
{ {
"name": "Sistine Chapel", "name": "Sistine Chapel",
@ -800,7 +804,8 @@
"requiredBuilding": "Opera House", "requiredBuilding": "Opera House",
"maintenance": 3, "maintenance": 3,
"hurryCostModifier": 0, "hurryCostModifier": 0,
"requiredTech": "Archaeology" "requiredTech": "Archaeology",
"uniques": ["Destroyed when the city is captured"]
}, },
{ {
"name": "The Louvre", "name": "The Louvre",
@ -848,7 +853,7 @@
"hurryCostModifier": 0, "hurryCostModifier": 0,
"maintenance": 1, "maintenance": 1,
"requiredBuilding": "Armory", "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" "requiredTech": "Military Science"
}, },
{ {
@ -905,7 +910,8 @@
"percentStatBonus": {"culture": 33}, "percentStatBonus": {"culture": 33},
"requiredBuilding": "Museum", "requiredBuilding": "Museum",
"maintenance": 3, "maintenance": 3,
"requiredTech": "Radio" "requiredTech": "Radio",
"uniques": ["Destroyed when the city is captured"]
}, },
{ {
"name": "Eiffel Tower", "name": "Eiffel Tower",
@ -923,7 +929,8 @@
"cityHealth": 25, "cityHealth": 25,
"hurryCostModifier": 25, "hurryCostModifier": 25,
"requiredBuilding": "Arsenal", "requiredBuilding": "Arsenal",
"requiredTech": "Replaceable Parts" "requiredTech": "Replaceable Parts",
"uniques": ["Destroyed when the city is captured"]
}, },
{ {
"name": "Statue of Liberty", "name": "Statue of Liberty",
@ -1134,7 +1141,7 @@
"faith": 2, "faith": 2,
"uniques": ["[+1 Culture, +1 Faith] from [Wine] tiles [in this city]", "uniques": ["[+1 Culture, +1 Faith] from [Wine] tiles [in this city]",
"[+1 Culture, +1 Faith] from [Incense] 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", "name": "Mosque",

View File

@ -1166,3 +1166,4 @@ in all cities with a garrison =
Only available after [] turns = Only available after [] turns =
This Unit upgrades for free = This Unit upgrades for free =
[stats] when a city adopts this religion for the first time = [stats] when a city adopts this religion for the first time =
Never destroyed when the city is captured =

View File

@ -15,15 +15,66 @@ import java.util.*
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.random.Random
/** Helper class for containing 200 lines of "how to move cities between civs" */ /** Helper class for containing 200 lines of "how to move cities between civs" */
class CityInfoConquestFunctions(val city: CityInfo){ class CityInfoConquestFunctions(val city: CityInfo){
fun annexCity() { private val tileBasedRandom = Random(city.getCenterTile().position.toString().hashCode())
city.isPuppet = false
city.cityConstructions.inProgressConstructions.clear() // undo all progress of the previous civ on units etc. private fun getGoldForCapturingCity(conqueringCiv: CivilizationInfo): Int {
city.cityStats.update() val baseGold = 20 + 10 * city.population.population + tileBasedRandom.nextInt(40)
if (!UncivGame.Current.consoleMode) val turnModifier = max(0, min(50, city.civInfo.gameInfo.turns - city.turnAcquired)) / 50f
UncivGame.Current.worldScreen.shouldUpdate = true 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 // Gain gold for plundering city
val goldPlundered = getGoldForCapturingCity(conqueringCiv) val goldPlundered = getGoldForCapturingCity(conqueringCiv)
city.apply { city.apply {
conqueringCiv.addGold(goldPlundered)
conqueringCiv.addNotification("Received [$goldPlundered] Gold for capturing [$name]", getCenterTile().position, NotificationIcon.Gold)
val oldCiv = civInfo val oldCiv = civInfo
val reconqueredCityWhileStillInResistance = previousOwner == conqueringCiv.civName && resistanceCounter != 0
previousOwner = oldCiv.civName
// must be before moving the city to the conquering civ, // must be before moving the city to the conquering civ,
// so the repercussions are properly checked // so the repercussions are properly checked
diplomaticRepercussionsForConqueringCity(oldCiv, conqueringCiv) diplomaticRepercussionsForConqueringCity(oldCiv, conqueringCiv)
moveToCiv(conqueringCiv) conquerCity(conqueringCiv, oldCiv, 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
isPuppet = true isPuppet = true
health = getMaxHealth() / 2 // I think that cities recover to half health when conquered?
cityStats.update() cityStats.update()
// The city could be producing something that puppets shouldn't, like units // The city could be producing something that puppets shouldn't, like units
cityConstructions.currentConstructionIsUserSet = false cityConstructions.currentConstructionIsUserSet = false
@ -63,18 +101,14 @@ class CityInfoConquestFunctions(val city: CityInfo){
} }
} }
fun annexCity() {
fun getGoldForCapturingCity(conqueringCiv: CivilizationInfo): Int { city.isPuppet = false
val baseGold = 20 + 10 * city.population.population + Random().nextInt(40) city.cityConstructions.inProgressConstructions.clear() // undo all progress of the previous civ on units etc.
val turnModifier = max(0, min(50, city.civInfo.gameInfo.turns - city.turnAcquired)) / 50f city.cityStats.update()
val cityModifier = if (city.containsBuildingUnique("Doubles Gold given to enemy if city is captured")) 2f else 1f if (!UncivGame.Current.consoleMode)
val conqueringCivModifier = if (conqueringCiv.hasUnique("Receive triple Gold from Barbarian encampments and pillaging Cities")) 3f else 1f UncivGame.Current.worldScreen.shouldUpdate = true
val goldPlundered = baseGold * turnModifier * cityModifier * conqueringCivModifier
return goldPlundered.toInt()
} }
private fun diplomaticRepercussionsForConqueringCity(oldCiv: CivilizationInfo, conqueringCiv: CivilizationInfo) { private fun diplomaticRepercussionsForConqueringCity(oldCiv: CivilizationInfo, conqueringCiv: CivilizationInfo) {
val currentPopulation = city.population.population val currentPopulation = city.population.population
val percentageOfCivPopulationInThatCity = currentPopulation * 100f / val percentageOfCivPopulationInThatCity = currentPopulation * 100f /
@ -100,32 +134,24 @@ class CityInfoConquestFunctions(val city: CityInfo){
} }
fun liberateCity(conqueringCiv: CivilizationInfo) { fun liberateCity(conqueringCiv: CivilizationInfo) {
val goldPlundered = getGoldForCapturingCity(conqueringCiv)
city.apply { city.apply {
if (foundingCiv == "") { // this should never happen but just in case... if (foundingCiv == "") { // this should never happen but just in case...
puppetCity(conqueringCiv) this@CityInfoConquestFunctions.puppetCity(conqueringCiv)
annexCity() this@CityInfoConquestFunctions.annexCity()
return 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 } val foundingCiv = civInfo.gameInfo.civilizations.first { it.civName == foundingCiv }
if (foundingCiv.isDefeated()) // resurrected civ if (foundingCiv.isDefeated()) // resurrected civ
for (diploManager in foundingCiv.diplomacy.values) for (diploManager in foundingCiv.diplomacy.values)
if (diploManager.diplomaticStatus == DiplomaticStatus.War) if (diploManager.diplomaticStatus == DiplomaticStatus.War)
diploManager.makePeace() diploManager.makePeace()
diplomaticRepercussionsForLiberatingCity(conqueringCiv) val oldCiv = civInfo
moveToCiv(foundingCiv)
Battle.destroyIfDefeated(oldCiv, conqueringCiv) diplomaticRepercussionsForLiberatingCity(conqueringCiv, oldCiv)
health = getMaxHealth() / 2 // I think that cities recover to half health when conquered? conquerCity(conqueringCiv, oldCiv, foundingCiv)
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 (foundingCiv.cities.size == 1) cityConstructions.addBuilding(capitalCityIndicator()) // Resurrection! if (foundingCiv.cities.size == 1) cityConstructions.addBuilding(capitalCityIndicator()) // Resurrection!
isPuppet = false isPuppet = false
@ -141,12 +167,11 @@ class CityInfoConquestFunctions(val city: CityInfo){
} }
private fun diplomaticRepercussionsForLiberatingCity(conqueringCiv: CivilizationInfo) { private fun diplomaticRepercussionsForLiberatingCity(conqueringCiv: CivilizationInfo, conqueredCiv: CivilizationInfo) {
val oldOwningCiv = city.civInfo val foundingCiv = conqueredCiv.gameInfo.civilizations.first { it.civName == city.foundingCiv }
val foundingCiv = oldOwningCiv.gameInfo.civilizations.first { it.civName == city.foundingCiv }
val percentageOfCivPopulationInThatCity = city.population.population * val percentageOfCivPopulationInThatCity = city.population.population *
100f / (foundingCiv.cities.sumBy { it.population.population } + 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 // In order to get "plus points" in Diplomacy, you have to establish diplomatic relations if you haven't yet
if (!conqueringCiv.knows(foundingCiv)) if (!conqueringCiv.knows(foundingCiv))
@ -154,7 +179,7 @@ class CityInfoConquestFunctions(val city: CityInfo){
if (foundingCiv.isMajorCiv()) { if (foundingCiv.isMajorCiv()) {
foundingCiv.getDiplomacyManager(conqueringCiv) foundingCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.CapturedOurCities, respecForLiberatingOurCity) .addModifier(DiplomaticModifiers.CapturedOurCities, respectForLiberatingOurCity)
} else { } else {
//Liberating a city state gives a large amount of influence, and peace //Liberating a city state gives a large amount of influence, and peace
foundingCiv.getDiplomacyManager(conqueringCiv).influence = 90f foundingCiv.getDiplomacyManager(conqueringCiv).influence = 90f
@ -166,8 +191,8 @@ class CityInfoConquestFunctions(val city: CityInfo){
} }
} }
val otherCivsRespecForLiberating = (respecForLiberatingOurCity / 10).roundToInt().toFloat() val otherCivsRespecForLiberating = (respectForLiberatingOurCity / 10).roundToInt().toFloat()
for (thirdPartyCiv in conqueringCiv.getKnownCivs().filter { it.isMajorCiv() && it != oldOwningCiv }) { for (thirdPartyCiv in conqueringCiv.getKnownCivs().filter { it.isMajorCiv() && it != conqueredCiv }) {
thirdPartyCiv.getDiplomacyManager(conqueringCiv) thirdPartyCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.LiberatedCity, otherCivsRespecForLiberating) // Cool, keep at at! =D .addModifier(DiplomaticModifiers.LiberatedCity, otherCivsRespecForLiberating) // Cool, keep at at! =D
} }
@ -183,6 +208,7 @@ class CityInfoConquestFunctions(val city: CityInfo){
if (isOriginalCapital) civInfo.hasEverOwnedOriginalCapital = true if (isOriginalCapital) civInfo.hasEverOwnedOriginalCapital = true
hasJustBeenConquered = false hasJustBeenConquered = false
turnAcquired = civInfo.gameInfo.turns turnAcquired = civInfo.gameInfo.turns
previousOwner = oldCiv.civName
// now that the tiles have changed, we need to reassign population // now that the tiles have changed, we need to reassign population
for (it in workedTiles.filterNot { tiles.contains(it) }) { 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!) // Place palace for newCiv if this is the only city they have
for (building in cityConstructions.getBuiltBuildings().filter { it.isNationalWonder })
cityConstructions.removeBuilding(building.name)
// Locate palace for newCiv if this is the only city they have
if (newCivInfo.cities.count() == 1) { if (newCivInfo.cities.count() == 1) {
cityConstructions.addBuilding(capitalCityIndicator) cityConstructions.addBuilding(capitalCityIndicator)
} }

View File

@ -27,6 +27,9 @@ interface INonPerpetualConstruction : IConstruction, INamed, IHasUniques {
fun getMatchingUniques(uniqueTemplate: String): Sequence<Unique> { fun getMatchingUniques(uniqueTemplate: String): Sequence<Unique> {
return uniqueObjects.asSequence().filter { it.placeholderText == uniqueTemplate } 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 { fun canBePurchasedWithStat(cityInfo: CityInfo, stat: Stat, ignoreCityRequirements: Boolean = false): Boolean {
if (stat in listOf(Stat.Production, Stat.Happiness)) return false if (stat in listOf(Stat.Production, Stat.Happiness)) return false