chore: improvement stat functions are more related to 'stat' than to 'improvement'

This commit is contained in:
Yair Morgenstern 2023-02-02 20:22:13 +02:00
parent e21cfe8145
commit 38f544a35d
5 changed files with 145 additions and 143 deletions

View File

@ -1,7 +1,7 @@
package com.unciv.logic.automation
import com.unciv.logic.city.CityFocus
import com.unciv.logic.city.City
import com.unciv.logic.city.CityFocus
import com.unciv.logic.city.INonPerpetualConstruction
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.map.BFS
@ -389,7 +389,7 @@ object Automation {
// Improvements are good: less points
if (tile.improvement != null &&
tile.improvementFunctions.getImprovementStats(
tile.stats.getImprovementStats(
tile.getTileImprovement()!!,
city.civ,
city,

View File

@ -169,142 +169,6 @@ class TileInfoImprovementFunctions(val tile: Tile) {
}
// Also multiplies the stats by the percentage bonus for improvements (but not for tiles)
fun getImprovementStats(
improvement: TileImprovement,
observingCiv: Civilization,
city: City?,
cityUniqueCache: LocalUniqueCache = LocalUniqueCache(false)
): Stats {
val stats = improvement.cloneStats()
if (tile.hasViewableResource(observingCiv) && tile.tileResource.isImprovedBy(improvement.name)
&& tile.tileResource.improvementStats != null
)
stats.add(tile.tileResource.improvementStats!!.clone()) // resource-specific improvement
val conditionalState = StateForConditionals(civInfo = observingCiv, city = city, tile = tile)
for (unique in improvement.getMatchingUniques(UniqueType.Stats, conditionalState)) {
stats.add(unique.stats)
}
for (unique in improvement.getMatchingUniques(UniqueType.ImprovementStatsForAdjacencies, conditionalState)) {
val adjacent = unique.params[1]
val numberOfBonuses = tile.neighbors.count {
it.matchesFilter(adjacent, observingCiv)
|| it.getUnpillagedRoad().name == adjacent
}
stats.add(unique.stats.times(numberOfBonuses.toFloat()))
}
if (city != null) stats.add(getImprovementStatsForCity(improvement, city, conditionalState, cityUniqueCache))
for ((stat, value) in getImprovementPercentageStats(improvement, observingCiv, city, cityUniqueCache)) {
stats[stat] *= value.toPercent()
}
return stats
}
private fun getImprovementStatsForCity(
improvement: TileImprovement,
city: City,
conditionalState: StateForConditionals,
cityUniqueCache: LocalUniqueCache
): Stats {
val stats = Stats()
fun statsFromTiles(){
// Since the conditionalState contains the current tile, it is different for each tile,
// therefore if we want the cache to be useful it needs to hold the pre-filtered uniques,
// and then for each improvement we'll filter the uniques locally.
// This is still a MASSIVE save of RAM!
val tileUniques = cityUniqueCache.get(UniqueType.StatsFromTiles.name,
city.getMatchingUniques(UniqueType.StatsFromTiles, StateForConditionals.IgnoreConditionals)
.filter { city.matchesFilter(it.params[2]) }) // These are the uniques for all improvements for this city,
.filter { it.conditionalsApply(conditionalState) } // ...and this is those with applicable conditions
val improvementUniques =
improvement.getMatchingUniques(UniqueType.ImprovementStatsOnTile, conditionalState)
for (unique in tileUniques + improvementUniques) {
if (improvement.matchesFilter(unique.params[1])
|| unique.params[1] == Constants.freshWater && tile.isAdjacentTo(Constants.freshWater)
|| unique.params[1] == "non-fresh water" && !tile.isAdjacentTo(Constants.freshWater)
)
stats.add(unique.stats)
}
}
statsFromTiles()
fun statsFromObject() {
// Same as above - cache holds unfiltered uniques for the city, while we use only the filtered ones
val uniques = cityUniqueCache.get(UniqueType.StatsFromObject.name,
city.getMatchingUniques(UniqueType.StatsFromObject, StateForConditionals.IgnoreConditionals))
.filter { it.conditionalsApply(conditionalState) }
for (unique in uniques) {
if (improvement.matchesFilter(unique.params[1])) {
stats.add(unique.stats)
}
}
}
statsFromObject()
return stats
}
@Suppress("MemberVisibilityCanBePrivate")
fun getImprovementPercentageStats(
improvement: TileImprovement,
observingCiv: Civilization,
city: City?,
cityUniqueCache: LocalUniqueCache
): Stats {
val stats = Stats()
val conditionalState = StateForConditionals(civInfo = observingCiv, city = city, tile = tile)
// I would love to make an interface 'canCallMatchingUniques'
// from which both cityInfo and CivilizationInfo derive, so I don't have to duplicate all this code
// But something something too much for this PR.
if (city != null) {
// As above, since the conditional is tile-dependant,
// we save uniques in the cache without conditional filtering, and use only filtered ones
val allStatPercentUniques = cityUniqueCache.get(UniqueType.AllStatsPercentFromObject.name,
city.getMatchingUniques(UniqueType.AllStatsPercentFromObject, StateForConditionals.IgnoreConditionals))
.filter { it.conditionalsApply(conditionalState) }
for (unique in allStatPercentUniques) {
if (!improvement.matchesFilter(unique.params[1])) continue
for (stat in Stat.values()) {
stats[stat] += unique.params[0].toFloat()
}
}
// Same trick different unique - not sure if worth generalizing this 'late apply' of conditions?
val statPercentUniques = cityUniqueCache.get(UniqueType.StatPercentFromObject.name,
city.getMatchingUniques(UniqueType.StatPercentFromObject, StateForConditionals.IgnoreConditionals))
.filter { it.conditionalsApply(conditionalState) }
for (unique in statPercentUniques) {
if (!improvement.matchesFilter(unique.params[2])) continue
val stat = Stat.valueOf(unique.params[1])
stats[stat] += unique.params[0].toFloat()
}
} else {
for (unique in observingCiv.getMatchingUniques(UniqueType.AllStatsPercentFromObject, conditionalState)) {
if (!improvement.matchesFilter(unique.params[1])) continue
for (stat in Stat.values()) {
stats[stat] += unique.params[0].toFloat()
}
}
for (unique in observingCiv.getMatchingUniques(UniqueType.StatPercentFromObject, conditionalState)) {
if (!improvement.matchesFilter(unique.params[2])) continue
val stat = Stat.valueOf(unique.params[1])
stats[stat] += unique.params[0].toFloat()
}
}
return stats
}
/** Marks tile as target tile for a building with a [UniqueType.CreatesOneImprovement] unique */
fun markForCreatesOneImprovement(improvement: String) {

View File

@ -3,6 +3,7 @@ package com.unciv.logic.map.tile
import com.unciv.Constants
import com.unciv.logic.city.City
import com.unciv.logic.civilization.Civilization
import com.unciv.models.ruleset.tile.TileImprovement
import com.unciv.models.ruleset.unique.LocalUniqueCache
import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.UniqueType
@ -71,7 +72,7 @@ class TileStatFunctions(val tile: Tile) {
val improvement = tile.getUnpillagedTileImprovement()
if (improvement != null)
stats.add(tile.improvementFunctions.getImprovementStats(improvement, observingCiv, city, localUniqueCache))
stats.add(getImprovementStats(improvement, observingCiv, city, localUniqueCache))
if (stats.gold != 0f && observingCiv.goldenAges.isGoldenAge())
stats.gold++
@ -174,4 +175,141 @@ class TileStatFunctions(val tile: Tile) {
return stats.food + stats.production + stats.gold
}
// Also multiplies the stats by the percentage bonus for improvements (but not for tiles)
fun getImprovementStats(
improvement: TileImprovement,
observingCiv: Civilization,
city: City?,
cityUniqueCache: LocalUniqueCache = LocalUniqueCache(false)
): Stats {
val stats = improvement.cloneStats()
if (tile.hasViewableResource(observingCiv) && tile.tileResource.isImprovedBy(improvement.name)
&& tile.tileResource.improvementStats != null
)
stats.add(tile.tileResource.improvementStats!!.clone()) // resource-specific improvement
val conditionalState = StateForConditionals(civInfo = observingCiv, city = city, tile = tile)
for (unique in improvement.getMatchingUniques(UniqueType.Stats, conditionalState)) {
stats.add(unique.stats)
}
for (unique in improvement.getMatchingUniques(UniqueType.ImprovementStatsForAdjacencies, conditionalState)) {
val adjacent = unique.params[1]
val numberOfBonuses = tile.neighbors.count {
it.matchesFilter(adjacent, observingCiv)
|| it.getUnpillagedRoad().name == adjacent
}
stats.add(unique.stats.times(numberOfBonuses.toFloat()))
}
if (city != null) stats.add(getImprovementStatsForCity(improvement, city, conditionalState, cityUniqueCache))
for ((stat, value) in getImprovementPercentageStats(improvement, observingCiv, city, cityUniqueCache)) {
stats[stat] *= value.toPercent()
}
return stats
}
private fun getImprovementStatsForCity(
improvement: TileImprovement,
city: City,
conditionalState: StateForConditionals,
cityUniqueCache: LocalUniqueCache
): Stats {
val stats = Stats()
fun statsFromTiles(){
// Since the conditionalState contains the current tile, it is different for each tile,
// therefore if we want the cache to be useful it needs to hold the pre-filtered uniques,
// and then for each improvement we'll filter the uniques locally.
// This is still a MASSIVE save of RAM!
val tileUniques = cityUniqueCache.get(UniqueType.StatsFromTiles.name,
city.getMatchingUniques(UniqueType.StatsFromTiles, StateForConditionals.IgnoreConditionals)
.filter { city.matchesFilter(it.params[2]) }) // These are the uniques for all improvements for this city,
.filter { it.conditionalsApply(conditionalState) } // ...and this is those with applicable conditions
val improvementUniques =
improvement.getMatchingUniques(UniqueType.ImprovementStatsOnTile, conditionalState)
for (unique in tileUniques + improvementUniques) {
if (improvement.matchesFilter(unique.params[1])
|| unique.params[1] == Constants.freshWater && tile.isAdjacentTo(Constants.freshWater)
|| unique.params[1] == "non-fresh water" && !tile.isAdjacentTo(Constants.freshWater)
)
stats.add(unique.stats)
}
}
statsFromTiles()
fun statsFromObject() {
// Same as above - cache holds unfiltered uniques for the city, while we use only the filtered ones
val uniques = cityUniqueCache.get(UniqueType.StatsFromObject.name,
city.getMatchingUniques(UniqueType.StatsFromObject, StateForConditionals.IgnoreConditionals))
.filter { it.conditionalsApply(conditionalState) }
for (unique in uniques) {
if (improvement.matchesFilter(unique.params[1])) {
stats.add(unique.stats)
}
}
}
statsFromObject()
return stats
}
@Suppress("MemberVisibilityCanBePrivate")
fun getImprovementPercentageStats(
improvement: TileImprovement,
observingCiv: Civilization,
city: City?,
cityUniqueCache: LocalUniqueCache
): Stats {
val stats = Stats()
val conditionalState = StateForConditionals(civInfo = observingCiv, city = city, tile = tile)
// I would love to make an interface 'canCallMatchingUniques'
// from which both cityInfo and CivilizationInfo derive, so I don't have to duplicate all this code
// But something something too much for this PR.
if (city != null) {
// As above, since the conditional is tile-dependant,
// we save uniques in the cache without conditional filtering, and use only filtered ones
val allStatPercentUniques = cityUniqueCache.get(UniqueType.AllStatsPercentFromObject.name,
city.getMatchingUniques(UniqueType.AllStatsPercentFromObject, StateForConditionals.IgnoreConditionals))
.filter { it.conditionalsApply(conditionalState) }
for (unique in allStatPercentUniques) {
if (!improvement.matchesFilter(unique.params[1])) continue
for (stat in Stat.values()) {
stats[stat] += unique.params[0].toFloat()
}
}
// Same trick different unique - not sure if worth generalizing this 'late apply' of conditions?
val statPercentUniques = cityUniqueCache.get(UniqueType.StatPercentFromObject.name,
city.getMatchingUniques(UniqueType.StatPercentFromObject, StateForConditionals.IgnoreConditionals))
.filter { it.conditionalsApply(conditionalState) }
for (unique in statPercentUniques) {
if (!improvement.matchesFilter(unique.params[2])) continue
val stat = Stat.valueOf(unique.params[1])
stats[stat] += unique.params[0].toFloat()
}
} else {
for (unique in observingCiv.getMatchingUniques(UniqueType.AllStatsPercentFromObject, conditionalState)) {
if (!improvement.matchesFilter(unique.params[1])) continue
for (stat in Stat.values()) {
stats[stat] += unique.params[0].toFloat()
}
}
for (unique in observingCiv.getMatchingUniques(UniqueType.StatPercentFromObject, conditionalState)) {
if (!improvement.matchesFilter(unique.params[2])) continue
val stat = Stat.valueOf(unique.params[1])
stats[stat] += unique.params[0].toFloat()
}
}
return stats
}
}

View File

@ -197,13 +197,13 @@ class CityScreen(
fun isExistingImprovementValuable(tile: Tile, improvementToPlace: TileImprovement): Boolean {
if (tile.improvement == null) return false
val civInfo = city.civ
val existingStats = tile.improvementFunctions.getImprovementStats(
val existingStats = tile.stats.getImprovementStats(
tile.getTileImprovement()!!,
civInfo,
city,
cityUniqueCache
)
val replacingStats = tile.improvementFunctions.getImprovementStats(
val replacingStats = tile.stats.getImprovementStats(
improvementToPlace,
civInfo,
city,

View File

@ -155,7 +155,7 @@ class ImprovementPickerScreen(
val statIcons = getStatIconsTable(provideResource, removeImprovement)
// get benefits of the new improvement
val stats = tile.improvementFunctions.getImprovementStats(
val stats = tile.stats.getImprovementStats(
improvement,
currentPlayerCiv,
tile.getCity(),
@ -164,7 +164,7 @@ class ImprovementPickerScreen(
// subtract the benefits of the replaced improvement, if any
val existingImprovement = tile.getTileImprovement()
if (existingImprovement != null && removeImprovement) {
val existingStats = tile.improvementFunctions.getImprovementStats(
val existingStats = tile.stats.getImprovementStats(
existingImprovement,
currentPlayerCiv,
tile.getCity(),