diff --git a/android/assets/jsons/Civ V - Gods & Kings/GlobalUniques.json b/android/assets/jsons/Civ V - Gods & Kings/GlobalUniques.json index 24c0c3d4f1..d26ebaa47f 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/GlobalUniques.json +++ b/android/assets/jsons/Civ V - Gods & Kings/GlobalUniques.json @@ -1,5 +1,5 @@ { - "name": "Global uniques", + "name": "Global uniques", "uniques": [ "[-75]% growth [in all cities] ", "Nullifies Growth [in all cities] ", @@ -7,19 +7,19 @@ "[-33]% Strength ", "Cannot build [Settler] units ", "Rebel units may spawn " - + // TODO: Implement the uniques below // "[+20]% [Culture] [in all cities] ", // "[+20]% [Production] [in all cities] ", - + // "[+10]% growth [in all cities] ", - + // "Nullifies All Yield ", - + // "[-25]% [Science] [in pupetted cities]" -- Imo cityFilters should ideally become conditionals anyway // "[-25]% [Culture] [in pupetted cities]" - + // "[+20]% [Production] [in cities connected via railroad]" // something something unit supply ] -} \ No newline at end of file +} diff --git a/android/assets/jsons/Civ V - Gods & Kings/TileImprovements.json b/android/assets/jsons/Civ V - Gods & Kings/TileImprovements.json index 845e7a4a21..7aeb217c83 100644 --- a/android/assets/jsons/Civ V - Gods & Kings/TileImprovements.json +++ b/android/assets/jsons/Civ V - Gods & Kings/TileImprovements.json @@ -108,8 +108,7 @@ "terrainsCanBeBuiltOn": ["Land"], "turnsToBuild": 4, "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"], "shortcutKey": "R", "civilopediaText": [ {"text":"Reduces movement cost to ½ if the other tile also has a Road or Railroad"}, @@ -122,7 +121,7 @@ "terrainsCanBeBuiltOn": ["Land"], "turnsToBuild": 4, "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", "civilopediaText": [{"text":"Reduces movement cost to ⅒ if the other tile also has a Railroad"}] }, diff --git a/android/assets/jsons/Civ V - Vanilla/TileImprovements.json b/android/assets/jsons/Civ V - Vanilla/TileImprovements.json index 1edf5df41b..2a5d6b18a5 100644 --- a/android/assets/jsons/Civ V - Vanilla/TileImprovements.json +++ b/android/assets/jsons/Civ V - Vanilla/TileImprovements.json @@ -108,8 +108,7 @@ "terrainsCanBeBuiltOn": ["Land"], "turnsToBuild": 4, "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", "civilopediaText": [ {"text":"Reduces movement cost to ½ if the other tile also has a Road or Railroad"}, @@ -122,7 +121,7 @@ "terrainsCanBeBuiltOn": ["Land"], "turnsToBuild": 4, "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", "civilopediaText": [{"text":"Reduces movement cost to ⅒ if the other tile also has a Railroad"}] }, diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index 2f444239a6..032a13a5c7 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -66,7 +66,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion companion object { /** 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. */ - const val CURRENT_COMPATIBILITY_NUMBER = 2 + const val CURRENT_COMPATIBILITY_NUMBER = 3 val CURRENT_COMPATIBILITY_VERSION = CompatibilityVersion(CURRENT_COMPATIBILITY_NUMBER, UncivGame.VERSION) @@ -498,6 +498,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion civInfo.thingsToFocusOnForVictory = civInfo.getPreferredVictoryTypeObject()?.getThingsToFocus(civInfo) ?: setOf() } + tileMap.setNeutralTransients() // has to happen after civInfo.setTransients() sets owningCity convertFortify() diff --git a/core/src/com/unciv/logic/civilization/CivInfoStats.kt b/core/src/com/unciv/logic/civilization/CivInfoStats.kt index 5761943eae..0fdc190ab7 100644 --- a/core/src/com/unciv/logic/civilization/CivInfoStats.kt +++ b/core/src/com/unciv/logic/civilization/CivInfoStats.kt @@ -73,8 +73,8 @@ class CivInfoStats(val civInfo: CivilizationInfo) { return cost.toInt() } - private fun getTransportationUpkeep(): Int { - var transportationUpkeep = 0f + private fun getTransportationUpkeep(): Stats { + val transportationUpkeep = Stats() // 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 @@ -88,14 +88,38 @@ class CivInfoStats(val civInfo: CivilizationInfo) { if (tile.isCityCenter()) continue if (tile.getUnpillagedRoad() == RoadStatus.None) continue // Cheap checks before pricey checks if (ignoredTileTypes.any { tile.matchesFilter(it, civInfo) }) continue - - transportationUpkeep += tile.getUnpillagedRoad().upkeep + val road = tile.getUnpillagedRoadImprovement() + 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)) - transportationUpkeep *= unique.params[0].toPercent() + transportationUpkeep.times(unique.params[0].toPercent()) - return transportationUpkeep.toInt() + return transportationUpkeep } 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()) diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index 3439bc84c6..73dba40188 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -145,6 +145,9 @@ class CivilizationInfo : IsPartOfGameInfoSerialization { @Transient var thingsToFocusOnForVictory = setOf() + @Transient + var neutralRoads = HashSet() + var playerType = PlayerType.AI /** Used in online multiplayer for human players */ @@ -280,6 +283,7 @@ class CivilizationInfo : IsPartOfGameInfoSerialization { toReturn.diplomacy[diplomacyManager.otherCivName] = diplomacyManager toReturn.proximity.putAll(proximity) 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. // Cloning it by-pointer is a horrific move, since the serialization would go over it ANYWAY and still lead to concurrency problems. diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index 99c8445f04..fc63a806d0 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -44,10 +44,20 @@ open class TileInfo : IsPartOfGameInfoSerialization { private set 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 isCityCenterInternal = getCity()?.location == position - if (city != null) // only when taking control, otherwise last owner - roadOwner = city.civInfo.civName } @Transient @@ -272,6 +282,10 @@ open class TileInfo : IsPartOfGameInfoSerialization { else roadStatus } + fun getUnpillagedRoadImprovement(): TileImprovement? { + return if (getUnpillagedRoad() == RoadStatus.None) null + else ruleset.tileImprovements[getUnpillagedRoad().name] + } fun changeImprovement(improvementStr: String?) { improvementIsPillaged = false @@ -282,16 +296,20 @@ open class TileInfo : IsPartOfGameInfoSerialization { fun addRoad(roadType: RoadStatus, unitCivInfo: CivilizationInfo) { roadStatus = roadType roadIsPillaged = false - roadOwner = if (getOwner() == null) - unitCivInfo.civName // neutral tile, use building unit - else - getOwner()!!.civName + if (getOwner() == null) { + roadOwner = unitCivInfo.civName // neutral tile, use building unit + unitCivInfo.neutralRoads.add(this.position) + } else { + roadOwner = getOwner()!!.civName + } } // function handling when removing a road from the tile fun removeRoad() { roadStatus = RoadStatus.None roadIsPillaged = false + if (owningCity == null) + getRoadOwner()!!.neutralRoads.remove(this.position) } fun getShownImprovement(viewingCiv: CivilizationInfo?): String? { @@ -1165,6 +1183,7 @@ open class TileInfo : IsPartOfGameInfoSerialization { fun setTransients() { setTerrainTransients() setUnitTransients(true) + setOwnerTransients() } fun setTerrainTransients() { @@ -1194,6 +1213,11 @@ open class TileInfo : IsPartOfGameInfoSerialization { } } + fun setOwnerTransients() { + if (owningCity == null && roadOwner != "") + getRoadOwner()!!.neutralRoads.add(this.position) + } + fun stripUnits() { for (unit in this.getUnits()) removeUnit(unit) } diff --git a/core/src/com/unciv/logic/map/TileMap.kt b/core/src/com/unciv/logic/map/TileMap.kt index ff73f792e5..c0b24a581d 100644 --- a/core/src/com/unciv/logic/map/TileMap.kt +++ b/core/src/com/unciv/logic/map/TileMap.kt @@ -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) { for (tile in this.values) { for (terrainFeature in tile.terrainFeatures.filter { !ruleSet.terrains.containsKey(it) }) diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 77c660272d..a5295bda30 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -565,7 +565,10 @@ enum class UniqueType(val text: String, vararg targets: UniqueTarget, val flags: RemovesFeaturesIfBuilt("Removes removable features when built", 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), TakeOverTilesAroundWhenBuilt("Constructing it will take over the tiles around it and assign them to your closest city", UniqueTarget.Improvement),