mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-29 06:50:08 -04:00
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:
parent
9eee47a628
commit
db08c30363
@ -281,7 +281,8 @@
|
|||||||
{
|
{
|
||||||
"name": "City center",
|
"name": "City center",
|
||||||
"terrainsCanBeBuiltOn": ["Land"],
|
"terrainsCanBeBuiltOn": ["Land"],
|
||||||
"uniques": ["Unpillagable", "Irremovable", "Unbuildable"],
|
"uniques": ["Ensures a minimum tile yield of [+2 Food, +1 Production]",
|
||||||
|
"Unpillagable", "Irremovable", "Unbuildable"],
|
||||||
"civilopediaText": [
|
"civilopediaText": [
|
||||||
{"text":"Marks the center of a city"},
|
{"text":"Marks the center of a city"},
|
||||||
{"text":"Appearance changes with the technological era of the owning civilization"}
|
{"text":"Appearance changes with the technological era of the owning civilization"}
|
||||||
|
@ -270,7 +270,8 @@
|
|||||||
{
|
{
|
||||||
"name": "City center",
|
"name": "City center",
|
||||||
"terrainsCanBeBuiltOn": ["Land"],
|
"terrainsCanBeBuiltOn": ["Land"],
|
||||||
"uniques": ["Unpillagable", "Irremovable", "Unbuildable"],
|
"uniques": ["Ensures a minimum tile yield of [+2 Food, +1 Production]",
|
||||||
|
"Unpillagable", "Irremovable", "Unbuildable"],
|
||||||
"civilopediaText": [
|
"civilopediaText": [
|
||||||
{"text":"Marks the center of a city"},
|
{"text":"Marks the center of a city"},
|
||||||
{"text":"Appearance changes with the technological era of the owning civilization"}
|
{"text":"Appearance changes with the technological era of the owning civilization"}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"tileScales": {
|
"tileScales": {
|
||||||
"Atoll":0.35,
|
"Atoll":0.35,
|
||||||
"City center":0.7,
|
"City center":0.7,
|
||||||
"Faulout":0.35,
|
"Fallout":0.35,
|
||||||
"Flood plains":0.35,
|
"Flood plains":0.35,
|
||||||
"Forest":0.35,
|
"Forest":0.35,
|
||||||
"Hill":0.35,
|
"Hill":0.35,
|
||||||
|
@ -38,6 +38,7 @@ object Constants {
|
|||||||
const val freshWaterFilter = "Fresh Water"
|
const val freshWaterFilter = "Fresh Water"
|
||||||
|
|
||||||
const val barbarianEncampment = "Barbarian encampment"
|
const val barbarianEncampment = "Barbarian encampment"
|
||||||
|
const val cityCenter = "City center"
|
||||||
|
|
||||||
const val peaceTreaty = "Peace Treaty"
|
const val peaceTreaty = "Peace Treaty"
|
||||||
const val researchAgreement = "Research Agreement"
|
const val researchAgreement = "Research Agreement"
|
||||||
|
@ -17,7 +17,9 @@ import com.unciv.models.metadata.Player
|
|||||||
import com.unciv.models.ruleset.ModOptionsConstants
|
import com.unciv.models.ruleset.ModOptionsConstants
|
||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
import com.unciv.models.ruleset.RulesetCache
|
import com.unciv.models.ruleset.RulesetCache
|
||||||
|
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||||
import com.unciv.models.ruleset.unique.UniqueType
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
|
import com.unciv.models.stats.Stats
|
||||||
import com.unciv.models.translations.equalsPlaceholderText
|
import com.unciv.models.translations.equalsPlaceholderText
|
||||||
import com.unciv.models.translations.getPlaceholderParameters
|
import com.unciv.models.translations.getPlaceholderParameters
|
||||||
import com.unciv.utils.debug
|
import com.unciv.utils.debug
|
||||||
@ -329,9 +331,15 @@ object GameStarter {
|
|||||||
var startingUnits: MutableList<String>
|
var startingUnits: MutableList<String>
|
||||||
var eraUnitReplacement: 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)
|
val startScores = HashMap<Tile, Float>(tileMap.values.size)
|
||||||
for (tile in tileMap.values) {
|
for (tile in tileMap.values) {
|
||||||
startScores[tile] = tile.stats.getTileStartScore()
|
startScores[tile] = tile.stats.getTileStartScore(cityCenterMinStats)
|
||||||
}
|
}
|
||||||
val allCivs = gameInfo.civilizations.filter { !it.isBarbarian() }
|
val allCivs = gameInfo.civilizations.filter { !it.isBarbarian() }
|
||||||
val landTilesInBigEnoughGroup = getCandidateLand(allCivs.size, tileMap, startScores)
|
val landTilesInBigEnoughGroup = getCandidateLand(allCivs.size, tileMap, startScores)
|
||||||
|
@ -18,28 +18,11 @@ class TileStatFunctions(val tile: Tile) {
|
|||||||
fun getTileStats(city: City?, observingCiv: Civilization?,
|
fun getTileStats(city: City?, observingCiv: Civilization?,
|
||||||
localUniqueCache: LocalUniqueCache = LocalUniqueCache(false)
|
localUniqueCache: LocalUniqueCache = LocalUniqueCache(false)
|
||||||
): Stats {
|
): 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)
|
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) {
|
if (city != null) {
|
||||||
var tileUniques = city.getMatchingUniques(UniqueType.StatsFromTiles, StateForConditionals.IgnoreConditionals)
|
var tileUniques = city.getMatchingUniques(UniqueType.StatsFromTiles, StateForConditionals.IgnoreConditionals)
|
||||||
.filter { city.matchesFilter(it.params[2]) }
|
.filter { city.matchesFilter(it.params[2]) }
|
||||||
@ -76,14 +59,16 @@ class TileStatFunctions(val tile: Tile) {
|
|||||||
|
|
||||||
if (stats.gold != 0f && observingCiv.goldenAges.isGoldenAge())
|
if (stats.gold != 0f && observingCiv.goldenAges.isGoldenAge())
|
||||||
stats.gold++
|
stats.gold++
|
||||||
|
|
||||||
|
if (improvement != null) {
|
||||||
|
val ensureMinUnique = improvement
|
||||||
|
.getMatchingUniques(UniqueType.EnsureMinimumStats, stateForConditionals)
|
||||||
|
.firstOrNull()
|
||||||
|
if (ensureMinUnique != null) minimumStats = ensureMinUnique.stats
|
||||||
}
|
}
|
||||||
if (tile.isCityCenter()) {
|
|
||||||
if (stats.food < 2) stats.food = 2f
|
|
||||||
if (stats.production < 1) stats.production = 1f
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for ((stat, value) in stats)
|
stats.coerceAtLeast(minimumStats) // Minimum 0 or as defined by City center
|
||||||
if (value < 0f) stats[stat] = 0f
|
|
||||||
|
|
||||||
for ((stat, value) in getTilePercentageStats(observingCiv, city)) {
|
for ((stat, value) in getTilePercentageStats(observingCiv, city)) {
|
||||||
stats[stat] *= value.toPercent()
|
stats[stat] *= value.toPercent()
|
||||||
@ -92,6 +77,30 @@ class TileStatFunctions(val tile: Tile) {
|
|||||||
return stats
|
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
|
// Only gets the tile percentage bonus, not the improvement percentage bonus
|
||||||
@Suppress("MemberVisibilityCanBePrivate")
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
fun getTilePercentageStats(observingCiv: Civilization?, city: City?): Stats {
|
fun getTilePercentageStats(observingCiv: Civilization?, city: City?): Stats {
|
||||||
@ -132,10 +141,12 @@ class TileStatFunctions(val tile: Tile) {
|
|||||||
return stats
|
return stats
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTileStartScore(): Float {
|
fun getTileStartScore(cityCenterMinStats: Stats): Float {
|
||||||
var sum = 0f
|
var sum = 0f
|
||||||
for (closeTile in tile.getTilesInDistance(2)) {
|
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
|
sum += tileYield
|
||||||
if (closeTile in tile.neighbors)
|
if (closeTile in tile.neighbors)
|
||||||
sum += tileYield
|
sum += tileYield
|
||||||
@ -155,24 +166,11 @@ class TileStatFunctions(val tile: Tile) {
|
|||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTileStartYield(isCenter: Boolean): Float {
|
private fun getTileStartYield(minimumStats: Stats) =
|
||||||
var stats = tile.getBaseTerrain().cloneStats()
|
getTerrainStats().run {
|
||||||
|
if (tile.resource != null) add(tile.tileResource)
|
||||||
for (terrainFeatureBase in tile.terrainFeatureObjects) {
|
coerceAtLeast(minimumStats)
|
||||||
if (terrainFeatureBase.overrideStats)
|
food + production + gold
|
||||||
stats = terrainFeatureBase.cloneStats()
|
|
||||||
else
|
|
||||||
stats.add(terrainFeatureBase)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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),
|
StatsFromTradeRoute("[stats] from each Trade Route", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||||
StatsFromGlobalCitiesFollowingReligion("[stats] for each global city following this religion", UniqueTarget.FounderBelief),
|
StatsFromGlobalCitiesFollowingReligion("[stats] for each global city following this religion", UniqueTarget.FounderBelief),
|
||||||
StatsFromGlobalFollowers("[stats] from every [amount] global followers [cityFilter]", 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
|
// Stat percentage boosts
|
||||||
StatPercentBonus("[relativeAmount]% [stat]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
StatPercentBonus("[relativeAmount]% [stat]", UniqueTarget.Global, UniqueTarget.FollowerBelief),
|
||||||
|
@ -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 {
|
fun toStringForNotifications() = this.joinToString {
|
||||||
(if (it.value > 0) "+" else "") + it.value.toInt() + " " + it.key.toString().tr(Constants.english)
|
(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 {
|
fun parse(string: String): Stats {
|
||||||
val toReturn = Stats()
|
val toReturn = Stats()
|
||||||
val statsWithBonuses = string.split(", ")
|
val statsWithBonuses = string.split(", ")
|
||||||
for(statWithBonuses in statsWithBonuses){
|
for(statWithBonuses in statsWithBonuses) {
|
||||||
val match = statRegex.matchEntire(statWithBonuses)!!
|
val match = statRegex.matchEntire(statWithBonuses)!!
|
||||||
val statName = match.groupValues[3]
|
val statName = match.groupValues[3]
|
||||||
val statAmount = match.groupValues[2].toFloat() * (if (match.groupValues[1] == "-") -1 else 1)
|
val statAmount = match.groupValues[2].toFloat() * (if (match.groupValues[1] == "-") -1 else 1)
|
||||||
@ -241,6 +241,9 @@ open class Stats(
|
|||||||
}
|
}
|
||||||
return toReturn
|
return toReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ZERO = Stats()
|
||||||
|
val DefaultCityCenterMinimum = Stats(food = 2f, production = 1f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +248,7 @@ class MapEditorEditImprovementsTab(
|
|||||||
companion object {
|
companion object {
|
||||||
//todo This should really be easier, the attributes should allow such a test in one go
|
//todo This should really be easier, the attributes should allow such a test in one go
|
||||||
private val disallowImprovements = listOf(
|
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 {
|
private fun TileImprovement.group() = when {
|
||||||
RoadStatus.values().any { it.name == name } -> 2
|
RoadStatus.values().any { it.name == name } -> 2
|
||||||
|
@ -197,8 +197,8 @@ object UnitActions {
|
|||||||
if (unit.civ.playerType != PlayerType.AI)
|
if (unit.civ.playerType != PlayerType.AI)
|
||||||
UncivGame.Current.settings.addCompletedTutorialTask("Found city")
|
UncivGame.Current.settings.addCompletedTutorialTask("Found city")
|
||||||
unit.civ.addCity(tile.position)
|
unit.civ.addCity(tile.position)
|
||||||
if (tile.ruleset.tileImprovements.containsKey("City center"))
|
if (tile.ruleset.tileImprovements.containsKey(Constants.cityCenter))
|
||||||
tile.changeImprovement("City center")
|
tile.changeImprovement(Constants.cityCenter)
|
||||||
tile.removeRoad()
|
tile.removeRoad()
|
||||||
|
|
||||||
if (hasActionModifiers) activateSideEffects(unit, unique)
|
if (hasActionModifiers) activateSideEffects(unit, unique)
|
||||||
|
@ -1491,6 +1491,11 @@ Simple unique parameters are explained by mouseover. Complex parameters are expl
|
|||||||
Applicable to: Terrain
|
Applicable to: Terrain
|
||||||
|
|
||||||
## Improvement uniques
|
## 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"
|
??? example "Can also be built on tiles adjacent to fresh water"
|
||||||
Applicable to: Improvement
|
Applicable to: Improvement
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.unciv.testing
|
package com.unciv.testing
|
||||||
|
|
||||||
import com.badlogic.gdx.Gdx
|
import com.badlogic.gdx.Gdx
|
||||||
|
import com.unciv.Constants
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
import com.unciv.json.json
|
import com.unciv.json.json
|
||||||
import com.unciv.logic.GameInfo
|
import com.unciv.logic.GameInfo
|
||||||
@ -74,8 +75,8 @@ class SerializationTests {
|
|||||||
val unit = civ.units.getCivUnits().first { it.hasUnique(UniqueType.FoundCity) }
|
val unit = civ.units.getCivUnits().first { it.hasUnique(UniqueType.FoundCity) }
|
||||||
val tile = unit.getTile()
|
val tile = unit.getTile()
|
||||||
unit.civ.addCity(tile.position)
|
unit.civ.addCity(tile.position)
|
||||||
if (tile.ruleset.tileImprovements.containsKey("City center"))
|
if (tile.ruleset.tileImprovements.containsKey(Constants.cityCenter))
|
||||||
tile.changeImprovement("City center")
|
tile.changeImprovement(Constants.cityCenter)
|
||||||
unit.destroy()
|
unit.destroy()
|
||||||
|
|
||||||
// Ensure some diplomacy objects are instantiated
|
// Ensure some diplomacy objects are instantiated
|
||||||
|
Loading…
x
Reference in New Issue
Block a user