diff --git a/core/src/com/unciv/Constants.kt b/core/src/com/unciv/Constants.kt index 03cec9c80b..da3eab525d 100644 --- a/core/src/com/unciv/Constants.kt +++ b/core/src/com/unciv/Constants.kt @@ -2,10 +2,7 @@ package com.unciv object Constants { const val worker = "Worker" - const val canBuildImprovements = "Can build [] improvements on tiles" - const val workBoatsUnique = "May create improvements on water resources" const val settler = "Settler" - const val settlerUnique = "Founds a new city" const val eraSpecificUnit = "Era Starting Unit" const val spreadReligionAbilityCount = "Spread Religion" const val removeHeresyAbilityCount = "Remove Foreign religions from your own cities" diff --git a/core/src/com/unciv/logic/GameStarter.kt b/core/src/com/unciv/logic/GameStarter.kt index 6e0e0c4605..5aab9b6b4a 100644 --- a/core/src/com/unciv/logic/GameStarter.kt +++ b/core/src/com/unciv/logic/GameStarter.kt @@ -262,7 +262,7 @@ object GameStarter { val startingLocations = getStartingLocations(allCivs, tileMap, landTilesInBigEnoughGroup, startScores) val settlerLikeUnits = ruleSet.units.filter { - it.value.uniqueObjects.any { unique -> unique.placeholderText == Constants.settlerUnique } + it.value.hasUnique(UniqueType.FoundCity) } // no starting units for Barbarians and Spectators @@ -315,9 +315,8 @@ object GameStarter { } if (unit == "Worker" && "Worker" !in ruleSet.units) { val buildableWorkerLikeUnits = ruleSet.units.filter { - it.value.uniqueObjects.any { unique -> unique.placeholderText == Constants.canBuildImprovements } - && it.value.isBuildable(civ) - && it.value.isCivilian() + it.value.hasUnique(UniqueType.BuildImprovements) && + it.value.isBuildable(civ) && it.value.isCivilian() } if (buildableWorkerLikeUnits.isEmpty()) return null // No workers in this mod return civ.getEquivalentUnit(buildableWorkerLikeUnits.keys.random()).name diff --git a/core/src/com/unciv/logic/automation/ConstructionAutomation.kt b/core/src/com/unciv/logic/automation/ConstructionAutomation.kt index 54781f9e18..ce6c7a61cf 100644 --- a/core/src/com/unciv/logic/automation/ConstructionAutomation.kt +++ b/core/src/com/unciv/logic/automation/ConstructionAutomation.kt @@ -1,6 +1,5 @@ package com.unciv.logic.automation -import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.city.CityConstructions import com.unciv.logic.city.PerpetualConstruction @@ -10,8 +9,8 @@ import com.unciv.logic.civilization.PlayerType import com.unciv.logic.map.BFS import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.VictoryType +import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.stats.Stat -import com.unciv.models.translations.equalsPlaceholderText import kotlin.math.min import kotlin.math.sqrt @@ -105,7 +104,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ if (!cityIsOverAverageProduction) modifier /= 5 // higher production cities will deal with this val civilianUnit = cityInfo.getCenterTile().civilianUnit - if (civilianUnit != null && civilianUnit.hasUnique(Constants.settlerUnique) + if (civilianUnit != null && civilianUnit.hasUnique(UniqueType.FoundCity) && cityInfo.getCenterTile().getTilesInDistance(5).none { it.militaryUnit?.civInfo == civInfo }) modifier = 5f // there's a settler just sitting here, doing nothing - BAD @@ -115,10 +114,10 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ private fun addWorkBoatChoice() { val buildableWorkboatUnits = cityInfo.cityConstructions.getConstructableUnits() - .filter { it.uniques.contains(Constants.workBoatsUnique) + .filter { it.hasUnique(UniqueType.CreateWaterImprovements) && Automation.allowSpendingResource(civInfo, it) } val canBuildWorkboat = buildableWorkboatUnits.any() - && !cityInfo.getTiles().any { it.civilianUnit?.hasUnique(Constants.workBoatsUnique) == true } + && !cityInfo.getTiles().any { it.civilianUnit?.hasUnique(UniqueType.CreateWaterImprovements) == true } if (!canBuildWorkboat) return val tilesThatNeedWorkboat = cityInfo.getTiles() .filter { it.isWater && it.hasViewableResource(civInfo) && it.improvement == null }.toList() @@ -139,9 +138,9 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ private fun addWorkerChoice() { val workerEquivalents = civInfo.gameInfo.ruleSet.units.values - .filter { it.uniques.any { - unique -> unique.equalsPlaceholderText(Constants.canBuildImprovements) - } && it.isBuildable(cityConstructions) + .filter { + it.hasUnique(UniqueType.BuildImprovements) + && it.isBuildable(cityConstructions) && Automation.allowSpendingResource(civInfo, it) } if (workerEquivalents.isEmpty()) return // for mods with no worker units if (civInfo.getIdleUnits().any { it.isAutomated() && it.hasUniqueToBuildImprovements }) diff --git a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt index 136512e2a3..39dc1074ae 100644 --- a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt @@ -689,12 +689,12 @@ object NextTurnAutomation { if (civInfo.cities.none() || civInfo.getHappiness() <= civInfo.cities.size + 5) return val settlerUnits = civInfo.gameInfo.ruleSet.units.values - .filter { it.uniques.contains(Constants.settlerUnique) && it.isBuildable(civInfo) } + .filter { it.hasUnique(UniqueType.FoundCity) && it.isBuildable(civInfo) } if (settlerUnits.isEmpty()) return - if (civInfo.getCivUnits().none { it.hasUnique(Constants.settlerUnique) } + if (civInfo.getCivUnits().none { it.hasUnique(UniqueType.FoundCity) } && civInfo.cities.none { val currentConstruction = it.cityConstructions.getCurrentConstruction() - currentConstruction is BaseUnit && currentConstruction.uniques.contains(Constants.settlerUnique) + currentConstruction is BaseUnit && currentConstruction.hasUnique(UniqueType.FoundCity) }) { val bestCity = civInfo.cities.maxByOrNull { it.cityStats.currentCityStats.production }!! diff --git a/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt index 3dbe984677..e8a1072735 100644 --- a/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/SpecificUnitAutomation.kt @@ -159,17 +159,23 @@ object SpecificUnitAutomation { && !unit.civInfo.isCityState() // ..unless you're a city state that was unable to settle its city on turn 1 && unit.getDamageFromTerrain() < unit.health) return // Also make sure we won't die waiting - val tilesNearCities = unit.civInfo.gameInfo.getCities().asSequence() - .flatMap { - val distanceAwayFromCity = - if (unit.civInfo.knows(it.civInfo) - // If the CITY OWNER knows that the UNIT OWNER agreed not to settle near them - && it.civInfo.getDiplomacyManager(unit.civInfo).hasFlag(DiplomacyFlags.AgreedToNotSettleNearUs)) - 6 - else 3 - it.getCenterTile().getTilesInDistance(distanceAwayFromCity) + val tilesNearCities = sequence { + for (city in unit.civInfo.gameInfo.getCities()) { + val center = city.getCenterTile() + if (unit.civInfo.knows(city.civInfo) && + // If the CITY OWNER knows that the UNIT OWNER agreed not to settle near them + city.civInfo.getDiplomacyManager(unit.civInfo).hasFlag(DiplomacyFlags.AgreedToNotSettleNearUs) + ) { + yieldAll(center.getTilesInDistance(6)) + continue } - .toSet() + for (tile in center.getTilesAtDistance(3)) { + if (tile.getContinent() == center.getContinent()) + yield(tile) + } + yieldAll(center.getTilesInDistance(2)) + } + }.toSet() // This is to improve performance - instead of ranking each tile in the area up to 19 times, do it once. val nearbyTileRankings = unit.getTile().getTilesInDistance(7) diff --git a/core/src/com/unciv/logic/automation/UnitAutomation.kt b/core/src/com/unciv/logic/automation/UnitAutomation.kt index 4645b903fa..e1dadab938 100644 --- a/core/src/com/unciv/logic/automation/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/UnitAutomation.kt @@ -101,7 +101,7 @@ object UnitAutomation { if (unit.isCivilian()) { if (tryRunAwayIfNeccessary(unit)) return - if (unit.hasUnique(Constants.settlerUnique)) + if (unit.hasUnique(UniqueType.FoundCity)) return SpecificUnitAutomation.automateSettlerActions(unit) if (unit.hasUniqueToBuildImprovements) @@ -119,7 +119,7 @@ object UnitAutomation { ) return SpecificUnitAutomation.enhanceReligion(unit) - if (unit.hasUnique(Constants.workBoatsUnique)) + if (unit.hasUnique(UniqueType.CreateWaterImprovements)) return SpecificUnitAutomation.automateWorkBoats(unit) if (unit.hasUnique("Bonus for units in 2 tile radius 15%")) @@ -311,7 +311,7 @@ object UnitAutomation { .firstOrNull { val tile = it.currentTile it.isCivilian() && - (it.hasUnique(Constants.settlerUnique) || unit.isGreatPerson()) + (it.hasUnique(UniqueType.FoundCity) || unit.isGreatPerson()) && tile.militaryUnit == null && unit.movement.canMoveTo(tile) && unit.movement.canReach(tile) } ?: return false unit.movement.headTowards(settlerOrGreatPersonToAccompany.currentTile) diff --git a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt index 5284968cbf..9979f6039e 100644 --- a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt +++ b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt @@ -1,6 +1,5 @@ package com.unciv.logic.civilization -import com.unciv.Constants import com.unciv.logic.automation.NextTurnAutomation import com.unciv.logic.civilization.diplomacy.* import com.unciv.models.metadata.GameSpeed @@ -23,7 +22,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { /** Attempts to initialize the city state, returning true if successful. */ fun initCityState(ruleset: Ruleset, startingEra: String, unusedMajorCivs: Collection): Boolean { val cityStateType = ruleset.nations[civInfo.civName]?.cityStateType - if (cityStateType == null) return false + ?: return false val startingTechs = ruleset.technologies.values.filter { it.uniques.contains("Starting tech") } for (tech in startingTechs) @@ -406,9 +405,8 @@ class CityStateFunctions(val civInfo: CivilizationInfo) { if (!civInfo.isCityState()) throw Exception("You can only demand workers from City-States!") val buildableWorkerLikeUnits = civInfo.gameInfo.ruleSet.units.filter { - it.value.uniqueObjects.any { unique -> unique.placeholderText == Constants.canBuildImprovements } - && it.value.isCivilian() - && it.value.isBuildable(civInfo) + it.value.hasUnique(UniqueType.BuildImprovements) && + it.value.isCivilian() && it.value.isBuildable(civInfo) } if (buildableWorkerLikeUnits.isEmpty()) return // Bad luck? demandingCiv.placeUnitNearTile(civInfo.getCapital().location, buildableWorkerLikeUnits.keys.random()) diff --git a/core/src/com/unciv/logic/civilization/CivInfoTransientUpdater.kt b/core/src/com/unciv/logic/civilization/CivInfoTransientUpdater.kt index 04e5e4695e..b54b60efc2 100644 --- a/core/src/com/unciv/logic/civilization/CivInfoTransientUpdater.kt +++ b/core/src/com/unciv/logic/civilization/CivInfoTransientUpdater.kt @@ -72,9 +72,9 @@ class CivInfoTransientUpdater(val civInfo: CivilizationInfo) { // And so, sequences to the rescue! val ownedTiles = civInfo.cities.asSequence().flatMap { it.getTiles() } newViewableTiles.addAll(ownedTiles) - val neighboringUnownedTiles = ownedTiles.flatMap { it.neighbors.filter { it.getOwner() != civInfo } } + val neighboringUnownedTiles = ownedTiles.flatMap { tile -> tile.neighbors.filter { it.getOwner() != civInfo } } newViewableTiles.addAll(neighboringUnownedTiles) - newViewableTiles.addAll(civInfo.getCivUnits().flatMap { it.viewableTiles.asSequence().filter { it.getOwner() != civInfo } }) + newViewableTiles.addAll(civInfo.getCivUnits().flatMap { unit -> unit.viewableTiles.asSequence().filter { it.getOwner() != civInfo } }) if (!civInfo.isCityState()) { for (otherCiv in civInfo.getKnownCivs()) { @@ -110,9 +110,7 @@ class CivInfoTransientUpdater(val civInfo: CivilizationInfo) { } if (civInfo.hasUnique("100 Gold for discovering a Natural Wonder (bonus enhanced to 500 Gold if first to discover it)")) { - if (!discoveredNaturalWonders.contains(tile.naturalWonder!!)) - goldGained += 500 - else goldGained += 100 + goldGained += if (discoveredNaturalWonders.contains(tile.naturalWonder!!)) 100 else 500 } if (goldGained > 0) { diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index 8458cc9dfc..2aa356211c 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -277,7 +277,7 @@ class MapUnit { cannotEnterOceanTiles = hasUnique(UniqueType.CannotEnterOcean) cannotEnterOceanTilesUntilAstronomy = hasUnique(UniqueType.CannotEnterOceanUntilAstronomy) - hasUniqueToBuildImprovements = hasUnique(Constants.canBuildImprovements) + hasUniqueToBuildImprovements = hasUnique(UniqueType.BuildImprovements) canEnterForeignTerrain = hasUnique("May enter foreign tiles without open borders, but loses [] religious strength each turn it ends there") || hasUnique("May enter foreign tiles without open borders") @@ -1051,7 +1051,7 @@ class MapUnit { } fun canBuildImprovement(improvement: TileImprovement, tile: TileInfo = currentTile): Boolean { - val matchingUniques = getMatchingUniques(Constants.canBuildImprovements) + val matchingUniques = getMatchingUniques(UniqueType.BuildImprovements) return matchingUniques.any { improvement.matchesFilter(it.params[0]) || tile.matchesTerrainFilter(it.params[0]) } } diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt index 5fc8cc9022..5844697278 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueParameterType.kt @@ -8,6 +8,7 @@ import com.unciv.models.stats.Stat // parameterName values should be compliant with autogenerated values in TranslationFileWriter.generateStringsFromJSONs // Eventually we'll merge the translation generation to take this as the source of that +@Suppress("unused") // Some are used only via enumerating the enum matching on parameterName enum class UniqueParameterType(val parameterName:String) { Number("amount") { override fun getErrorSeverity(parameterText: String, ruleset: Ruleset): @@ -145,6 +146,16 @@ enum class UniqueParameterType(val parameterName:String) { else -> UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific } }, + /** should mirror TileImprovement.matchesFilter exactly */ + ImprovementFilter("improvementFilter") { + private val knownValues = setOf("All", "All Road", "Great Improvement", "Great") + override fun getErrorSeverity(parameterText: String, ruleset: Ruleset): + UniqueType.UniqueComplianceErrorSeverity? { + if (parameterText in knownValues) return null + if (ruleset.tileImprovements.containsKey(parameterText)) return null + return UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific + } + }, Resource("resource") { override fun getErrorSeverity(parameterText: String, ruleset: Ruleset): UniqueType.UniqueComplianceErrorSeverity? = when (parameterText) { diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt index 2a57bde296..f312a53348 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt @@ -1,6 +1,7 @@ package com.unciv.models.ruleset.unique import com.badlogic.gdx.math.Vector2 +import com.unciv.Constants import com.unciv.logic.city.CityInfo import com.unciv.logic.civilization.* import com.unciv.logic.map.MapUnit @@ -33,7 +34,7 @@ object UniqueTriggerActivation { OneTimeFreeUnit -> { val unitName = unique.params[0] val unit = civInfo.gameInfo.ruleSet.units[unitName] - if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger())) + if (chosenCity == null || unit == null || (unit.hasUnique(UniqueType.FoundCity) && civInfo.isOneCityChallenger())) return false val placedUnit = civInfo.addUnit(unitName, chosenCity) @@ -49,7 +50,7 @@ object UniqueTriggerActivation { OneTimeAmountFreeUnits -> { val unitName = unique.params[1] val unit = civInfo.gameInfo.ruleSet.units[unitName] - if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger())) + if (chosenCity == null || unit == null || (unit.hasUnique(UniqueType.FoundCity) && civInfo.isOneCityChallenger())) return false val tilesUnitsWerePlacedOn: MutableList = mutableListOf() diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 96f47d8a33..7a73f8bb8b 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -124,7 +124,10 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) { ///////////////////////////////////////// UNIT UNIQUES ///////////////////////////////////////// - + FoundCity("Founds a new city", UniqueTarget.Unit), + BuildImprovements("Can build [improvementFilter/terrainFilter] improvements on tiles", UniqueTarget.Unit), + CreateWaterImprovements("May create improvements on water resources", UniqueTarget.Unit), + Strength("[amount]% Strength", UniqueTarget.Unit, UniqueTarget.Global), StrengthNearCapital("[amount]% Strength decreasing with distance from the capital", UniqueTarget.Unit), diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index d25027b20a..122b3c2fd8 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -403,7 +403,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { }) } - if (uniques.contains(Constants.settlerUnique) && + if (hasUnique(UniqueType.FoundCity) && (civInfo.isCityState() || civInfo.isOneCityChallenger()) ) rejectionReasons.add(RejectionReason.NoSettlerForOneCityPlayers) diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index 959f4eaf0e..091dcdf680 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -128,7 +128,7 @@ object UnitActions { fun getWaterImprovementAction(unit: MapUnit): UnitAction? { val tile = unit.currentTile - if (!tile.isWater || !unit.hasUnique(Constants.workBoatsUnique) || tile.resource == null) return null + if (!tile.isWater || !unit.hasUnique(UniqueType.CreateWaterImprovements) || tile.resource == null) return null val improvementName = tile.getTileResource().improvement ?: return null val improvement = tile.ruleset.tileImprovements[improvementName] ?: return null @@ -161,9 +161,11 @@ object UnitActions { * (no movement left, too close to another city). */ fun getFoundCityAction(unit: MapUnit, tile: TileInfo): UnitAction? { - if (!unit.hasUnique("Founds a new city") || tile.isWater || tile.isImpassible()) return null + if (!unit.hasUnique(UniqueType.FoundCity) || tile.isWater || tile.isImpassible()) return null - if (unit.currentMovement <= 0 || tile.getTilesInDistance(3).any { it.isCityCenter() }) + if (unit.currentMovement <= 0 || + tile.getTilesInDistance(2).any { it.isCityCenter() } || + tile.getTilesAtDistance(3).any { it.isCityCenter() && it.getContinent() == tile.getContinent() }) return UnitAction(UnitActionType.FoundCity, action = null) val foundAction = { diff --git a/tests/src/com/unciv/testing/SerializationTests.kt b/tests/src/com/unciv/testing/SerializationTests.kt index fd83d2cf1d..ac339a1452 100644 --- a/tests/src/com/unciv/testing/SerializationTests.kt +++ b/tests/src/com/unciv/testing/SerializationTests.kt @@ -14,6 +14,7 @@ import com.unciv.models.metadata.GameSettings import com.unciv.models.metadata.Player import com.unciv.models.ruleset.RulesetCache import com.unciv.models.metadata.GameSetupInfo +import com.unciv.models.ruleset.unique.UniqueType import org.junit.After import org.junit.Assert import org.junit.Before @@ -68,7 +69,7 @@ class SerializationTests { // Found a city otherwise too many classes have no instance and are not tested val civ = game.getCurrentPlayerCivilization() - val unit = civ.getCivUnits().first { it.hasUnique(Constants.settlerUnique) } + val unit = civ.getCivUnits().first { it.hasUnique(UniqueType.FoundCity) } val tile = unit.getTile() unit.civInfo.addCity(tile.position) if (tile.ruleset.tileImprovements.containsKey("City center"))