G&K Neutral Tile Road Maintenance (#8138)

* Redo

* New parameterized and actually used Road Unique

* Remove unneeded

* Update CURRENT_COMPATIBILITY_NUMBER since adding new serialization data

* Make neutralRoads for CivilizationInfo.kt Transient
This commit is contained in:
itanasi
2022-12-24 09:43:37 -08:00
committed by GitHub
parent 8d0acd0647
commit bc483e8984
9 changed files with 90 additions and 28 deletions

View File

@ -1,5 +1,5 @@
{ {
"name": "Global uniques", "name": "Global uniques",
"uniques": [ "uniques": [
"[-75]% growth [in all cities] <when between [-10] and [0] Happiness>", "[-75]% growth [in all cities] <when between [-10] and [0] Happiness>",
"Nullifies Growth [in all cities] <when below [-10] Happiness>", "Nullifies Growth [in all cities] <when below [-10] Happiness>",
@ -7,19 +7,19 @@
"[-33]% Strength <for [All] units> <when below [-10] Happiness>", "[-33]% Strength <for [All] units> <when below [-10] Happiness>",
"Cannot build [Settler] units <when below [-10] Happiness>", "Cannot build [Settler] units <when below [-10] Happiness>",
"Rebel units may spawn <when below [-20] Happiness>" "Rebel units may spawn <when below [-20] Happiness>"
// TODO: Implement the uniques below // TODO: Implement the uniques below
// "[+20]% [Culture] [in all cities] <during a golden age>", // "[+20]% [Culture] [in all cities] <during a golden age>",
// "[+20]% [Production] [in all cities] <during a golden age>", // "[+20]% [Production] [in all cities] <during a golden age>",
// "[+10]% growth [in all cities] <during We Love The King Day>", // "[+10]% growth [in all cities] <during We Love The King Day>",
// "Nullifies All Yield <while is in resistance>", // "Nullifies All Yield <while is in resistance>",
// "[-25]% [Science] [in pupetted cities]" -- Imo cityFilters should ideally become conditionals anyway // "[-25]% [Science] [in pupetted cities]" -- Imo cityFilters should ideally become conditionals anyway
// "[-25]% [Culture] [in pupetted cities]" // "[-25]% [Culture] [in pupetted cities]"
// "[+20]% [Production] [in cities connected via railroad]" // "[+20]% [Production] [in cities connected via railroad]"
// something something unit supply // something something unit supply
] ]
} }

View File

@ -108,8 +108,7 @@
"terrainsCanBeBuiltOn": ["Land"], "terrainsCanBeBuiltOn": ["Land"],
"turnsToBuild": 4, "turnsToBuild": 4,
"techRequired": "The Wheel", "techRequired": "The Wheel",
// "Costs [1] gold per turn when in your territory" does nothing and is only to inform the user "uniques": ["Can be built outside your borders", "Costs [1] [Gold] per turn"],
"uniques": ["Can be built outside your borders", "Costs [1] gold per turn when in your territory"],
"shortcutKey": "R", "shortcutKey": "R",
"civilopediaText": [ "civilopediaText": [
{"text":"Reduces movement cost to ½ if the other tile also has a Road or Railroad"}, {"text":"Reduces movement cost to ½ if the other tile also has a Road or Railroad"},
@ -122,7 +121,7 @@
"terrainsCanBeBuiltOn": ["Land"], "terrainsCanBeBuiltOn": ["Land"],
"turnsToBuild": 4, "turnsToBuild": 4,
"techRequired": "Railroads", "techRequired": "Railroads",
"uniques": ["Can be built outside your borders", "Costs [2] gold per turn when in your territory"], "uniques": ["Can be built outside your borders", "Costs [2] [Gold] per turn"],
"shortcutKey": "R", "shortcutKey": "R",
"civilopediaText": [{"text":"Reduces movement cost to ⅒ if the other tile also has a Railroad"}] "civilopediaText": [{"text":"Reduces movement cost to ⅒ if the other tile also has a Railroad"}]
}, },

View File

@ -108,8 +108,7 @@
"terrainsCanBeBuiltOn": ["Land"], "terrainsCanBeBuiltOn": ["Land"],
"turnsToBuild": 4, "turnsToBuild": 4,
"techRequired": "The Wheel", "techRequired": "The Wheel",
// "Costs [1] gold per turn when in your territory" does nothing and is only to inform the user "uniques": ["Can be built outside your borders", "Costs [1] [Gold] per turn when in your territory"],
"uniques": ["Can be built outside your borders", "Costs [1] gold per turn when in your territory"],
"shortcutKey": "R", "shortcutKey": "R",
"civilopediaText": [ "civilopediaText": [
{"text":"Reduces movement cost to ½ if the other tile also has a Road or Railroad"}, {"text":"Reduces movement cost to ½ if the other tile also has a Road or Railroad"},
@ -122,7 +121,7 @@
"terrainsCanBeBuiltOn": ["Land"], "terrainsCanBeBuiltOn": ["Land"],
"turnsToBuild": 4, "turnsToBuild": 4,
"techRequired": "Railroads", "techRequired": "Railroads",
"uniques": ["Can be built outside your borders", "Costs [2] gold per turn when in your territory"], "uniques": ["Can be built outside your borders", "Costs [2] [Gold] per turn when in your territory"],
"shortcutKey": "R", "shortcutKey": "R",
"civilopediaText": [{"text":"Reduces movement cost to ⅒ if the other tile also has a Railroad"}] "civilopediaText": [{"text":"Reduces movement cost to ⅒ if the other tile also has a Railroad"}]
}, },

View File

@ -66,7 +66,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
companion object { companion object {
/** The current compatibility version of [GameInfo]. This number is incremented whenever changes are made to the save file structure that guarantee that /** The current compatibility version of [GameInfo]. This number is incremented whenever changes are made to the save file structure that guarantee that
* previous versions of the game will not be able to load or play a game normally. */ * previous versions of the game will not be able to load or play a game normally. */
const val CURRENT_COMPATIBILITY_NUMBER = 2 const val CURRENT_COMPATIBILITY_NUMBER = 3
val CURRENT_COMPATIBILITY_VERSION = CompatibilityVersion(CURRENT_COMPATIBILITY_NUMBER, UncivGame.VERSION) val CURRENT_COMPATIBILITY_VERSION = CompatibilityVersion(CURRENT_COMPATIBILITY_NUMBER, UncivGame.VERSION)
@ -498,6 +498,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
civInfo.thingsToFocusOnForVictory = civInfo.thingsToFocusOnForVictory =
civInfo.getPreferredVictoryTypeObject()?.getThingsToFocus(civInfo) ?: setOf() civInfo.getPreferredVictoryTypeObject()?.getThingsToFocus(civInfo) ?: setOf()
} }
tileMap.setNeutralTransients() // has to happen after civInfo.setTransients() sets owningCity
convertFortify() convertFortify()

View File

@ -73,8 +73,8 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
return cost.toInt() return cost.toInt()
} }
private fun getTransportationUpkeep(): Int { private fun getTransportationUpkeep(): Stats {
var transportationUpkeep = 0f val transportationUpkeep = Stats()
// we no longer use .flatMap, because there are a lot of tiles and keeping them all in a list // we no longer use .flatMap, because there are a lot of tiles and keeping them all in a list
// just to go over them once is a waste of memory - there are low-end phones who don't have much ram // just to go over them once is a waste of memory - there are low-end phones who don't have much ram
@ -88,14 +88,38 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
if (tile.isCityCenter()) continue if (tile.isCityCenter()) continue
if (tile.getUnpillagedRoad() == RoadStatus.None) continue // Cheap checks before pricey checks if (tile.getUnpillagedRoad() == RoadStatus.None) continue // Cheap checks before pricey checks
if (ignoredTileTypes.any { tile.matchesFilter(it, civInfo) }) continue if (ignoredTileTypes.any { tile.matchesFilter(it, civInfo) }) continue
val road = tile.getUnpillagedRoadImprovement()
transportationUpkeep += tile.getUnpillagedRoad().upkeep if (road!!.hasUnique(UniqueType.ImprovementMaintenance, StateForConditionals(civInfo, tile = tile))) {
for(unique in road.getMatchingUniques(UniqueType.ImprovementMaintenance)) {
transportationUpkeep.add(Stat.valueOf(unique.params[1]), unique.params[0].toFloat())
}
}
if (road.hasUnique(UniqueType.ImprovementAllMaintenance, StateForConditionals(civInfo, tile = tile))) {
for(unique in road.getMatchingUniques(UniqueType.ImprovementAllMaintenance)) {
transportationUpkeep.add(Stat.valueOf(unique.params[1]), unique.params[0].toFloat())
}
}
// backwards compatible
if (road.hasUnique(UniqueType.OldImprovementMaintenance, StateForConditionals(civInfo, tile = tile))) {
transportationUpkeep.add(Stat.Gold, tile.getUnpillagedRoad().upkeep.toFloat())
}
}
}
// tabulate neutral roads
for (position in civInfo.neutralRoads) {
val tile = civInfo.gameInfo.tileMap[position]
if (tile.getUnpillagedRoad() == RoadStatus.None) continue // Cheap checks before pricey checks
val road = tile.getUnpillagedRoadImprovement()
if (road!!.hasUnique(UniqueType.ImprovementAllMaintenance, StateForConditionals(civInfo, tile = tile))) {
for(unique in road.getMatchingUniques(UniqueType.ImprovementAllMaintenance)) {
transportationUpkeep.add(Stat.valueOf(unique.params[1]), unique.params[0].toFloat())
}
} }
} }
for (unique in civInfo.getMatchingUniques(UniqueType.RoadMaintenance)) for (unique in civInfo.getMatchingUniques(UniqueType.RoadMaintenance))
transportationUpkeep *= unique.params[0].toPercent() transportationUpkeep.times(unique.params[0].toPercent())
return transportationUpkeep.toInt() return transportationUpkeep
} }
fun getUnitSupply(): Int { fun getUnitSupply(): Int {
@ -156,7 +180,7 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
} }
} }
statMap["Transportation upkeep"] = Stats(gold = -getTransportationUpkeep().toFloat()) statMap["Transportation upkeep"] = getTransportationUpkeep() * -1
statMap["Unit upkeep"] = Stats(gold = -getUnitMaintenance().toFloat()) statMap["Unit upkeep"] = Stats(gold = -getUnitMaintenance().toFloat())

