Add a Unique allowing an Improvement to specify which Resource(s) it improves (#11718)

* Linting

* Add UniqueType.ImprovesResources

* Address reviews and extensive Kdoc

* Oops, better include the generated stuff
This commit is contained in:
SomeTroglodyte
2024-06-27 22:26:14 +02:00
committed by GitHub
parent c9bcf10f2a
commit 8da58ed34a
9 changed files with 103 additions and 19 deletions

View File

@ -290,7 +290,7 @@ class TileStatFunctions(val tile: Tile) {
if (tile.hasViewableResource(observingCiv) && tile.tileResource.isImprovedBy(improvement.name)
&& tile.tileResource.improvementStats != null
)
stats.add(tile.tileResource.improvementStats!!.clone()) // resource-specific improvement
stats.add(tile.tileResource.improvementStats!!) // resource-specific improvement
val conditionalState = StateForConditionals(civInfo = observingCiv, city = city, tile = tile)
for (unique in improvement.getMatchingUniques(UniqueType.Stats, conditionalState)) {

View File

@ -242,7 +242,7 @@ class Ruleset {
fun allICivilopediaText(): Sequence<ICivilopediaText> =
allRulesetObjects() + events.values + events.values.flatMap { it.choices }
fun load(folderHandle: FileHandle) {
internal fun load(folderHandle: FileHandle) {
// Note: Most files are loaded using createHashmap, which sets originRuleset automatically.
// For other files containing IRulesetObject's we'll have to remember to do so manually - e.g. Tech.
val modOptionsFile = folderHandle.child("ModOptions.json")
@ -450,6 +450,8 @@ class Ruleset {
).isEmpty()
})
}
updateResourceTransients()
}
}
@ -458,17 +460,21 @@ class Ruleset {
* Alternatively, if you edit a tech column's building costs, you want it to affect all buildings in that column.
* This deals with that
* */
fun updateBuildingCosts() {
internal fun updateBuildingCosts() {
for (building in buildings.values) {
if (building.cost == -1 && building.getMatchingUniques(UniqueType.Unbuildable).none { it.conditionals.isEmpty() }) {
val column = building.techColumn(this)
if (column != null) {
building.cost = if (building.isAnyWonder()) column.wonderCost else column.buildingCost
}
}
if (building.cost != -1) continue
if (building.getMatchingUniques(UniqueType.Unbuildable).any { it.conditionals.isEmpty() }) continue
val column = building.techColumn(this) ?: continue
building.cost = if (building.isAnyWonder()) column.wonderCost else column.buildingCost
}
}
/** Introduced to support UniqueType.ImprovesResources: gives a resource the chance to scan improvements */
internal fun updateResourceTransients() {
for (resource in tileResources.values)
resource.setTransients(this)
}
/** Used for displaying a RuleSet's name */
override fun toString() = when {
name.isNotEmpty() -> name

View File

@ -153,6 +153,7 @@ object RulesetCache : HashMap<String, Ruleset>() {
newRuleset.mods += mod.name
}
newRuleset.updateBuildingCosts() // only after we've added all the mods can we calculate the building costs
newRuleset.updateResourceTransients()
return newRuleset
}

View File

@ -16,21 +16,58 @@ class TileResource : RulesetStatsObject() {
var resourceType: ResourceType = ResourceType.Bonus
var terrainsCanBeFoundOn: List<String> = listOf()
var improvement: String? = null
/** stats that this resource adds to a tile */
var improvementStats: Stats? = null
var revealedBy: String? = null
/** Legacy "which improvement will unlock this treausre"
* @see improvedBy
* @see getImprovements
*/
var improvement: String? = null
/** Defines which improvement will "unlock" this resource
* @see improvement
* @see getImprovements
*/
var improvedBy: List<String> = listOf()
var majorDepositAmount: DepositAmount = DepositAmount()
var minorDepositAmount: DepositAmount = DepositAmount()
private val _allImprovements by lazy {
if (improvement == null) improvedBy
else improvedBy + improvement!!
private var improvementsInitialized = false
/** Cache collecting [improvement], [improvedBy] and [UniqueType.ImprovesResources] uniques on the improvements themselves. */
private val allImprovements = mutableSetOf<String>()
private var ruleset: Ruleset? = null
/** Collects which improvements "unlock" this resource, caches and returns the Set.
* - The cache is cleared after ruleset load and combine, via [setTransients].
* @see improvement
* @see improvedBy
* @see UniqueType.ImprovesResources
*/
fun getImprovements(): Set<String> {
if (improvementsInitialized) return allImprovements
val ruleset = this.ruleset
?: throw IllegalStateException("No ruleset on TileResource when initializing improvements")
if (improvement != null) allImprovements += improvement!!
allImprovements.addAll(improvedBy)
for (improvement in ruleset.tileImprovements.values) {
if (improvement.getMatchingUniques(UniqueType.ImprovesResources).none { matchesFilter(it.params[0]) }) continue
allImprovements += improvement.name
}
improvementsInitialized = true
return allImprovements
}
fun getImprovements(): List<String> {
return _allImprovements
/** Clears the cache for [getImprovements] and saves the Ruleset the cache update will need.
* - Doesn't evaluate the cache immediately, that would break tests as it trips the uniqueObjects lazy and tests manipulate uniques after ruleset load.
* - called from [Ruleset.updateResourceTransients]
*/
fun setTransients(ruleset: Ruleset) {
allImprovements.clear()
improvementsInitialized = false
this.ruleset = ruleset
}
override fun getUniqueTarget() = UniqueTarget.Resource
@ -135,12 +172,21 @@ class TileResource : RulesetStatsObject() {
return getImprovements().contains(improvementName)
}
fun getImprovingImprovement(tile: Tile, civInfo: Civilization): String? {
/** @return Of all the potential improvements in [getImprovements], the first this [civ] can actually build, if any. */
fun getImprovingImprovement(tile: Tile, civ: Civilization): String? {
return getImprovements().firstOrNull {
tile.improvementFunctions.canBuildImprovement(civInfo.gameInfo.ruleset.tileImprovements[it]!!, civInfo)
tile.improvementFunctions.canBuildImprovement(civ.gameInfo.ruleset.tileImprovements[it]!!, civ)
}
}
fun matchesFilter(filter: String) = when (filter) {
name -> true
"any" -> true
resourceType.name -> true
in uniques -> true
else -> improvementStats?.any { filter == it.key.name } == true
}
fun generatesNaturallyOn(tile: Tile): Boolean {
if (tile.lastTerrain.name !in terrainsCanBeFoundOn) return false
val stateForConditionals = StateForConditionals(tile = tile)

View File

@ -419,6 +419,21 @@ enum class UniqueParameterType(
override fun isKnownValue(parameterText: String, ruleset: Ruleset) = ruleset.tileResources[parameterText]?.isStockpiled() == true
},
/** Used by [UniqueType.ImprovesResources], implemented by [com.unciv.models.ruleset.tile.TileResource.matchesFilter] */
ResourceFilter("resourceFilter", "Strategic", "A resource name, type, 'all', or a Stat listed in the resource's improvementStats",
severityDefault = UniqueType.UniqueParameterErrorSeverity.PossibleFilteringUnique
) {
private val knownValues = setOf("any") + Constants.all // "any" sounds nicer than "all" in that UniqueType
override fun isKnownValue(parameterText: String, ruleset: Ruleset) = when {
parameterText in knownValues -> true
parameterText in ruleset.tileResources -> true
ResourceType.values().any { it.name == parameterText } -> true
Stat.isStat(parameterText) -> true
else -> false
}
override fun getTranslationWriterStringsForOutput() = knownValues
},
/** Used by [UniqueType.FreeExtraBeliefs], see ReligionManager.getBeliefsToChooseAt* functions */
BeliefTypeName("beliefType", "Follower", "'Pantheon', 'Follower', 'Founder' or 'Enhancer'",
severityDefault = UniqueType.UniqueParameterErrorSeverity.RulesetInvariant

View File

@ -638,6 +638,10 @@ enum class UniqueType(
PillageYieldFixed("Pillaging this improvement yields [stats]", UniqueTarget.Improvement),
Irremovable("Irremovable", UniqueTarget.Improvement),
AutomatedUnitsWillNotReplace("Will not be replaced by automated units", UniqueTarget.Improvement),
ImprovesResources("Improves [resourceFilter] resource in this tile", UniqueTarget.Improvement, flags = UniqueFlag.setOfNoConditionals,
docDescription = "This is offered as an alternative to the improvedBy field of a resource." +
" The result will be cached within the resource definition when loading a game, without knowledge about terrain, cities, civs, units or time." +
" Therefore, most conditionals will not work, only those **not** dependent on game state."),
//endregion
/////////////////////////////////// region 07 PERSONALITY UNIQUES ////////////////////////////////////////

View File

@ -81,7 +81,7 @@ open class Stats(
faith = 0f
}
/** **Mutating function**
/** **Mutating function** (but does **not** mutate [other])
* Adds each value of another [Stats] instance to this one in place
* @return this for chaining */
fun add(other: Stats): Stats {

View File

@ -11,7 +11,7 @@ Note that all of these are case-sensitive!
## General Filter Rules
All filters except for `populationFilter` accept multiple values in the format: `{A} {B} {C}` etc, meaning "the object must match ALL of these filters"
All filters except for `populationFilter` and `resourceFilter` accept multiple values in the format: `{A} {B} {C}` etc, meaning "the object must match ALL of these filters"
> Example: `[{Military} {Water}] units`, `[{Wounded} {Armor}] units`, etc.
@ -206,6 +206,11 @@ For example: `+1 Science`.
These can be strung together with ", " between them, for example: `+2 Production, +3 Food`.
## resourceFilter
At the moment, only used for the `"Improves [resourceFilter] resource in this tile"` Unique on Improvements.
Allows filtering resources by their name, their type, or by any Stat listed in their `improvementStats` property. The `all` keyword works too.
## stockpiledResource
This indicates a text that corresponds to a custom Stockpile Resource.

View File

@ -1795,6 +1795,12 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
??? example "Will not be replaced by automated units"
Applicable to: Improvement
??? example "Improves [resourceFilter] resource in this tile"
This is offered as an alternative to the improvedBy field of a resource. The result will be cached within the resource definition when loading a game, without knowledge about terrain, cities, civs, units or time. Therefore, most conditionals will not work, only those **not** dependent on game state.
Example: "Improves [Strategic] resource in this tile"
Applicable to: Improvement
## Resource uniques
??? example "Deposits in [tileFilter] tiles always provide [amount] resources"
Example: "Deposits in [Farm] tiles always provide [3] resources"
@ -2522,6 +2528,7 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
*[promotion]: The name of any promotion.
*[relativeAmount]: This indicates a number, usually with a + or - sign, such as `+25` (this kind of parameter is often followed by '%' which is nevertheless not part of the value).
*[resource]: The name of any resource.
*[resourceFilter]: A resource name, type, 'all', or a Stat listed in the resource's improvementStats.
*[specialist]: The name of any specialist.
*[speed]: The name of any speed.
*[stat]: This is one of the 7 major stats in the game - `Gold`, `Science`, `Production`, `Food`, `Happiness`, `Culture` and `Faith`. Note that the stat names need to be capitalized!