Modding: Added "non-[filter]" filtering for unit filters

Cleaned up unit filter validations to match actual filters
This commit is contained in:
Yair Morgenstern
2023-11-18 22:16:43 +02:00
parent 2b0568f025
commit dc6413d707
12 changed files with 126 additions and 40 deletions

View File

@ -0,0 +1,17 @@
package com.unciv.logic
object MultiFilter {
fun multiFilter(input: String, filterFunction: (String)->Boolean,
/** Unique validity doesn't check for actual matching */ forUniqueValidityTests:Boolean=false): Boolean {
if (input.contains("} {"))
return input.removePrefix("{").removeSuffix("}").split("} {")
.all{ multiFilter(it, filterFunction) }
if (input.startsWith("non-[") && input.endsWith("]")) {
val internalResult = multiFilter(input.removePrefix("non-[").removeSuffix("]"), filterFunction)
return if (forUniqueValidityTests) internalResult else !internalResult
}
return filterFunction(input)
}
}

View File

@ -3,6 +3,7 @@ package com.unciv.logic.map.mapunit
import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.logic.IsPartOfGameInfoSerialization
import com.unciv.logic.MultiFilter
import com.unciv.logic.automation.unit.UnitAutomation
import com.unciv.logic.battle.BattleUnitCapture
import com.unciv.logic.battle.MapUnitCombatant
@ -24,7 +25,6 @@ import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.ruleset.unit.UnitType
import com.unciv.models.stats.Stats
import com.unciv.ui.components.UnitMovementMemoryType
import com.unciv.ui.components.extensions.filterAndLogic
import java.text.DecimalFormat
import kotlin.math.pow
import kotlin.math.ulp
@ -888,9 +888,11 @@ class MapUnit : IsPartOfGameInfoSerialization {
/** Implements [UniqueParameterType.MapUnitFilter][com.unciv.models.ruleset.unique.UniqueParameterType.MapUnitFilter] */
fun matchesFilter(filter: String): Boolean {
return filter.filterAndLogic { matchesFilter(it) } // multiple types at once - AND logic. Looks like:"{Military} {Land}"
?: when (filter) {
return MultiFilter.multiFilter(filter, ::matchesSingleFilter)
}
private fun matchesSingleFilter(filter:String): Boolean {
return when (filter) {
Constants.wounded, "wounded units" -> health < 100
Constants.barbarians, "Barbarian" -> civ.isBarbarian()
"City-State" -> civ.isCityState()

View File

@ -1,6 +1,7 @@
package com.unciv.models.ruleset.unique
import com.unciv.Constants
import com.unciv.logic.MultiFilter
import com.unciv.models.metadata.BaseRuleset
import com.unciv.models.ruleset.BeliefType
import com.unciv.models.ruleset.Ruleset
@ -9,7 +10,6 @@ import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unique.UniqueParameterType.Companion.guessTypeForTranslationWriter
import com.unciv.models.stats.Stat
import com.unciv.models.translations.TranslationFileWriter
import com.unciv.ui.components.extensions.filterCompositeLogic
// 'region' names beginning with an underscore are used here for a prettier "Structure window" - they go in front ot the rest.
@ -39,6 +39,8 @@ enum class UniqueParameterType(
) {
//endregion
Number("amount", "3", "This indicates a whole number, possibly with a + or - sign, such as `2`, `+13`, or `-3`") {
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueParameterErrorSeverity? {
@ -72,61 +74,76 @@ enum class UniqueParameterType(
CombatantFilter("combatantFilter", "City", "This indicates a combatant, which can either be a unit or a city (when bombarding). Must either be `City` or a `mapUnitFilter`") {
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueParameterErrorSeverity? {
if (parameterText == "City") return null // City also recognizes "All" but that's covered by UnitTypeFilter too
if (parameterText == "City") return null // City also recognizes "All" but that's covered by BaseUnitFilter too
return MapUnitFilter.getErrorSeverity(parameterText, ruleset)
}
},
/** Implemented by [MapUnit.matchesFilter][com.unciv.logic.map.mapunit.MapUnit.matchesFilter] */
MapUnitFilter("mapUnitFilter", Constants.wounded, null, "Map Unit Filters") {
private val knownValues = setOf(Constants.wounded, Constants.barbarians, "City-State", Constants.embarked, "Non-City")
private val knownValues = setOf(Constants.wounded, Constants.barbarians, "Barbarian",
"City-State", Constants.embarked, "Non-City")
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueParameterErrorSeverity? {
if (parameterText.startsWith('{')) // "{filter} {filter}" for and logic
return parameterText.filterCompositeLogic({ getErrorSeverity(it, ruleset) }) { a, b -> maxOf(a, b) }
if (parameterText in knownValues) return null
if (ruleset.unitPromotions.values.any { it.hasUnique(parameterText) })
return null
return BaseUnitFilter.getErrorSeverity(parameterText, ruleset)
val isKnown = MultiFilter.multiFilter(parameterText, {isKnownValue(it, ruleset)}, true)
if (isKnown) return null
return UniqueType.UniqueParameterErrorSeverity.PossibleFilteringUnique
}
override fun isKnownValue(parameterText:String, ruleset: Ruleset): Boolean {
if (parameterText in knownValues) return true
if (ruleset.unitPromotions.values.any { it.hasUnique(parameterText) }) return true
if (BaseUnitFilter.isKnownValue(parameterText, ruleset)) return true
return false
}
override fun getTranslationWriterStringsForOutput() = knownValues
},
/** Implemented by [BaseUnit.matchesFilter][com.unciv.models.ruleset.unit.BaseUnit.matchesFilter] */
BaseUnitFilter("baseUnitFilter", "Melee") {
private val knownValues = setOf(
"All", "Melee", "Ranged", "Civilian", "Military", "non-air",
"Nuclear Weapon", "Great Person", "Religious",
"relevant", // used for UniqueType.UnitStartingPromotions
)
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueParameterErrorSeverity? {
if (parameterText.startsWith('{')) // "{filter} {filter}" for and logic
return parameterText.filterCompositeLogic({ getErrorSeverity(it, ruleset) }) { a, b -> maxOf(a, b) }
if (UnitName.getErrorSeverity(parameterText, ruleset) == null) return null
if (ruleset.units.values.any { it.uniques.contains(parameterText) }) return null
return UnitTypeFilter.getErrorSeverity(parameterText, ruleset)
val isKnown = MultiFilter.multiFilter(parameterText, {isKnownValue(it, ruleset)}, true)
if (isKnown) return null
return UniqueType.UniqueParameterErrorSeverity.PossibleFilteringUnique
}
override fun isKnownValue(parameterText:String, ruleset: Ruleset): Boolean {
if (parameterText in knownValues) return true
if (UnitName.getErrorSeverity(parameterText, ruleset) == null) return true
if (ruleset.units.values.any { it.uniques.contains(parameterText) }) return true
if (UnitTypeFilter.isKnownValue(parameterText, ruleset)) return true
return false
}
},
/** Implemented by [UnitType.matchesFilter][com.unciv.models.ruleset.unit.UnitType.matchesFilter] */
//todo there is a large discrepancy between this parameter type and the actual filter, most of these are actually implemented by BaseUnitFilter
UnitTypeFilter("unitType", "Water", null, "Unit Type Filters") {
// As you can see there is a difference between these and what's in unitTypeStrings (for translation) -
// the goal is to unify, but for now this is the "real" list
// Note: this can't handle combinations of parameters (e.g. [{Military} {Water}])
private val knownValues = setOf(
"All", "Melee", "Ranged", "Civilian", "Military", "Land", "Water", "Air",
"non-air", "Nuclear Weapon", "Great Person", "Religious", "Barbarian",
"relevant", "City",
// These are up for debate
// "land units", "water units", "air units", "military units", "submarine units",
"Land", "Water", "Air",
)
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueParameterErrorSeverity? {
if (parameterText in knownValues) return null
if (ruleset.unitTypes.containsKey(parameterText)) return null
if (ruleset.eras.containsKey(parameterText)) return null
if (ruleset.unitTypes.values.any { it.uniques.contains(parameterText) }) return null
val isKnown = MultiFilter.multiFilter(parameterText, {isKnownValue(it, ruleset)}, true)
if (isKnown) return null
return UniqueType.UniqueParameterErrorSeverity.PossibleFilteringUnique
}
override fun isKnownValue(parameterText: String, ruleset: Ruleset): Boolean {
if (parameterText in knownValues) return true
if (ruleset.unitTypes.containsKey(parameterText)) return true
if (ruleset.eras.containsKey(parameterText)) return true
if (ruleset.unitTypes.values.any { it.uniques.contains(parameterText) }) return true
return false
}
override fun isTranslationWriterGuess(parameterText: String, ruleset: Ruleset) =
parameterText in ruleset.unitTypes.keys || parameterText in getTranslationWriterStringsForOutput()
@ -560,6 +577,8 @@ enum class UniqueParameterType(
/** Validate a [Unique] parameter */
abstract fun getErrorSeverity(parameterText: String, ruleset: Ruleset): UniqueType.UniqueParameterErrorSeverity?
open fun isKnownValue(parameterText: String, ruleset: Ruleset): Boolean = false
/** Pick this type when [TranslationFileWriter] tries to guess for an untyped [Unique] */
open fun isTranslationWriterGuess(parameterText: String, ruleset: Ruleset): Boolean =
getErrorSeverity(parameterText, ruleset) == null

View File

@ -1,5 +1,6 @@
package com.unciv.models.ruleset.unit
import com.unciv.logic.MultiFilter
import com.unciv.logic.city.City
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.civilization.Civilization
@ -15,7 +16,6 @@ import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueTarget
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import com.unciv.ui.components.extensions.filterAndLogic
import com.unciv.ui.components.extensions.getNeedMoreAmountString
import com.unciv.ui.components.extensions.toPercent
import com.unciv.ui.objectdescriptions.BaseUnitDescriptions
@ -264,9 +264,11 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
/** Implements [UniqueParameterType.BaseUnitFilter][com.unciv.models.ruleset.unique.UniqueParameterType.BaseUnitFilter] */
fun matchesFilter(filter: String): Boolean {
return filter.filterAndLogic { matchesFilter(it) } // multiple types at once - AND logic. Looks like:"{Military} {Land}"
?: when (filter) {
return MultiFilter.multiFilter(filter, ::matchesSingleFilter)
}
fun matchesSingleFilter(filter: String): Boolean {
return when (filter) {
unitType -> true
name -> true
replaces -> true
@ -287,9 +289,9 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
else -> {
if (type.matchesFilter(filter)) return true
if (requiredTech != null && ruleset.technologies[requiredTech]?.matchesFilter(filter)==true) return true
if (requiredTech != null && ruleset.technologies[requiredTech]?.matchesFilter(filter) == true) return true
if (
// Uniques using these kinds of filters should be deprecated and replaced with adjective-only parameters
// Uniques using these kinds of filters should be deprecated and replaced with adjective-only parameters
filter.endsWith(" units")
// "military units" --> "Military", using invariant locale
&& matchesFilter(filter.removeSuffix(" units").lowercase().replaceFirstChar { it.uppercaseChar() })