View File

@ -145,6 +145,9 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
@Transient @Transient
var thingsToFocusOnForVictory = setOf<Victory.Focus>() var thingsToFocusOnForVictory = setOf<Victory.Focus>()
@Transient
var neutralRoads = HashSet<Vector2>()
var playerType = PlayerType.AI var playerType = PlayerType.AI
/** Used in online multiplayer for human players */ /** Used in online multiplayer for human players */
@ -280,6 +283,7 @@ class CivilizationInfo : IsPartOfGameInfoSerialization {
toReturn.diplomacy[diplomacyManager.otherCivName] = diplomacyManager toReturn.diplomacy[diplomacyManager.otherCivName] = diplomacyManager
toReturn.proximity.putAll(proximity) toReturn.proximity.putAll(proximity)
toReturn.cities = cities.map { it.clone() } toReturn.cities = cities.map { it.clone() }
toReturn.neutralRoads = neutralRoads
// This is the only thing that is NOT switched out, which makes it a source of ConcurrentModification errors. // This is the only thing that is NOT switched out, which makes it a source of ConcurrentModification errors.
// Cloning it by-pointer is a horrific move, since the serialization would go over it ANYWAY and still lead to concurrency problems. // Cloning it by-pointer is a horrific move, since the serialization would go over it ANYWAY and still lead to concurrency problems.

View File

@ -44,10 +44,20 @@ open class TileInfo : IsPartOfGameInfoSerialization {
private set private set
fun setOwningCity(city:CityInfo?){ fun setOwningCity(city:CityInfo?){
if (city != null) {
if (roadStatus != RoadStatus.None && roadOwner != "") {
// remove previous neutral tile owner
getRoadOwner()!!.neutralRoads.remove(this.position)
}
roadOwner = city.civInfo.civName // only when taking control, otherwise last owner
} else {
if (roadStatus != RoadStatus.None && owningCity != null) {
// previous tile owner still owns road, add to tracker
owningCity!!.civInfo.neutralRoads.add(this.position)
}
}
owningCity = city owningCity = city
isCityCenterInternal = getCity()?.location == position isCityCenterInternal = getCity()?.location == position
if (city != null) // only when taking control, otherwise last owner
roadOwner = city.civInfo.civName
} }
@Transient @Transient
@ -272,6 +282,10 @@ open class TileInfo : IsPartOfGameInfoSerialization {
else else
roadStatus roadStatus
} }
fun getUnpillagedRoadImprovement(): TileImprovement? {
return if (getUnpillagedRoad() == RoadStatus.None) null
else ruleset.tileImprovements[getUnpillagedRoad().name]
}
fun changeImprovement(improvementStr: String?) { fun changeImprovement(improvementStr: String?) {
improvementIsPillaged = false improvementIsPillaged = false
@ -282,16 +296,20 @@ open class TileInfo : IsPartOfGameInfoSerialization {
fun addRoad(roadType: RoadStatus, unitCivInfo: CivilizationInfo) { fun addRoad(roadType: RoadStatus, unitCivInfo: CivilizationInfo) {
roadStatus = roadType roadStatus = roadType
roadIsPillaged = false roadIsPillaged = false
roadOwner = if (getOwner() == null) if (getOwner() == null) {
unitCivInfo.civName // neutral tile, use building unit roadOwner = unitCivInfo.civName // neutral tile, use building unit
else unitCivInfo.neutralRoads.add(this.position)
getOwner()!!.civName } else {
roadOwner = getOwner()!!.civName
}
} }
// function handling when removing a road from the tile // function handling when removing a road from the tile
fun removeRoad() { fun removeRoad() {
roadStatus = RoadStatus.None roadStatus = RoadStatus.None
roadIsPillaged = false roadIsPillaged = false
if (owningCity == null)
getRoadOwner()!!.neutralRoads.remove(this.position)
} }
fun getShownImprovement(viewingCiv: CivilizationInfo?): String? { fun getShownImprovement(viewingCiv: CivilizationInfo?): String? {
@ -1165,6 +1183,7 @@ open class TileInfo : IsPartOfGameInfoSerialization {
fun setTransients() { fun setTransients() {
setTerrainTransients() setTerrainTransients()
setUnitTransients(true) setUnitTransients(true)
setOwnerTransients()
} }
fun setTerrainTransients() { fun setTerrainTransients() {
@ -1194,6 +1213,11 @@ open class TileInfo : IsPartOfGameInfoSerialization {
} }
} }
fun setOwnerTransients() {
if (owningCity == null && roadOwner != "")
getRoadOwner()!!.neutralRoads.add(this.position)
}
fun stripUnits() { fun stripUnits() {
for (unit in this.getUnits()) removeUnit(unit) for (unit in this.getUnits()) removeUnit(unit)
} }

View File

@ -459,6 +459,14 @@ class TileMap : IsPartOfGameInfoSerialization {
} }
} }
/** Initialize based on TileInfo which Civ has neutral tile roads
*/
fun setNeutralTransients() {
for (tileInfo in values) {
tileInfo.setOwnerTransients()
}
}
fun removeMissingTerrainModReferences(ruleSet: Ruleset) { fun removeMissingTerrainModReferences(ruleSet: Ruleset) {
for (tile in this.values) { for (tile in this.values) {
for (terrainFeature in tile.terrainFeatures.filter { !ruleSet.terrains.containsKey(it) }) for (terrainFeature in tile.terrainFeatures.filter { !ruleSet.terrains.containsKey(it) })

View File

@ -565,7 +565,10 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags:
RemovesFeaturesIfBuilt("Removes removable features when built", UniqueTarget.Improvement), RemovesFeaturesIfBuilt("Removes removable features when built", UniqueTarget.Improvement),
DefensiveBonus("Gives a defensive bonus of [relativeAmount]%", UniqueTarget.Improvement), DefensiveBonus("Gives a defensive bonus of [relativeAmount]%", UniqueTarget.Improvement),
ImprovementMaintenance("Costs [amount] gold per turn when in your territory", UniqueTarget.Improvement), // Unused ImprovementMaintenance("Costs [amount] [stat] per turn when in your territory", UniqueTarget.Improvement), // Roads
ImprovementAllMaintenance("Costs [amount] [stat] per turn", UniqueTarget.Improvement), // Roads
//@Deprecated("as of 4.3.9", ReplaceWith("Costs [amount] [stats] per turn when in your territory"), DeprecationLevel.ERROR)
OldImprovementMaintenance("Costs [amount] gold per turn when in your territory", UniqueTarget.Improvement), // unused
DamagesAdjacentEnemyUnits("Adjacent enemy units ending their turn take [amount] damage", UniqueTarget.Improvement), DamagesAdjacentEnemyUnits("Adjacent enemy units ending their turn take [amount] damage", UniqueTarget.Improvement),
TakeOverTilesAroundWhenBuilt("Constructing it will take over the tiles around it and assign them to your closest city", UniqueTarget.Improvement), TakeOverTilesAroundWhenBuilt("Constructing it will take over the tiles around it and assign them to your closest city", UniqueTarget.Improvement),