diff --git a/core/src/com/unciv/logic/city/CityInfo.kt b/core/src/com/unciv/logic/city/CityInfo.kt index 103a87ba00..86787b990c 100644 --- a/core/src/com/unciv/logic/city/CityInfo.kt +++ b/core/src/com/unciv/logic/city/CityInfo.kt @@ -11,6 +11,7 @@ import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileMap import com.unciv.models.Counter import com.unciv.models.ruleset.Unique +import com.unciv.models.ruleset.UniqueType import com.unciv.models.ruleset.tile.ResourceSupplyList import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.unit.BaseUnit @@ -292,10 +293,10 @@ class CityInfo { cityResources.add( resource, unique.params[0].toInt() * civInfo.getResourceModifier(resource), - "Tiles" + "Improvements" ) } - if (unique.placeholderText == "Consumes [] []") { + if (unique.matches(UniqueType.ConsumesResources, getRuleset())) { val resource = getRuleset().tileResources[unique.params[1]] ?: continue cityResources.add( resource, diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index 74b8775fc2..a76b3edbd3 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -7,6 +7,7 @@ import com.unciv.logic.HexMath import com.unciv.logic.city.CityInfo import com.unciv.logic.civilization.CivilizationInfo import com.unciv.models.ruleset.Ruleset +import com.unciv.models.ruleset.UniqueType import com.unciv.models.ruleset.tile.* import com.unciv.models.stats.Stats import com.unciv.models.translations.tr @@ -387,7 +388,7 @@ open class TileInfo { matchesTerrainFilter(it.params[0]) && !civInfo.tech.isResearched(it.params[1]) } -> false improvement.uniqueObjects.any { - it.placeholderText == "Consumes [] []" + it.matches(UniqueType.ConsumesResources, ruleset) && civInfo.getCivResourcesByName()[it.params[1]]!! < it.params[0].toInt() } -> false else -> canImprovementBeBuiltHere(improvement, hasViewableResource(civInfo)) diff --git a/core/src/com/unciv/models/ruleset/Building.kt b/core/src/com/unciv/models/ruleset/Building.kt index 810948523b..d1dd2801ad 100644 --- a/core/src/com/unciv/models/ruleset/Building.kt +++ b/core/src/com/unciv/models/ruleset/Building.kt @@ -95,7 +95,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText { if (!tileBonusHashmap.containsKey(stats)) tileBonusHashmap[stats] = ArrayList() tileBonusHashmap[stats]!!.add(unique.params[1]) } - unique.placeholderText == "Consumes [] []" -> Unit // skip these, + unique.isOfType(UniqueType.ConsumesResources) -> Unit // skip these, else -> yield(unique.text) } for ((key, value) in tileBonusHashmap) @@ -706,7 +706,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText { val resourceRequirements = HashMap() if (requiredResource != null) resourceRequirements[requiredResource!!] = 1 for (unique in uniqueObjects) - if (unique.placeholderText == "Consumes [] []") + if (unique.isOfType(UniqueType.ConsumesResources)) resourceRequirements[unique.params[1]] = unique.params[0].toInt() return resourceRequirements } diff --git a/core/src/com/unciv/models/ruleset/Unique.kt b/core/src/com/unciv/models/ruleset/Unique.kt index 2ee4adbcab..4b1b484f36 100644 --- a/core/src/com/unciv/models/ruleset/Unique.kt +++ b/core/src/com/unciv/models/ruleset/Unique.kt @@ -14,9 +14,68 @@ import com.unciv.models.translations.hasPlaceholderParameters import com.unciv.ui.worldscreen.unit.UnitActions import kotlin.random.Random -class Unique(val text:String){ + +// 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 +enum class UniqueParameterType(val parameterName:String, val complianceCheck:(String, Ruleset)->Boolean) { + Number("amount", { s, r -> s.toIntOrNull() != null }), + UnitFilter("unitType", { s, r -> r.unitTypes.containsKey(s) || unitTypeStrings.contains(s) }), + Unknown("",{s,r -> true}); + + companion object { + val unitTypeStrings = hashSetOf( + "Military", + "Civilian", + "non-air", + "relevant", + "Nuclear Weapon", + "City", + // These are up for debate + "Air", + "land units", + "water units", + "air units", + "military units", + "submarine units", + // Note: this can't handle combinations of parameters (e.g. [{Military} {Water}]) + ) + } +} + +enum class UniqueType(val text:String) { + + ConsumesResources("Consumes [amount] [resource]"); + + /** For uniques that have "special" parameters that can accept multiple types, we can override them manually + * For 95% of cases, auto-matching is fine. */ + private val parameterTypeMap = ArrayList>() + + init { + for (placeholder in text.getPlaceholderParameters()) { + val matchingParameterType = + UniqueParameterType.values().firstOrNull { it.parameterName == placeholder } + ?: UniqueParameterType.Unknown + parameterTypeMap.add(listOf(matchingParameterType)) + } + } + + val placeholderText = text.getPlaceholderText() + + fun checkCompliance(unique: Unique, ruleset: Ruleset): Boolean { + for ((index, param) in unique.params.withIndex()) + if (parameterTypeMap[index].none { it.complianceCheck(param, ruleset) }) + return false + return true + } +} + + + +class Unique(val text:String) { val placeholderText = text.getPlaceholderText() val params = text.getPlaceholderParameters() + val type = UniqueType.values().firstOrNull { it.placeholderText == placeholderText } + /** This is so the heavy regex-based parsing is only activated once per unique, instead of every time it's called * - for instance, in the city screen, we call every tile unique for every tile, which can lead to ANRs */ val stats: Stats by lazy { @@ -24,6 +83,11 @@ class Unique(val text:String){ if (firstStatParam == null) Stats() // So badly-defined stats don't crash the entire game else Stats.parse(firstStatParam) } + + + fun isOfType(uniqueType: UniqueType) = uniqueType == type + fun matches(uniqueType: UniqueType, ruleset: Ruleset) = isOfType(uniqueType) + && uniqueType.checkCompliance(this, ruleset) } class UniqueMap:HashMap>() { @@ -116,7 +180,7 @@ object UniqueTriggerActivation { return placedUnit != null } - + // spectators get all techs at start of game, and if (in a mod) a tech gives a free policy, the game gets stuck on the policy picker screen "Free Social Policy" -> { if (civInfo.isSpectator()) return false @@ -201,7 +265,7 @@ object UniqueTriggerActivation { } return true } - + "Free Technology" -> { if (civInfo.isSpectator()) return false civInfo.tech.freeTechs += 1 @@ -457,8 +521,8 @@ object UniqueTriggerActivation { civInfo.addNotification(notification, NotificationIcon.Diplomacy) return true } - - "Provides the cheapest [] building in your first [] cities for free", + + "Provides the cheapest [] building in your first [] cities for free", "Provides a [] in your first [] cities for free" -> civInfo.civConstructions.tryAddFreeBuildings() } diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index 91f62c567f..96dd1da06e 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -7,6 +7,7 @@ import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.MapUnit import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Unique +import com.unciv.models.ruleset.UniqueType import com.unciv.models.stats.INamed import com.unciv.models.stats.Stat import com.unciv.models.translations.tr @@ -516,7 +517,7 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText { val resourceRequirements = HashMap() if (requiredResource != null) resourceRequirements[requiredResource!!] = 1 for (unique in uniqueObjects) - if (unique.placeholderText == "Consumes [] []") + if (unique.isOfType(UniqueType.ConsumesResources)) resourceRequirements[unique.params[1]] = unique.params[0].toInt() return resourceRequirements } diff --git a/core/src/com/unciv/models/translations/TranslationFileWriter.kt b/core/src/com/unciv/models/translations/TranslationFileWriter.kt index fc077f3e14..13452e7d34 100644 --- a/core/src/com/unciv/models/translations/TranslationFileWriter.kt +++ b/core/src/com/unciv/models/translations/TranslationFileWriter.kt @@ -200,22 +200,7 @@ object TranslationFileWriter { "Buildings", "Building" )) } - val unitTypeMap = ruleset.unitTypes.keys.toMutableSet().apply { addAll(sequenceOf( - "Military", - "Civilian", - "non-air", - "relevant", - "Nuclear Weapon", - "City", - // These are up for debate - "Air", - "land units", - "water units", - "air units", - "military units", - "submarine units", - // Note: this can't handle combinations of parameters (e.g. [{Military} {Water}]) - )) } + val unitTypeMap = ruleset.unitTypes.keys.toMutableSet().apply { addAll(UniqueParameterType.unitTypeStrings) } val cityFilterMap = setOf( "in this city", "in all cities", diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index 1691e0f7d4..a763ca4900 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -16,6 +16,7 @@ import com.unciv.models.UncivSound import com.unciv.models.UnitAction import com.unciv.models.UnitActionType import com.unciv.models.ruleset.Building +import com.unciv.models.ruleset.UniqueType import com.unciv.models.stats.Stat import com.unciv.models.stats.Stats import com.unciv.models.translations.tr @@ -594,7 +595,7 @@ object UnitActions { var resourcesAvailable = true if (improvement.uniqueObjects.any { - it.placeholderText == "Consumes [] []" && civResources[unique.params[1]] ?: 0 < unique.params[0].toInt() + it.matches(UniqueType.ConsumesResources, tile.ruleset) && civResources[unique.params[1]] ?: 0 < unique.params[0].toInt() }) resourcesAvailable = false