Code reorg - moved all 'city moves between civs' functions to a separate class

This commit is contained in:
Yair Morgenstern
2021-04-04 19:31:06 +03:00
parent bf596586c7
commit 0233aa1bc5
2 changed files with 229 additions and 206 deletions

View File

@ -1,21 +1,11 @@
package com.unciv.logic.city
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.battle.Battle
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap
import com.unciv.logic.trade.TradeLogic
import com.unciv.logic.trade.TradeOffer
import com.unciv.logic.trade.TradeType
import com.unciv.models.ruleset.tile.ResourceSupplyList
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unit.BaseUnit
@ -24,11 +14,8 @@ import com.unciv.models.stats.StatMap
import com.unciv.models.stats.Stats
import com.unciv.models.translations.equalsPlaceholderText
import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.ui.utils.withoutItem
import java.util.*
import kotlin.collections.HashSet
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
@ -313,13 +300,9 @@ class CityInfo {
return stats
}
internal fun getMaxHealth(): Int {
return 200 + cityConstructions.getBuiltBuildings().sumBy { it.cityHealth }
}
internal fun getMaxHealth() = 200 + cityConstructions.getBuiltBuildings().sumBy { it.cityHealth }
override fun toString(): String {
return name
} // for debug
override fun toString() = name // for debug
//endregion
//region state-changing functions
@ -411,188 +394,17 @@ class CityInfo {
}
}
fun annexCity() {
isPuppet = false
cityConstructions.inProgressConstructions.clear() // undo all progress of the previous civ on units etc.
cityStats.update()
if (!UncivGame.Current.consoleMode)
UncivGame.Current.worldScreen.shouldUpdate = true
}
fun annexCity() = CityInfoConquestFunctions(this).annexCity()
/** This happens when we either puppet OR annex, basically whenever we conquer a city and don't liberate it */
fun puppetCity(conqueringCiv: CivilizationInfo) {
// Gain gold for plundering city
val goldPlundered = getGoldForCapturingCity(conqueringCiv)
conqueringCiv.gold += goldPlundered
conqueringCiv.addNotification("Received [$goldPlundered] Gold for capturing [$name]", centerTileInfo.position, NotificationIcon.Gold)
val oldCiv = civInfo
val reconqueredOurCity = previousOwner == conqueringCiv.civName
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.population -= 1 + population.population / 4 // so from 2-4 population, remove 1, from 5-8, remove 2, etc.
reassignPopulation()
if (reconqueredOurCity && resistanceCounter != 0) // we reconquered our city while it was still in resistance - we get it back with no resistance
resistanceCounter = 0
else resistanceCounter = population.population // I checked, and even if you puppet there's resistance for conquering
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
cityConstructions.constructionQueue.clear()
cityConstructions.chooseNextConstruction()
}
private fun diplomaticRepercussionsForConqueringCity(oldCiv: CivilizationInfo, conqueringCiv: CivilizationInfo) {
val currentPopulation = population.population
val percentageOfCivPopulationInThatCity = currentPopulation * 100f /
oldCiv.cities.sumBy { it.population.population }
val aggroGenerated = 10f + percentageOfCivPopulationInThatCity.roundToInt()
// How can you conquer a city but not know the civ you conquered it from?!
// I don't know either, but some of our players have managed this, and crashed their game!
if (!conqueringCiv.knows(oldCiv))
conqueringCiv.meetCivilization(oldCiv)
oldCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.CapturedOurCities, -aggroGenerated)
for (thirdPartyCiv in conqueringCiv.getKnownCivs().filter { it.isMajorCiv() }) {
val aggroGeneratedForOtherCivs = (aggroGenerated / 10).roundToInt().toFloat()
if (thirdPartyCiv.isAtWarWith(oldCiv)) // You annoyed our enemy?
thirdPartyCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.SharedEnemy, aggroGeneratedForOtherCivs) // Cool, keep at at! =D
else thirdPartyCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.WarMongerer, -aggroGeneratedForOtherCivs) // Uncool bro.
}
}
fun puppetCity(conqueringCiv: CivilizationInfo) = CityInfoConquestFunctions(this).puppetCity(conqueringCiv)
/* Liberating is returning a city to its founder - makes you LOSE warmongering points **/
fun liberateCity(conqueringCiv: CivilizationInfo) {
if (foundingCiv == "") { // this should never happen but just in case...
puppetCity(conqueringCiv)
annexCity()
return
}
fun liberateCity(conqueringCiv: CivilizationInfo) = CityInfoConquestFunctions(this).liberateCity(conqueringCiv)
val oldCiv = civInfo
fun moveToCiv(newCivInfo: CivilizationInfo) = CityInfoConquestFunctions(this).moveToCiv(newCivInfo)
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?
reassignPopulation()
if (foundingCiv.cities.size == 1) cityConstructions.addBuilding(capitalCityIndicator()) // Resurrection!
isPuppet = false
cityStats.update()
// Move units out of the city when liberated
for (unit in getTiles().flatMap { it.getUnits() }.toList())
if (!unit.movement.canPassThrough(unit.currentTile))
unit.movement.teleportToClosestMoveableTile()
UncivGame.Current.worldScreen.shouldUpdate = true
}
private fun diplomaticRepercussionsForLiberatingCity(conqueringCiv: CivilizationInfo) {
val oldOwningCiv = civInfo
val foundingCiv = civInfo.gameInfo.civilizations.first { it.civName == foundingCiv }
val percentageOfCivPopulationInThatCity = population.population *
100f / (foundingCiv.cities.sumBy { it.population.population } + population.population)
val respecForLiberatingOurCity = 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))
conqueringCiv.meetCivilization(foundingCiv)
if (foundingCiv.isMajorCiv()) {
foundingCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.CapturedOurCities, respecForLiberatingOurCity)
} else {
//Liberating a city state gives a large amount of influence, and peace
foundingCiv.getDiplomacyManager(conqueringCiv).influence = 90f
if (foundingCiv.isAtWarWith(conqueringCiv)) {
val tradeLogic = TradeLogic(foundingCiv, conqueringCiv)
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.peaceTreaty, TradeType.Treaty))
tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.peaceTreaty, TradeType.Treaty))
tradeLogic.acceptTrade()
}
}
val otherCivsRespecForLiberating = (respecForLiberatingOurCity / 10).roundToInt().toFloat()
for (thirdPartyCiv in conqueringCiv.getKnownCivs().filter { it.isMajorCiv() && it != oldOwningCiv }) {
thirdPartyCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.LiberatedCity, otherCivsRespecForLiberating) // Cool, keep at at! =D
}
}
fun moveToCiv(newCivInfo: CivilizationInfo) {
val oldCiv = civInfo
civInfo.cities = civInfo.cities.toMutableList().apply { remove(this@CityInfo) }
newCivInfo.cities = newCivInfo.cities.toMutableList().apply { add(this@CityInfo) }
civInfo = newCivInfo
hasJustBeenConquered = false
turnAcquired = civInfo.gameInfo.turns
// now that the tiles have changed, we need to reassign population
for (it in workedTiles.filterNot { tiles.contains(it) }) {
workedTiles = workedTiles.withoutItem(it)
population.autoAssignPopulation()
}
// Remove/relocate palace for old Civ
val capitalCityIndicator = capitalCityIndicator()
if (cityConstructions.isBuilt(capitalCityIndicator)) {
cityConstructions.removeBuilding(capitalCityIndicator)
if (oldCiv.cities.isNotEmpty()) {
oldCiv.cities.first().cityConstructions.addBuilding(capitalCityIndicator) // relocate palace
}
}
// 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
if (newCivInfo.cities.count() == 1) {
cityConstructions.addBuilding(capitalCityIndicator)
}
isBeingRazed = false
// Transfer unique buildings
for (building in cityConstructions.getBuiltBuildings()) {
val civEquivalentBuilding = newCivInfo.getEquivalentBuilding(building.name)
if (building != civEquivalentBuilding) {
cityConstructions.removeBuilding(building.name)
cityConstructions.addBuilding(civEquivalentBuilding.name)
}
}
tryUpdateRoadStatus()
}
private fun tryUpdateRoadStatus() {
internal fun tryUpdateRoadStatus() {
if (getCenterTile().roadStatus == RoadStatus.None) {
val roadImprovement = getRuleset().tileImprovements["Road"]
if (roadImprovement != null && roadImprovement.techRequired in civInfo.tech.techsResearched)
@ -617,16 +429,6 @@ class CityInfo {
civInfo.updateDetailedCivResources() // this building could be a resource-requiring one
}
fun getGoldForCapturingCity(conqueringCiv: CivilizationInfo): Int {
val baseGold = 20 + 10 * population.population + Random().nextInt(40)
val turnModifier = max(0, min(50, civInfo.gameInfo.turns - turnAcquired)) / 50f
val cityModifier = if (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()
}
/*
When someone settles a city within 6 tiles of another civ,
this makes the AI unhappy and it starts a rolling event.
@ -673,4 +475,5 @@ class CityInfo {
}
//endregion
}
}

View File

@ -0,0 +1,220 @@
package com.unciv.logic.city
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.battle.Battle
import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.trade.TradeLogic
import com.unciv.logic.trade.TradeOffer
import com.unciv.logic.trade.TradeType
import com.unciv.ui.utils.withoutItem
import java.util.*
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
/** 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
}
/** This happens when we either puppet OR annex, basically whenever we conquer a city and don't liberate it */
fun puppetCity(conqueringCiv: CivilizationInfo) {
// Gain gold for plundering city
val goldPlundered = getGoldForCapturingCity(conqueringCiv)
city.apply {
conqueringCiv.gold += goldPlundered
conqueringCiv.addNotification("Received [$goldPlundered] Gold for capturing [$name]", getCenterTile().position, NotificationIcon.Gold)
val oldCiv = civInfo
val reconqueredOurCity = previousOwner == conqueringCiv.civName
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.population -= 1 + population.population / 4 // so from 2-4 population, remove 1, from 5-8, remove 2, etc.
reassignPopulation()
if (reconqueredOurCity && resistanceCounter != 0) // we reconquered our city while it was still in resistance - we get it back with no resistance
resistanceCounter = 0
else resistanceCounter = population.population // I checked, and even if you puppet there's resistance for conquering
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
cityConstructions.constructionQueue.clear()
cityConstructions.chooseNextConstruction()
}
}
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()
}
private fun diplomaticRepercussionsForConqueringCity(oldCiv: CivilizationInfo, conqueringCiv: CivilizationInfo) {
val currentPopulation = city.population.population
val percentageOfCivPopulationInThatCity = currentPopulation * 100f /
oldCiv.cities.sumBy { it.population.population }
val aggroGenerated = 10f + percentageOfCivPopulationInThatCity.roundToInt()
// How can you conquer a city but not know the civ you conquered it from?!
// I don't know either, but some of our players have managed this, and crashed their game!
if (!conqueringCiv.knows(oldCiv))
conqueringCiv.meetCivilization(oldCiv)
oldCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.CapturedOurCities, -aggroGenerated)
for (thirdPartyCiv in conqueringCiv.getKnownCivs().filter { it.isMajorCiv() }) {
val aggroGeneratedForOtherCivs = (aggroGenerated / 10).roundToInt().toFloat()
if (thirdPartyCiv.isAtWarWith(oldCiv)) // You annoyed our enemy?
thirdPartyCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.SharedEnemy, aggroGeneratedForOtherCivs) // Cool, keep at at! =D
else thirdPartyCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.WarMongerer, -aggroGeneratedForOtherCivs) // Uncool bro.
}
}
fun liberateCity(conqueringCiv: CivilizationInfo) {
city.apply {
if (foundingCiv == "") { // this should never happen but just in case...
puppetCity(conqueringCiv)
annexCity()
return
}
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?
reassignPopulation()
if (foundingCiv.cities.size == 1) cityConstructions.addBuilding(capitalCityIndicator()) // Resurrection!
isPuppet = false
cityStats.update()
// Move units out of the city when liberated
for (unit in getTiles().flatMap { it.getUnits() }.toList())
if (!unit.movement.canPassThrough(unit.currentTile))
unit.movement.teleportToClosestMoveableTile()
}
}
private fun diplomaticRepercussionsForLiberatingCity(conqueringCiv: CivilizationInfo) {
val oldOwningCiv = city.civInfo
val foundingCiv = oldOwningCiv.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()
// In order to get "plus points" in Diplomacy, you have to establish diplomatic relations if you haven't yet
if (!conqueringCiv.knows(foundingCiv))
conqueringCiv.meetCivilization(foundingCiv)
if (foundingCiv.isMajorCiv()) {
foundingCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.CapturedOurCities, respecForLiberatingOurCity)
} else {
//Liberating a city state gives a large amount of influence, and peace
foundingCiv.getDiplomacyManager(conqueringCiv).influence = 90f
if (foundingCiv.isAtWarWith(conqueringCiv)) {
val tradeLogic = TradeLogic(foundingCiv, conqueringCiv)
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.peaceTreaty, TradeType.Treaty))
tradeLogic.currentTrade.theirOffers.add(TradeOffer(Constants.peaceTreaty, TradeType.Treaty))
tradeLogic.acceptTrade()
}
}
val otherCivsRespecForLiberating = (respecForLiberatingOurCity / 10).roundToInt().toFloat()
for (thirdPartyCiv in conqueringCiv.getKnownCivs().filter { it.isMajorCiv() && it != oldOwningCiv }) {
thirdPartyCiv.getDiplomacyManager(conqueringCiv)
.addModifier(DiplomaticModifiers.LiberatedCity, otherCivsRespecForLiberating) // Cool, keep at at! =D
}
}
fun moveToCiv(newCivInfo: CivilizationInfo) {
city.apply {
val oldCiv = civInfo
civInfo.cities = civInfo.cities.toMutableList().apply { remove(city) }
newCivInfo.cities = newCivInfo.cities.toMutableList().apply { add(city) }
civInfo = newCivInfo
hasJustBeenConquered = false
turnAcquired = civInfo.gameInfo.turns
// now that the tiles have changed, we need to reassign population
for (it in workedTiles.filterNot { tiles.contains(it) }) {
workedTiles = workedTiles.withoutItem(it)
population.autoAssignPopulation()
}
// Remove/relocate palace for old Civ
val capitalCityIndicator = capitalCityIndicator()
if (cityConstructions.isBuilt(capitalCityIndicator)) {
cityConstructions.removeBuilding(capitalCityIndicator)
if (oldCiv.cities.isNotEmpty()) {
oldCiv.cities.first().cityConstructions.addBuilding(capitalCityIndicator) // relocate palace
}
}
// 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
if (newCivInfo.cities.count() == 1) {
cityConstructions.addBuilding(capitalCityIndicator)
}
isBeingRazed = false
// Transfer unique buildings
for (building in cityConstructions.getBuiltBuildings()) {
val civEquivalentBuilding = newCivInfo.getEquivalentBuilding(building.name)
if (building != civEquivalentBuilding) {
cityConstructions.removeBuilding(building.name)
cityConstructions.addBuilding(civEquivalentBuilding.name)
}
}
tryUpdateRoadStatus()
}
}
}