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
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.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,

View File

@ -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))

View File

@ -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<String, Int>()
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
}

View File

@ -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<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 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<String, ArrayList<Unique>>() {
@ -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()
}

View File

@ -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<String, Int>()
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
}

View File

@ -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",

View File

@ -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