From 1b1a910eef018def23d32618146cec497d31e873 Mon Sep 17 00:00:00 2001 From: SimonCeder <63475501+SimonCeder@users.noreply.github.com> Date: Sun, 17 Oct 2021 23:05:17 +0200 Subject: [PATCH 1/4] Can now raze cities Austria has married (#5506) --- core/src/com/unciv/logic/civilization/CityStateFunctions.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt index bb962c3307..5d531ebc1e 100644 --- a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt +++ b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt @@ -294,6 +294,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { city.moveToCiv(otherCiv) city.isPuppet = true // Human players get a popup that allows them to annex instead city.foundingCiv = "" // This is no longer a city-state + city.isOriginalCapital = false // It's now an ordinary city and can be razed in later conquests } civInfo.destroy() } From 524dfa53648a6333ddda126dbfe1ef6bdebb8480 Mon Sep 17 00:00:00 2001 From: Xander Lenstra <71121390+xlenstra@users.noreply.github.com> Date: Mon, 18 Oct 2021 15:37:18 +0200 Subject: [PATCH 2/4] Band-aided a bug with building unique application (#5512) Building uniques applying to all cities, such as Temple of Artemis and Sistene Chapel would apply their bonus twice to the city they were build in. This was a result of it both being found as a non-local unique due to it having an effect outside the city and thus being classified as wonder, and as a building, as it was built in the city and had an effect on it. This patch is _untested_ and only a band-aid. The real solution would be to start using the sources of uniques saved in the uniques themselves to classify were uniques are received from. Implementing this is somewhere on my todo-list, but due to uni won't happen anytime soon. --- core/src/com/unciv/logic/civilization/CivilizationInfo.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index d42fca4fb7..f8bcfae8ce 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -343,7 +343,7 @@ class CivilizationInfo { cities.asSequence().flatMap { city -> if (cityItIsFor != null && city == cityItIsFor) - city.getAllUniquesWithNonLocalEffects().filter { it.params.none { param -> param == "in other cities" } } + city.getAllUniquesWithNonLocalEffects().filter { it.params.none { param -> param == "in other cities" || param == "in all cities" } } else city.getAllUniquesWithNonLocalEffects() } From 086867731d05f2ba143c3e1b43df284ab09f3d7f Mon Sep 17 00:00:00 2001 From: Xander Lenstra <71121390+xlenstra@users.noreply.github.com> Date: Tue, 19 Oct 2021 07:27:46 +0200 Subject: [PATCH 3/4] Enumified all terrain uniques in terrains.json (#5513) * Enumified all terrain uniques in the json * it * Fixed unit tests --- core/src/com/unciv/logic/BarbarianManager.kt | 3 ++- core/src/com/unciv/logic/battle/Battle.kt | 12 +++++------ .../com/unciv/logic/battle/CityCombatant.kt | 6 +++--- .../logic/civilization/CivilizationInfo.kt | 3 +-- core/src/com/unciv/logic/map/MapUnit.kt | 6 ++---- core/src/com/unciv/logic/map/TileInfo.kt | 2 +- core/src/com/unciv/logic/map/TileMap.kt | 17 ++++++++------- .../logic/map/mapgenerator/MapGenerator.kt | 20 ++++++++++-------- .../com/unciv/models/ruleset/tile/Terrain.kt | 7 +++---- .../unciv/models/ruleset/unique/UniqueType.kt | 21 +++++++++++++++++-- 10 files changed, 58 insertions(+), 39 deletions(-) diff --git a/core/src/com/unciv/logic/BarbarianManager.kt b/core/src/com/unciv/logic/BarbarianManager.kt index 68be5a7998..db65fe7416 100644 --- a/core/src/com/unciv/logic/BarbarianManager.kt +++ b/core/src/com/unciv/logic/BarbarianManager.kt @@ -6,6 +6,7 @@ import com.unciv.logic.civilization.NotificationIcon import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileMap import com.unciv.models.metadata.GameSpeed +import com.unciv.models.ruleset.unique.UniqueType import java.util.* import kotlin.collections.HashMap import kotlin.math.max @@ -210,7 +211,7 @@ class Encampment { || it.isCityCenter() || it.getFirstUnit() != null || (it.isWater && !canSpawnBoats) - || (it.hasUnique("Fresh water") && it.isWater) // No Lakes + || (it.hasUnique(UniqueType.FreshWater) && it.isWater) // No Lakes } if (validTiles.isEmpty()) return false diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index 3d4150c00b..e49eaed1d1 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -704,13 +704,13 @@ object Battle { tile.turnsToImprovement = 0 tile.roadStatus = RoadStatus.None if (tile.isLand && !tile.isImpassible() && !tile.terrainFeatures.contains("Fallout")) { - if (tile.terrainFeatures.any { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.uniques.contains("Resistant to nukes") }) { + if (tile.terrainFeatures.any { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.ResistsNukes) }) { if (Random().nextFloat() < 0.25f) { - tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.uniques.contains("Can be destroyed by nukes") } + tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.DestroyableByNukes) } tile.terrainFeatures.add("Fallout") } } else if (Random().nextFloat() < 0.5f) { - tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.uniques.contains("Can be destroyed by nukes") } + tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.DestroyableByNukes) } tile.terrainFeatures.add("Fallout") } } @@ -767,13 +767,13 @@ object Battle { tile.turnsToImprovement = 0 tile.roadStatus = RoadStatus.None if (tile.isLand && !tile.isImpassible() && !tile.terrainFeatures.contains("Fallout")) { - if (tile.terrainFeatures.any { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.uniques.contains("Resistant to nukes") }) { + if (tile.terrainFeatures.any { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.ResistsNukes) }) { if (Random().nextFloat() < 0.25f) { - tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.uniques.contains("Can be destroyed by nukes") } + tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.DestroyableByNukes) } tile.terrainFeatures.add("Fallout") } } else if (Random().nextFloat() < 0.5f) { - tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.uniques.contains("Can be destroyed by nukes") } + tile.terrainFeatures.removeAll { attacker.getCivInfo().gameInfo.ruleSet.terrains[it]!!.hasUnique(UniqueType.DestroyableByNukes) } tile.terrainFeatures.add("Fallout") } } diff --git a/core/src/com/unciv/logic/battle/CityCombatant.kt b/core/src/com/unciv/logic/battle/CityCombatant.kt index 1fad1b63c1..9343d898c7 100644 --- a/core/src/com/unciv/logic/battle/CityCombatant.kt +++ b/core/src/com/unciv/logic/battle/CityCombatant.kt @@ -4,6 +4,7 @@ import com.unciv.logic.city.CityInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.TileInfo import com.unciv.models.UncivSound +import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.UnitType import kotlin.math.pow import kotlin.math.roundToInt @@ -39,9 +40,8 @@ class CityCombatant(val city: CityInfo) : ICombatant { var strength = 8f strength += (city.population.population / 5) * 2 // Each 5 pop gives 2 defence val cityTile = city.getCenterTile() - for (unique in cityTile.getAllTerrains().flatMap { it.uniqueObjects }) - if (unique.placeholderText == "[] Strength for cities built on this terrain") - strength += unique.params[0].toInt() + for (unique in cityTile.getAllTerrains().flatMap { it.getMatchingUniques(UniqueType.GrantsCityStrength) }) + strength += unique.params[0].toInt() // as tech progresses so does city strength val techCount = getCivInfo().gameInfo.ruleSet.technologies.count() val techsPercentKnown: Float = if (techCount > 0) city.civInfo.tech.techsResearched.count().toFloat() / techCount else 0.5f // for mods with no tech diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index f8bcfae8ce..2fcf3dc835 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -1,7 +1,6 @@ package com.unciv.logic.civilization import com.badlogic.gdx.math.Vector2 -import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.UncivShowableException @@ -714,7 +713,7 @@ class CivilizationInfo { passThroughImpassableUnlocked = passableImpassables.isNotEmpty() // Cache whether this civ gets nonstandard terrain damage for performance reasons. - nonStandardTerrainDamage = getMatchingUniques("Units ending their turn on [] tiles take [] damage") + nonStandardTerrainDamage = getMatchingUniques(UniqueType.DamagesContainingUnits) .any { gameInfo.ruleSet.terrains[it.params[0]]!!.damagePerTurn != it.params[1].toInt() } // Cache the last era each resource is used for buildings or units respectively for AI building evaluation diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index c1289c5568..4388693aa7 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -614,9 +614,7 @@ class MapUnit { tile.roadStatus = RoadStatus.None else { val removedFeatureObject = tile.ruleset.terrains[removedFeatureName] - if (removedFeatureObject != null && removedFeatureObject.uniques - .contains("Provides a one-time Production bonus to the closest city when cut down") - ) { + if (removedFeatureObject != null && removedFeatureObject.hasUnique(UniqueType.ProductionBonusWhenRemoved)) { tryProvideProductionToClosestCity(removedFeatureName) } tile.terrainFeatures.remove(removedFeatureName) @@ -1006,7 +1004,7 @@ class MapUnit { fun getDamageFromTerrain(tile: TileInfo = currentTile): Int { if (civInfo.nonStandardTerrainDamage) { - for (unique in getMatchingUniques("Units ending their turn on [] tiles take [] damage")) { + for (unique in getMatchingUniques(UniqueType.DamagesContainingUnits)) { if (unique.params[0] in tile.getAllTerrains().map { it.name }) { return unique.params[1].toInt() // Use the damage from the unique } diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index 88c767f234..41391cc18f 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -436,7 +436,7 @@ open class TileInfo { && getTileImprovement().let { it != null && it.hasUnique("Irremovable") } -> false // Terrain blocks most improvements - getAllTerrains().any { it.getMatchingUniques("Only [] improvements may be built on this tile") + getAllTerrains().any { it.getMatchingUniques(UniqueType.RestrictedBuildableImprovements) .any { unique -> !improvement.matchesFilter(unique.params[0]) } } -> false // Decide cancelImprovementOrder earlier, otherwise next check breaks it diff --git a/core/src/com/unciv/logic/map/TileMap.kt b/core/src/com/unciv/logic/map/TileMap.kt index 16e6e0871a..749d8e7a98 100644 --- a/core/src/com/unciv/logic/map/TileMap.kt +++ b/core/src/com/unciv/logic/map/TileMap.kt @@ -317,14 +317,17 @@ class TileMap { This can all be summed up as "I can see c if a>b || c>b || (a==b && b !blocks same-elevation view)" */ - val containsViewableNeighborThatCanSeeOver = cTile.neighbors.any { - bNeighbor: TileInfo -> + val containsViewableNeighborThatCanSeeOver = cTile.neighbors.any { bNeighbor: TileInfo -> val bNeighborHeight = bNeighbor.height - viewableTiles.contains(bNeighbor) && ( - currentTileHeight > bNeighborHeight // a>b - || cTileHeight > bNeighborHeight // c>b - || currentTileHeight == bNeighborHeight // a==b - && !bNeighbor.hasUnique("Blocks line-of-sight from tiles at same elevation")) + viewableTiles.contains(bNeighbor) + && ( + currentTileHeight > bNeighborHeight // a>b + || cTileHeight > bNeighborHeight // c>b + || ( + currentTileHeight == bNeighborHeight // a==b + && !bNeighbor.hasUnique(UniqueType.BlocksLineOfSightAtSameElevation) + ) + ) } if (containsViewableNeighborThatCanSeeOver) tilesToAddInDistanceI.add(cTile) } diff --git a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt index 2a6ee2ce34..28a568744b 100644 --- a/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt +++ b/core/src/com/unciv/logic/map/mapgenerator/MapGenerator.kt @@ -9,6 +9,7 @@ import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.Terrain import com.unciv.models.ruleset.tile.TerrainType +import com.unciv.models.ruleset.unique.UniqueType import kotlin.math.abs import kotlin.math.max import kotlin.math.pow @@ -223,9 +224,11 @@ class MapGenerator(val ruleset: Ruleset) { * [MapParameters.elevationExponent] favors high elevation */ private fun raiseMountainsAndHills(tileMap: TileMap) { - val mountain = ruleset.terrains.values.firstOrNull { it.uniques.contains("Occurs in chains at high elevations") }?.name - val hill = ruleset.terrains.values.firstOrNull { it.uniques.contains("Occurs in groups around high elevations") }?.name - val flat = ruleset.terrains.values.firstOrNull { !it.impassable && it.type == TerrainType.Land && !it.uniques.contains("Rough Terrain") }?.name + val mountain = ruleset.terrains.values.firstOrNull { it.hasUnique(UniqueType.OccursInChains) }?.name + val hill = ruleset.terrains.values.firstOrNull { it.hasUnique(UniqueType.OccursInGroups) }?.name + val flat = ruleset.terrains.values.firstOrNull { + !it.impassable && it.type == TerrainType.Land && !it.hasUnique(UniqueType.RoughTerrain) + }?.name if (flat == null) { println("Ruleset seems to contain no flat terrain - can't generate heightmap") @@ -358,12 +361,11 @@ class MapGenerator(val ruleset: Ruleset) { val tempFrom: Float, val tempTo: Float, val humidFrom: Float, val humidTo: Float ) + // List is OK here as it's only sequentially scanned val limitsMap: List = - // List is OK here as it's only sequentially scanned - ruleset.terrains.values.flatMap { terrain -> - terrain.uniqueObjects.filter { - it.placeholderText == "Occurs at temperature between [] and [] and humidity between [] and []" - }.map { unique -> + ruleset.terrains.values.flatMap { terrain -> + terrain.getMatchingUniques(UniqueType.TileGenerationConditions) + .map { unique -> TerrainOccursRange(terrain, unique.params[0].toFloat(), unique.params[1].toFloat(), unique.params[2].toFloat(), unique.params[3].toFloat()) @@ -432,7 +434,7 @@ class MapGenerator(val ruleset: Ruleset) { */ private fun spawnRareFeatures(tileMap: TileMap) { val rareFeatures = ruleset.terrains.values.filter { - it.type == TerrainType.TerrainFeature && it.uniques.contains("Rare feature") + it.type == TerrainType.TerrainFeature && it.hasUnique(UniqueType.RareFeature) } for (tile in tileMap.values.asSequence().filter { it.terrainFeatures.isEmpty() }) { if (randomness.RNG.nextDouble() <= tileMap.mapParameters.rareFeaturesRichness) { diff --git a/core/src/com/unciv/models/ruleset/tile/Terrain.kt b/core/src/com/unciv/models/ruleset/tile/Terrain.kt index 091e8348da..de02bdf421 100644 --- a/core/src/com/unciv/models/ruleset/tile/Terrain.kt +++ b/core/src/com/unciv/models/ruleset/tile/Terrain.kt @@ -40,7 +40,8 @@ class Terrain : RulesetStatsObject() { @Transient var damagePerTurn = 0 - fun isRough(): Boolean = uniques.contains("Rough terrain") + // Shouldn't this just be a lazy property so it's automatically cached? + fun isRough(): Boolean = hasUnique(UniqueType.RoughTerrain) /** Tests base terrains, features and natural wonders whether they should be treated as Land/Water. * Currently only used for civilopedia display, as other code can test the tile itself. @@ -141,8 +142,6 @@ class Terrain : RulesetStatsObject() { } fun setTransients() { - damagePerTurn = uniqueObjects.sumOf { - if (it.placeholderText == "Units ending their turn on this terrain take [] damage") it.params[0].toInt() else 0 - } + damagePerTurn = getMatchingUniques(UniqueType.DamagesContainingUnits).sumOf { it.params[0].toInt() } } } diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 8199c89c5b..beb5d740b7 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -253,16 +253,33 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { // The "Except [terrainFilter]" could theoretically be implemented with a conditional NaturalWonderConvertNeighborsExcept("Neighboring tiles except [baseTerrain] will convert to [baseTerrain]", UniqueTarget.Terrain), + DamagesContainingUnits("Units ending their turn on this terrain take [amount] damage", UniqueTarget.Terrain), TerrainGrantsPromotion("Grants [promotion] ([comment]) to adjacent [mapUnitFilter] units for the rest of the game", UniqueTarget.Terrain), + GrantsCityStrength("[amount] Strength for cities built on this terrain", UniqueTarget.Terrain), + ProductionBonusWhenRemoved("Provides a one-time Production bonus to the closest city when cut down", UniqueTarget.Terrain), TileProvidesYieldWithoutPopulation("Tile provides yield without assigned population", UniqueTarget.Terrain, UniqueTarget.Improvement), NullifyYields("Nullifies all other stats this tile provides", UniqueTarget.Terrain), + RestrictedBuildableImprovements("Only [improvementFilter] improvements may be built on this tile", UniqueTarget.Terrain), + + BlocksLineOfSightAtSameElevation("Blocks line-of-sight from tiles at same elevation", UniqueTarget.Terrain), VisibilityElevation("Has an elevation of [amount] for visibility calculations", UniqueTarget.Terrain), NoNaturalGeneration("Doesn't generate naturally", UniqueTarget.Terrain), - + TileGenerationConditions("Occurs at temperature between [amount] and [amount] and humidity between [amount] and [amount]", UniqueTarget.Terrain), + OccursInChains("Occurs in chains at high elevations", UniqueTarget.Terrain), + OccursInGroups("Occurs in groups around high elevations", UniqueTarget.Terrain), + RareFeature("Rare feature", UniqueTarget.Terrain), + + ResistsNukes("Resistant to nukes", UniqueTarget.Terrain), + DestroyableByNukes("Can be destroyed by nukes", UniqueTarget.Terrain), + + FreshWater("Fresh water", UniqueTarget.Terrain), + RoughTerrain("Rough terrain", UniqueTarget.Terrain), + + // Resource uniques OverrideDepositAmountOnTileFilter("Deposits in [tileFilter] tiles always provide [amount] resources", UniqueTarget.Resource), - + ///////////////////////////////////////// CONDITIONALS ///////////////////////////////////////// From 8cd89deb712af448be5f187b5d653d2070e8280f Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky Date: Tue, 19 Oct 2021 19:47:35 +0300 Subject: [PATCH 4/4] Added information about technology required for improving resource to tile info. (#5509) * Added information about technology required for improving resource to tile info. * Tile improvement required tech info updated. --- core/src/com/unciv/logic/map/TileInfo.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index 41391cc18f..3ba18ee778 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -630,6 +630,17 @@ open class TileInfo { FormattedLine("{$resource} ($resourceAmount)", link="Resource/$resource") else FormattedLine(resource!!, link="Resource/$resource") + if (resource != null && viewingCiv != null && hasViewableResource(viewingCiv)) { + val tileImprovement = ruleset.tileImprovements[getTileResource().improvement] + if (tileImprovement?.techRequired != null + && !viewingCiv.tech.isResearched(tileImprovement.techRequired!!)) { + lineList += FormattedLine( + "Requires [${tileImprovement.techRequired}]", + link="Technology/${tileImprovement.techRequired}", + color= "#FAA" + ) + } + } if (naturalWonder != null) lineList += FormattedLine(naturalWonder!!, link="Terrain/$naturalWonder") if (roadStatus !== RoadStatus.None && !isCityCenter())