From 2594777b52f7648a4a89896c0e69dd910923d2eb Mon Sep 17 00:00:00 2001 From: Jack Rainy Date: Thu, 16 Apr 2020 10:33:58 +0300 Subject: [PATCH 1/2] The original capital can not be razed (#2412) * Civ is not defeated while at least 1 settler is alive * The original capital cannot be razed * Revert "Civ is not defeated while at least 1 settler is alive" Defeat condition is: no cities remained --- core/src/com/unciv/logic/city/CityInfo.kt | 8 +++++++- core/src/com/unciv/logic/civilization/CivilizationInfo.kt | 4 ++-- core/src/com/unciv/ui/cityscreen/CityScreen.kt | 4 +++- core/src/com/unciv/ui/worldscreen/AlertPopup.kt | 6 ++++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/core/src/com/unciv/logic/city/CityInfo.kt b/core/src/com/unciv/logic/city/CityInfo.kt index 88436be8..8a3c144e 100644 --- a/core/src/com/unciv/logic/city/CityInfo.kt +++ b/core/src/com/unciv/logic/city/CityInfo.kt @@ -56,6 +56,10 @@ class CityInfo { var attackedThisTurn = false var hasSoldBuildingThisTurn = false var isPuppet = false + /** The very first found city is the _original_ capital, + * while the _current_ capital can be any other city after the original one is captured. + * It is important to distinct them since the original cannot be razed and defines the Domination Victory. */ + var isOriginalCapital = false constructor() // for json parsing, we need to have a default constructor constructor(civInfo: CivilizationInfo, cityLocation: Vector2) { // new city! @@ -76,6 +80,7 @@ class CityInfo { name = cityNamePrefix + cityName + isOriginalCapital = civInfo.citiesCreated == 0 civInfo.citiesCreated++ civInfo.cities = civInfo.cities.toMutableList().apply { add(this@CityInfo) } @@ -125,6 +130,7 @@ class CityInfo { toReturn.foundingCiv = foundingCiv toReturn.turnAcquired = turnAcquired toReturn.isPuppet = isPuppet + toReturn.isOriginalCapital = isOriginalCapital return toReturn } @@ -139,7 +145,7 @@ class CityInfo { val mediumTypes = civInfo.citiesConnectedToCapitalToMediums[this] ?: return false return connectionTypePredicate(mediumTypes) } - fun isInResistance() = resistanceCounter>0 + fun isInResistance() = resistanceCounter > 0 fun getRuleset() = civInfo.gameInfo.ruleSet diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 49d9fb79..e169a92b 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -262,8 +262,8 @@ class CivilizationInfo { override fun toString(): String {return civName} // for debug - /** Returns true if the civ was fully initialized and has no cities or settlers remaining */ - fun isDefeated()= cities.isEmpty() + /** Returns true if the civ was fully initialized and has no cities remaining */ + fun isDefeated()= cities.isEmpty() // No cities && exploredTiles.isNotEmpty() // Dirty hack: exploredTiles are empty only before starting units are placed && !isBarbarian() // Barbarians can be never defeated && (citiesCreated > 0 || !getCivUnits().any { it.name == Constants.settler }) diff --git a/core/src/com/unciv/ui/cityscreen/CityScreen.kt b/core/src/com/unciv/ui/cityscreen/CityScreen.kt index b3a97f23..063d9474 100644 --- a/core/src/com/unciv/ui/cityscreen/CityScreen.kt +++ b/core/src/com/unciv/ui/cityscreen/CityScreen.kt @@ -125,7 +125,9 @@ class CityScreen(internal val city: CityInfo): CameraStageBaseScreen() { val razeCityButton = "Raze city".toTextButton() razeCityButton.labelCell.pad(10f) razeCityButton.onClick { city.isBeingRazed=true; update() } - if(!UncivGame.Current.worldScreen.isPlayersTurn) razeCityButton.disable() + if(!UncivGame.Current.worldScreen.isPlayersTurn || city.isOriginalCapital) + razeCityButton.disable() + razeCityButtonHolder.add(razeCityButton).colspan(cityPickerTable.columns) } else { val stopRazingCityButton = "Stop razing city".toTextButton() diff --git a/core/src/com/unciv/ui/worldscreen/AlertPopup.kt b/core/src/com/unciv/ui/worldscreen/AlertPopup.kt index 5021d5c3..565c1d4a 100644 --- a/core/src/com/unciv/ui/worldscreen/AlertPopup.kt +++ b/core/src/com/unciv/ui/worldscreen/AlertPopup.kt @@ -99,13 +99,15 @@ class AlertPopup(val worldScreen: WorldScreen, val popupAlert: PopupAlert): Popu addSeparator() - add("Raze".toTextButton().onClick { + add("Raze".toTextButton().apply { + if (city.isOriginalCapital) disable() + else onClick { city.puppetCity(conqueringCiv) city.annexCity() city.isBeingRazed = true worldScreen.shouldUpdate=true close() - }).row() + }}).row() addGoodSizedLabel("Razing the city annexes it, and starts razing the city to the ground.").row() addGoodSizedLabel("The population will gradually dwindle until the city is destroyed.").row() } else { From 048ce0d3c3b277940a7091cba174e88ce2e4bd13 Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Thu, 16 Apr 2020 09:41:29 +0200 Subject: [PATCH 2/2] Remove "Camp" hardcoding and allow mods to do similar things (#2417) --- android/assets/jsons/TileImprovements.json | 1 + .../logic/automation/WorkerAutomation.kt | 21 ++++++++++++++----- core/src/com/unciv/logic/map/TileInfo.kt | 2 +- .../models/ruleset/tile/TileImprovement.kt | 5 +++++ 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/android/assets/jsons/TileImprovements.json b/android/assets/jsons/TileImprovements.json index 6327ef2d..f5610045 100644 --- a/android/assets/jsons/TileImprovements.json +++ b/android/assets/jsons/TileImprovements.json @@ -39,6 +39,7 @@ // Resource-specific { "name": "Camp", + "resourceTerrainAllow": ["Forest"], "turnsToBuild": 7, "techRequired": "Trapping", "improvingTech": "Economics", diff --git a/core/src/com/unciv/logic/automation/WorkerAutomation.kt b/core/src/com/unciv/logic/automation/WorkerAutomation.kt index bab3b530..546e73e5 100644 --- a/core/src/com/unciv/logic/automation/WorkerAutomation.kt +++ b/core/src/com/unciv/logic/automation/WorkerAutomation.kt @@ -177,10 +177,10 @@ class WorkerAutomation(val unit: MapUnit) { private fun chooseImprovement(tile: TileInfo, civInfo: CivilizationInfo): TileImprovement? { val improvementStringForResource : String ?= when { tile.resource == null || !tile.hasViewableResource(civInfo) -> null - tile.terrainFeature == "Marsh" -> "Remove Marsh" - tile.terrainFeature == "Fallout" -> "Remove Fallout" - tile.terrainFeature == Constants.jungle -> "Remove Jungle" - tile.terrainFeature == Constants.forest && tile.getTileResource().improvement!="Camp" -> "Remove Forest" + tile.terrainFeature == "Marsh" && !isImprovementOnFeatureAllowed(tile,civInfo) -> "Remove Marsh" + tile.terrainFeature == "Fallout" && !isImprovementOnFeatureAllowed(tile,civInfo) -> "Remove Fallout" // for really mad modders + tile.terrainFeature == Constants.jungle && !isImprovementOnFeatureAllowed(tile,civInfo) -> "Remove Jungle" + tile.terrainFeature == Constants.forest && !isImprovementOnFeatureAllowed(tile,civInfo) -> "Remove Forest" else -> tile.getTileResource().improvement } @@ -197,7 +197,7 @@ class WorkerAutomation(val unit: MapUnit) { evaluateFortPlacement(tile,civInfo) -> "Fort" // I think we can assume that the unique improvement is better uniqueImprovement!=null && tile.canBuildImprovement(uniqueImprovement,civInfo) -> uniqueImprovement.name - + tile.terrainFeature == "Fallout" -> "Remove Fallout" tile.terrainFeature == "Marsh" -> "Remove Marsh" tile.terrainFeature == Constants.jungle -> "Trading post" @@ -211,6 +211,17 @@ class WorkerAutomation(val unit: MapUnit) { if (improvementString == null) return null return unit.civInfo.gameInfo.ruleSet.tileImprovements[improvementString]!! } + private fun isImprovementOnFeatureAllowed(tile: TileInfo, civInfo: CivilizationInfo): Boolean { + // Old hardcoded logic amounts to: + //return tile.terrainFeature == Constants.forest && tile.getTileResource().improvement == "Camp" + + // routine assumes the caller ensured that terrainFeature and resource are both present + val resourceImprovementName = tile.getTileResource().improvement + ?: return false + val resourceImprovement = civInfo.gameInfo.ruleSet.tileImprovements[resourceImprovementName] + ?: return false + return resourceImprovement.resourceTerrainAllow.contains(tile.terrainFeature!!) + } private fun isAcceptableTileForFort(tile: TileInfo, civInfo: CivilizationInfo): Boolean { diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index d95ad3fd..53d204dd 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -281,7 +281,7 @@ open class TileInfo { improvement.name == "Remove Road" && this.roadStatus == RoadStatus.Road -> true improvement.name == "Remove Railroad" && this.roadStatus == RoadStatus.Railroad -> true improvement.name == Constants.cancelImprovementOrder && this.improvementInProgress != null -> true - topTerrain.unbuildable && !(topTerrain.name == Constants.forest && improvement.name == "Camp") -> false + topTerrain.unbuildable && (topTerrain.name !in improvement.resourceTerrainAllow) -> false "Can only be built on Coastal tiles" in improvement.uniques && isCoastalTile() -> true else -> hasViewableResource(civInfo) && getTileResource().improvement == improvement.name } diff --git a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt index ea75f94c..2193fdbd 100644 --- a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt +++ b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt @@ -11,6 +11,11 @@ import kotlin.math.roundToInt class TileImprovement : NamedStats() { var terrainsCanBeBuiltOn: Collection = ArrayList() + + // Used only for Camp - but avoid hardcoded comparison and *allow modding* + // Terrain Features that need not be cleared if the improvement enables a resource + var resourceTerrainAllow: Collection = ArrayList() + var techRequired: String? = null var improvingTech: String? = null