Added basic functionality for uniques enum (#5222)

* Added basic functionality for uniques enum

* Added unique type to Unique class for faster enum comparisons

* And Elvis operator for unknown parameter type

* Resolved #5162 - AI much less motivated to attack city-states

* Whoops, wrong branch
This commit is contained in:
Yair Morgenstern 2021-09-16 20:52:06 +03:00 committed by GitHub
parent 51bfd927c1
commit 6d26a28619
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 81 additions and 28 deletions

View File

@ -11,6 +11,7 @@ import com.unciv.logic.map.TileInfo
import com.unciv.logic.map.TileMap import com.unciv.logic.map.TileMap
import com.unciv.models.Counter import com.unciv.models.Counter
import com.unciv.models.ruleset.Unique 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.ResourceSupplyList
import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unit.BaseUnit import com.unciv.models.ruleset.unit.BaseUnit
@ -292,10 +293,10 @@ class CityInfo {
cityResources.add( cityResources.add(
resource, resource,
unique.params[0].toInt() * civInfo.getResourceModifier(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 val resource = getRuleset().tileResources[unique.params[1]] ?: continue
cityResources.add( cityResources.add(
resource, resource,

View File

@ -7,6 +7,7 @@ import com.unciv.logic.HexMath
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.UniqueType
import com.unciv.models.ruleset.tile.* import com.unciv.models.ruleset.tile.*
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
@ -387,7 +388,7 @@ open class TileInfo {
matchesTerrainFilter(it.params[0]) && !civInfo.tech.isResearched(it.params[1]) matchesTerrainFilter(it.params[0]) && !civInfo.tech.isResearched(it.params[1])
} -> false } -> false
improvement.uniqueObjects.any { improvement.uniqueObjects.any {
it.placeholderText == "Consumes [] []" it.matches(UniqueType.ConsumesResources, ruleset)
&& civInfo.getCivResourcesByName()[it.params[1]]!! < it.params[0].toInt() && civInfo.getCivResourcesByName()[it.params[1]]!! < it.params[0].toInt()
} -> false } -> false
else -> canImprovementBeBuiltHere(improvement, hasViewableResource(civInfo)) else -> canImprovementBeBuiltHere(improvement, hasViewableResource(civInfo))

View File

@ -95,7 +95,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
if (!tileBonusHashmap.containsKey(stats)) tileBonusHashmap[stats] = ArrayList() if (!tileBonusHashmap.containsKey(stats)) tileBonusHashmap[stats] = ArrayList()
tileBonusHashmap[stats]!!.add(unique.params[1]) tileBonusHashmap[stats]!!.add(unique.params[1])
} }
unique.placeholderText == "Consumes [] []" -> Unit // skip these, unique.isOfType(UniqueType.ConsumesResources) -> Unit // skip these,
else -> yield(unique.text) else -> yield(unique.text)
} }
for ((key, value) in tileBonusHashmap) for ((key, value) in tileBonusHashmap)
@ -706,7 +706,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
val resourceRequirements = HashMap<String, Int>() val resourceRequirements = HashMap<String, Int>()
if (requiredResource != null) resourceRequirements[requiredResource!!] = 1 if (requiredResource != null) resourceRequirements[requiredResource!!] = 1
for (unique in uniqueObjects) for (unique in uniqueObjects)
if (unique.placeholderText == "Consumes [] []") if (unique.isOfType(UniqueType.ConsumesResources))
resourceRequirements[unique.params[1]] = unique.params[0].toInt() resourceRequirements[unique.params[1]] = unique.params[0].toInt()
return resourceRequirements return resourceRequirements
} }

View File

@ -14,9 +14,68 @@ import com.unciv.models.translations.hasPlaceholderParameters
import com.unciv.ui.worldscreen.unit.UnitActions import com.unciv.ui.worldscreen.unit.UnitActions
import kotlin.random.Random 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<List<UniqueParameterType>>()
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 placeholderText = text.getPlaceholderText()
val params = text.getPlaceholderParameters() 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 /** 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 */ * - for instance, in the city screen, we call every tile unique for every tile, which can lead to ANRs */
val stats: Stats by lazy { 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 if (firstStatParam == null) Stats() // So badly-defined stats don't crash the entire game
else Stats.parse(firstStatParam) 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<String, ArrayList<Unique>>() { class UniqueMap:HashMap<String, ArrayList<Unique>>() {
@ -116,7 +180,7 @@ object UniqueTriggerActivation {
return placedUnit != null 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 // 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" -> { "Free Social Policy" -> {
if (civInfo.isSpectator()) return false if (civInfo.isSpectator()) return false
@ -201,7 +265,7 @@ object UniqueTriggerActivation {
} }
return true return true
} }
"Free Technology" -> { "Free Technology" -> {
if (civInfo.isSpectator()) return false if (civInfo.isSpectator()) return false
civInfo.tech.freeTechs += 1 civInfo.tech.freeTechs += 1
@ -457,8 +521,8 @@ object UniqueTriggerActivation {
civInfo.addNotification(notification, NotificationIcon.Diplomacy) civInfo.addNotification(notification, NotificationIcon.Diplomacy)
return true 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" -> "Provides a [] in your first [] cities for free" ->
civInfo.civConstructions.tryAddFreeBuildings() civInfo.civConstructions.tryAddFreeBuildings()
} }

View File

@ -7,6 +7,7 @@ import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.Unique import com.unciv.models.ruleset.Unique
import com.unciv.models.ruleset.UniqueType
import com.unciv.models.stats.INamed import com.unciv.models.stats.INamed
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
@ -516,7 +517,7 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
val resourceRequirements = HashMap<String, Int>() val resourceRequirements = HashMap<String, Int>()
if (requiredResource != null) resourceRequirements[requiredResource!!] = 1 if (requiredResource != null) resourceRequirements[requiredResource!!] = 1
for (unique in uniqueObjects) for (unique in uniqueObjects)
if (unique.placeholderText == "Consumes [] []") if (unique.isOfType(UniqueType.ConsumesResources))
resourceRequirements[unique.params[1]] = unique.params[0].toInt() resourceRequirements[unique.params[1]] = unique.params[0].toInt()
return resourceRequirements return resourceRequirements
} }

View File

@ -200,22 +200,7 @@ object TranslationFileWriter {
"Buildings", "Buildings",
"Building" "Building"
)) } )) }
val unitTypeMap = ruleset.unitTypes.keys.toMutableSet().apply { addAll(sequenceOf( val unitTypeMap = ruleset.unitTypes.keys.toMutableSet().apply { addAll(UniqueParameterType.unitTypeStrings) }
"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 cityFilterMap = setOf( val cityFilterMap = setOf(
"in this city", "in this city",
"in all cities", "in all cities",

View File

@ -16,6 +16,7 @@ import com.unciv.models.UncivSound
import com.unciv.models.UnitAction import com.unciv.models.UnitAction
import com.unciv.models.UnitActionType import com.unciv.models.UnitActionType
import com.unciv.models.ruleset.Building import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.UniqueType
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
@ -594,7 +595,7 @@ object UnitActions {
var resourcesAvailable = true var resourcesAvailable = true
if (improvement.uniqueObjects.any { 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 resourcesAvailable = false