Make City center minimum tile yields moddable (#8804)

* Slight cleanup of TileStatFunctions

* Make City center minimum tile yields moddable

* Make City center minimum tile yields moddable - patch1

* Make City center minimum tile yields moddable - patch1
This commit is contained in:
SomeTroglodyte 2023-03-13 16:02:08 +01:00 committed by GitHub
parent 9eee47a628
commit db08c30363
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 76 additions and 56 deletions

View File

@ -281,7 +281,8 @@
{
"name": "City center",
"terrainsCanBeBuiltOn": ["Land"],
"uniques": ["Unpillagable", "Irremovable", "Unbuildable"],
"uniques": ["Ensures a minimum tile yield of [+2 Food, +1 Production]",
"Unpillagable", "Irremovable", "Unbuildable"],
"civilopediaText": [
{"text":"Marks the center of a city"},
{"text":"Appearance changes with the technological era of the owning civilization"}

View File

@ -270,7 +270,8 @@
{
"name": "City center",
"terrainsCanBeBuiltOn": ["Land"],
"uniques": ["Unpillagable", "Irremovable", "Unbuildable"],
"uniques": ["Ensures a minimum tile yield of [+2 Food, +1 Production]",
"Unpillagable", "Irremovable", "Unbuildable"],
"civilopediaText": [
{"text":"Marks the center of a city"},
{"text":"Appearance changes with the technological era of the owning civilization"}

View File

@ -5,7 +5,7 @@
"tileScales": {
"Atoll":0.35,
"City center":0.7,
"Faulout":0.35,
"Fallout":0.35,
"Flood plains":0.35,
"Forest":0.35,
"Hill":0.35,

View File

@ -38,6 +38,7 @@ object Constants {
const val freshWaterFilter = "Fresh Water"
const val barbarianEncampment = "Barbarian encampment"
const val cityCenter = "City center"
const val peaceTreaty = "Peace Treaty"
const val researchAgreement = "Research Agreement"

View File

@ -17,7 +17,9 @@ import com.unciv.models.metadata.Player
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stats
import com.unciv.models.translations.equalsPlaceholderText
import com.unciv.models.translations.getPlaceholderParameters
import com.unciv.utils.debug
@ -329,9 +331,15 @@ object GameStarter {
var startingUnits: MutableList<String>
var eraUnitReplacement: String
val cityCenterMinStats = sequenceOf(ruleSet.tileImprovements[Constants.cityCenter])
.filterNotNull()
.flatMap { it.getMatchingUniques(UniqueType.EnsureMinimumStats, StateForConditionals.IgnoreConditionals) }
.firstOrNull()
?.stats ?: Stats.DefaultCityCenterMinimum
val startScores = HashMap<Tile, Float>(tileMap.values.size)
for (tile in tileMap.values) {
startScores[tile] = tile.stats.getTileStartScore()
startScores[tile] = tile.stats.getTileStartScore(cityCenterMinStats)
}
val allCivs = gameInfo.civilizations.filter { !it.isBarbarian() }
val landTilesInBigEnoughGroup = getCandidateLand(allCivs.size, tileMap, startScores)

View File

@ -18,28 +18,11 @@ class TileStatFunctions(val tile: Tile) {
fun getTileStats(city: City?, observingCiv: Civilization?,
localUniqueCache: LocalUniqueCache = LocalUniqueCache(false)
): Stats {
var stats = tile.getBaseTerrain().cloneStats()
val stats = getTerrainStats()
var minimumStats = if (tile.isCityCenter()) Stats.DefaultCityCenterMinimum else Stats.ZERO
val stateForConditionals = StateForConditionals(civInfo = observingCiv, city = city, tile = tile)
for (terrainFeatureBase in tile.terrainFeatureObjects) {
when {
terrainFeatureBase.hasUnique(UniqueType.NullifyYields) ->
return terrainFeatureBase.cloneStats()
terrainFeatureBase.overrideStats -> stats = terrainFeatureBase.cloneStats()
else -> stats.add(terrainFeatureBase)
}
}
if (tile.naturalWonder != null) {
val wonderStats = tile.getNaturalWonder().cloneStats()
if (tile.getNaturalWonder().overrideStats)
stats = wonderStats
else
stats.add(wonderStats)
}
if (city != null) {
var tileUniques = city.getMatchingUniques(UniqueType.StatsFromTiles, StateForConditionals.IgnoreConditionals)
.filter { city.matchesFilter(it.params[2]) }
@ -76,14 +59,16 @@ class TileStatFunctions(val tile: Tile) {
if (stats.gold != 0f && observingCiv.goldenAges.isGoldenAge())
stats.gold++
}
if (tile.isCityCenter()) {
if (stats.food < 2) stats.food = 2f
if (stats.production < 1) stats.production = 1f
if (improvement != null) {
val ensureMinUnique = improvement
.getMatchingUniques(UniqueType.EnsureMinimumStats, stateForConditionals)
.firstOrNull()
if (ensureMinUnique != null) minimumStats = ensureMinUnique.stats
}
}
for ((stat, value) in stats)
if (value < 0f) stats[stat] = 0f
stats.coerceAtLeast(minimumStats) // Minimum 0 or as defined by City center
for ((stat, value) in getTilePercentageStats(observingCiv, city)) {
stats[stat] *= value.toPercent()
@ -92,6 +77,30 @@ class TileStatFunctions(val tile: Tile) {
return stats
}
/** Ensures each stat is >= [other].stat - modifies in place */
private fun Stats.coerceAtLeast(other: Stats) {
for ((stat, value) in other)
if (this[stat] < value) this[stat] = value
}
/** Gets basic stats to start off [getTileStats] or [getTileStartYield], independently mutable result */
private fun getTerrainStats(): Stats {
var stats: Stats? = null
// allTerrains iterates over base, natural wonder, then features
for (terrain in tile.allTerrains) {
when {
terrain.hasUnique(UniqueType.NullifyYields) ->
return terrain.cloneStats()
terrain.overrideStats || stats == null ->
stats = terrain.cloneStats()
else ->
stats.add(terrain)
}
}
return stats!!
}
// Only gets the tile percentage bonus, not the improvement percentage bonus
@Suppress("MemberVisibilityCanBePrivate")
fun getTilePercentageStats(observingCiv: Civilization?, city: City?): Stats {
@ -132,10 +141,12 @@ class TileStatFunctions(val tile: Tile) {
return stats
}
fun getTileStartScore(): Float {
fun getTileStartScore(cityCenterMinStats: Stats): Float {
var sum = 0f
for (closeTile in tile.getTilesInDistance(2)) {
val tileYield = closeTile.stats.getTileStartYield(closeTile == tile)
val tileYield = closeTile.stats.getTileStartYield(
if (closeTile == tile) cityCenterMinStats else Stats.ZERO
)
sum += tileYield
if (closeTile in tile.neighbors)
sum += tileYield
@ -155,25 +166,12 @@ class TileStatFunctions(val tile: Tile) {
return sum
}
private fun getTileStartYield(isCenter: Boolean): Float {
var stats = tile.getBaseTerrain().cloneStats()
for (terrainFeatureBase in tile.terrainFeatureObjects) {
if (terrainFeatureBase.overrideStats)
stats = terrainFeatureBase.cloneStats()
else
stats.add(terrainFeatureBase)
private fun getTileStartYield(minimumStats: Stats) =
getTerrainStats().run {
if (tile.resource != null) add(tile.tileResource)
coerceAtLeast(minimumStats)
food + production + gold
}
if (tile.resource != null) stats.add(tile.tileResource)
if (stats.production < 0) stats.production = 0f
if (isCenter) {
if (stats.food < 2) stats.food = 2f
if (stats.production < 1) stats.production = 1f
}
return stats.food + stats.production + stats.gold
}
// Also multiplies the stats by the percentage bonus for improvements (but not for tiles)

View File

@ -100,6 +100,8 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
StatsFromTradeRoute("[stats] from each Trade Route", UniqueTarget.Global, UniqueTarget.FollowerBelief),
StatsFromGlobalCitiesFollowingReligion("[stats] for each global city following this religion", UniqueTarget.FounderBelief),
StatsFromGlobalFollowers("[stats] from every [amount] global followers [cityFilter]", UniqueTarget.FounderBelief),
// Used for City center
EnsureMinimumStats("Ensures a minimum tile yield of [stats]", UniqueTarget.Improvement),
// Stat percentage boosts
StatPercentBonus("[relativeAmount]% [stat]", UniqueTarget.Global, UniqueTarget.FollowerBelief),

View File

@ -150,7 +150,7 @@ open class Stats(
}
}
/** Since notificaitons are translated on the fly, when saving stats there we need to do so in English */
/** Since notifications are translated on the fly, when saving stats there we need to do so in English */
fun toStringForNotifications() = this.joinToString {
(if (it.value > 0) "+" else "") + it.value.toInt() + " " + it.key.toString().tr(Constants.english)
}
@ -233,7 +233,7 @@ open class Stats(
fun parse(string: String): Stats {
val toReturn = Stats()
val statsWithBonuses = string.split(", ")
for(statWithBonuses in statsWithBonuses){
for(statWithBonuses in statsWithBonuses) {
val match = statRegex.matchEntire(statWithBonuses)!!
val statName = match.groupValues[3]
val statAmount = match.groupValues[2].toFloat() * (if (match.groupValues[1] == "-") -1 else 1)
@ -241,6 +241,9 @@ open class Stats(
}
return toReturn
}
val ZERO = Stats()
val DefaultCityCenterMinimum = Stats(food = 2f, production = 1f)
}
}

View File

@ -248,7 +248,7 @@ class MapEditorEditImprovementsTab(
companion object {
//todo This should really be easier, the attributes should allow such a test in one go
private val disallowImprovements = listOf(
"City center", Constants.repair, Constants.remove, Constants.cancelImprovementOrder
Constants.cityCenter, Constants.repair, Constants.remove, Constants.cancelImprovementOrder
)
private fun TileImprovement.group() = when {
RoadStatus.values().any { it.name == name } -> 2

View File

@ -197,8 +197,8 @@ object UnitActions {
if (unit.civ.playerType != PlayerType.AI)
UncivGame.Current.settings.addCompletedTutorialTask("Found city")
unit.civ.addCity(tile.position)
if (tile.ruleset.tileImprovements.containsKey("City center"))
tile.changeImprovement("City center")
if (tile.ruleset.tileImprovements.containsKey(Constants.cityCenter))
tile.changeImprovement(Constants.cityCenter)
tile.removeRoad()
if (hasActionModifiers) activateSideEffects(unit, unique)

View File

@ -1491,6 +1491,11 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
Applicable to: Terrain
## Improvement uniques
??? example "Ensures a minimum tile yield of [stats]"
Example: "Ensures a minimum tile yield of [+1 Gold, +2 Production]"
Applicable to: Improvement
??? example "Can also be built on tiles adjacent to fresh water"
Applicable to: Improvement

View File

@ -1,6 +1,7 @@
package com.unciv.testing
import com.badlogic.gdx.Gdx
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.json.json
import com.unciv.logic.GameInfo
@ -74,8 +75,8 @@ class SerializationTests {
val unit = civ.units.getCivUnits().first { it.hasUnique(UniqueType.FoundCity) }
val tile = unit.getTile()
unit.civ.addCity(tile.position)
if (tile.ruleset.tileImprovements.containsKey("City center"))
tile.changeImprovement("City center")
if (tile.ruleset.tileImprovements.containsKey(Constants.cityCenter))
tile.changeImprovement(Constants.cityCenter)
unit.destroy()
// Ensure some diplomacy objects are instantiated