From e84887b8c299a17c795bb8c8c7893283c6e1129c Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Thu, 2 Feb 2023 19:50:00 +0200 Subject: [PATCH] chore: separated unit cached values into separate file --- .../automation/city/ConstructionAutomation.kt | 2 +- .../automation/unit/SpecificUnitAutomation.kt | 2 +- .../logic/automation/unit/UnitAutomation.kt | 8 +- .../battle/GreatGeneralImplementation.kt | 2 +- .../com/unciv/logic/map/mapunit/MapUnit.kt | 125 +---------------- .../unciv/logic/map/mapunit/MapUnitCache.kt | 128 ++++++++++++++++++ .../map/mapunit/UnitMovementAlgorithms.kt | 46 +++---- .../unciv/logic/map/mapunit/UnitPromotions.kt | 2 +- .../logic/map/mapunit/UnitTurnManager.kt | 2 +- core/src/com/unciv/logic/trade/TradeLogic.kt | 2 +- .../com/unciv/ui/worldscreen/WorldScreen.kt | 4 +- .../worldscreen/unit/actions/UnitActions.kt | 17 ++- .../logic/map/UnitMovementAlgorithmsTests.kt | 6 +- 13 files changed, 184 insertions(+), 162 deletions(-) create mode 100644 core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt diff --git a/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt b/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt index 8fa1368d3f..54931c8bec 100644 --- a/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt +++ b/core/src/com/unciv/logic/automation/city/ConstructionAutomation.kt @@ -38,7 +38,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){ private val civUnits = civInfo.units.getCivUnits() private val militaryUnits = civUnits.count { it.baseUnit.isMilitary() } - private val workers = civUnits.count { it.hasUniqueToBuildImprovements && it.isCivilian() }.toFloat() + private val workers = civUnits.count { it.cache.hasUniqueToBuildImprovements && it.isCivilian() }.toFloat() private val cities = civInfo.cities.size private val allTechsAreResearched = civInfo.gameInfo.ruleSet.technologies.values .all { civInfo.tech.isResearched(it.name) || !civInfo.tech.canBeResearched(it.name)} diff --git a/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt index 6670b7c69c..86ca9e5e40 100644 --- a/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/SpecificUnitAutomation.kt @@ -108,7 +108,7 @@ object SpecificUnitAutomation { .sortedBy { it.aerialDistanceTo(unit.currentTile) } .firstOrNull { reachableTest(it) } ?: return - if (!unit.hasCitadelPlacementUnique) { + if (!unit.cache.hasCitadelPlacementUnique) { unit.movement.headTowards(cityToGarrison) return } diff --git a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt index c585b65cfd..fd76cb5b6f 100644 --- a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt @@ -223,7 +223,7 @@ object UnitAutomation { if (unit.hasUnique(UniqueType.FoundCity)) return SpecificUnitAutomation.automateSettlerActions(unit) - if (unit.hasUniqueToBuildImprovements) + if (unit.cache.hasUniqueToBuildImprovements) return WorkerAutomation.automateWorkerAction(unit) if (unit.hasUnique(UniqueType.MayFoundReligion) @@ -248,12 +248,12 @@ object UnitAutomation { //todo this now supports "Great General"-like mod units not combining 'aura' and citadel // abilities, but not additional capabilities if automation finds no use for those two - if (unit.hasStrengthBonusInRadiusUnique + if (unit.cache.hasStrengthBonusInRadiusUnique && SpecificUnitAutomation.automateGreatGeneral(unit)) return - if (unit.hasCitadelPlacementUnique && SpecificUnitAutomation.automateCitadelPlacer(unit)) + if (unit.cache.hasCitadelPlacementUnique && SpecificUnitAutomation.automateCitadelPlacer(unit)) return - if (unit.hasCitadelPlacementUnique || unit.hasStrengthBonusInRadiusUnique) + if (unit.cache.hasCitadelPlacementUnique || unit.cache.hasStrengthBonusInRadiusUnique) return SpecificUnitAutomation.automateGreatGeneralFallback(unit) if (unit.civ.religionManager.maySpreadReligionAtAll(unit)) diff --git a/core/src/com/unciv/logic/battle/GreatGeneralImplementation.kt b/core/src/com/unciv/logic/battle/GreatGeneralImplementation.kt index 479aacb918..c966015c93 100644 --- a/core/src/com/unciv/logic/battle/GreatGeneralImplementation.kt +++ b/core/src/com/unciv/logic/battle/GreatGeneralImplementation.kt @@ -34,7 +34,7 @@ object GreatGeneralImplementation { val unit = ourUnitCombatant.unit val civInfo = ourUnitCombatant.unit.civ val allGenerals = civInfo.units.getCivUnits() - .filter { it.hasStrengthBonusInRadiusUnique } + .filter { it.cache.hasStrengthBonusInRadiusUnique } if (allGenerals.none()) return Pair("", 0) val greatGeneral = allGenerals diff --git a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt index b3d76bc5f3..8edd0ebe78 100644 --- a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt +++ b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt @@ -15,7 +15,6 @@ import com.unciv.logic.map.tile.Tile import com.unciv.models.UnitActionType import com.unciv.models.helpers.UnitMovementMemoryType import com.unciv.models.ruleset.Ruleset -import com.unciv.models.ruleset.tile.TerrainType import com.unciv.models.ruleset.tile.TileImprovement import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.Unique @@ -52,83 +51,15 @@ class MapUnit : IsPartOfGameInfoSerialization { @Transient var isDestroyed = false + @Transient + var cache = MapUnitCache(this) + // This is saved per each unit because if we need to recalculate viewable tiles every time a unit moves, // and we need to go over ALL the units, that's a lot of time spent on updating information we should already know! // About 10% of total NextTurn performance time, at the time of this change! @Transient var viewableTiles = HashSet() - // These are for performance improvements to getMovementCostBetweenAdjacentTiles, - // a major component of getDistanceToTilesWithinTurn, - // which in turn is a component of getShortestPath and canReach - @Transient - var ignoresTerrainCost = false - private set - - @Transient - var ignoresZoneOfControl = false - private set - - @Transient - var allTilesCosts1 = false - private set - - @Transient - var canPassThroughImpassableTiles = false - private set - - @Transient - var roughTerrainPenalty = false - private set - - /** If set causes an early exit in getMovementCostBetweenAdjacentTiles - * - means no double movement uniques, roughTerrainPenalty or ignoreHillMovementCost */ - @Transient - var noTerrainMovementUniques = false - private set - - /** If set causes a second early exit in getMovementCostBetweenAdjacentTiles */ - @Transient - var noBaseTerrainOrHillDoubleMovementUniques = false - private set - - /** If set skips tile.matchesFilter tests for double movement in getMovementCostBetweenAdjacentTiles */ - @Transient - var noFilteredDoubleMovementUniques = false - private set - - /** Used for getMovementCostBetweenAdjacentTiles only, based on order of testing */ - enum class DoubleMovementTerrainTarget { Feature, Base, Hill, Filter } - /** Mod-friendly cache of double-movement terrains */ - @Transient - val doubleMovementInTerrain = HashMap() - - @Transient - var canEnterIceTiles = false - - @Transient - var cannotEnterOceanTiles = false - - @Transient - var canEnterForeignTerrain: Boolean = false - - @Transient - var costToDisembark: Float? = null - - @Transient - var costToEmbark: Float? = null - - @Transient - var paradropRange = 0 - - @Transient - var hasUniqueToBuildImprovements = false // not canBuildImprovements to avoid confusion - - @Transient - var hasStrengthBonusInRadiusUnique = false - @Transient - var hasCitadelPlacementUnique = false - /** civName owning the unit */ lateinit var owner: String @@ -285,7 +216,7 @@ class MapUnit : IsPartOfGameInfoSerialization { return getMatchingUniques(uniqueType, stateForConditionals, checkCivInfoUniques).any() } - fun updateUniques(ruleset: Ruleset) { + fun updateUniques() { val uniques = ArrayList() val baseUnit = baseUnit() uniques.addAll(baseUnit.uniqueObjects) @@ -299,48 +230,7 @@ class MapUnit : IsPartOfGameInfoSerialization { addUniques(uniques) } - allTilesCosts1 = hasUnique(UniqueType.AllTilesCost1Move) - canPassThroughImpassableTiles = hasUnique(UniqueType.CanPassImpassable) - ignoresTerrainCost = hasUnique(UniqueType.IgnoresTerrainCost) - ignoresZoneOfControl = hasUnique(UniqueType.IgnoresZOC) - roughTerrainPenalty = hasUnique(UniqueType.RoughTerrainPenalty) - - doubleMovementInTerrain.clear() - for (unique in getMatchingUniques(UniqueType.DoubleMovementOnTerrain)) { - val param = unique.params[0] - val terrain = ruleset.terrains[param] - doubleMovementInTerrain[param] = when { - terrain == null -> DoubleMovementTerrainTarget.Filter - terrain.name == Constants.hill -> DoubleMovementTerrainTarget.Hill - terrain.type == TerrainType.TerrainFeature -> DoubleMovementTerrainTarget.Feature - terrain.type.isBaseTerrain -> DoubleMovementTerrainTarget.Base - else -> DoubleMovementTerrainTarget.Filter - } - } - // Init shortcut flags - noTerrainMovementUniques = doubleMovementInTerrain.isEmpty() && - !roughTerrainPenalty && !civ.nation.ignoreHillMovementCost - noBaseTerrainOrHillDoubleMovementUniques = doubleMovementInTerrain - .none { it.value != DoubleMovementTerrainTarget.Feature } - noFilteredDoubleMovementUniques = doubleMovementInTerrain - .none { it.value == DoubleMovementTerrainTarget.Filter } - costToDisembark = (getMatchingUniques(UniqueType.ReducedDisembarkCost, checkCivInfoUniques = true)) - .minOfOrNull { it.params[0].toFloat() } - costToEmbark = getMatchingUniques(UniqueType.ReducedEmbarkCost, checkCivInfoUniques = true) - .minOfOrNull { it.params[0].toFloat() } - - //todo: consider parameterizing [terrainFilter] in some of the following: - canEnterIceTiles = hasUnique(UniqueType.CanEnterIceTiles) - cannotEnterOceanTiles = hasUnique(UniqueType.CannotEnterOcean, StateForConditionals(civInfo=civ, unit=this)) - - hasUniqueToBuildImprovements = hasUnique(UniqueType.BuildImprovements) - canEnterForeignTerrain = hasUnique(UniqueType.CanEnterForeignTiles) - || hasUnique(UniqueType.CanEnterForeignTilesButLosesReligiousStrength) - - hasStrengthBonusInRadiusUnique = hasUnique(UniqueType.StrengthBonusInRadius) - hasCitadelPlacementUnique = getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit) - .mapNotNull { civ.gameInfo.ruleSet.tileImprovements[it.params[0]] } - .any { it.hasUnique(UniqueType.TakesOverAdjacentTiles) } + cache.updateUniques() } fun copyStatisticsTo(newUnit: MapUnit) { @@ -355,7 +245,7 @@ class MapUnit : IsPartOfGameInfoSerialization { newUnit.promotions = promotions.clone() - newUnit.updateUniques(civ.gameInfo.ruleSet) + newUnit.updateUniques() newUnit.updateVisibleTiles() } @@ -368,7 +258,6 @@ class MapUnit : IsPartOfGameInfoSerialization { movement += getMatchingUniques(UniqueType.Movement, checkCivInfoUniques = true) .sumOf { it.params[0].toInt() } - if (movement < 1) movement = 1 return movement @@ -533,7 +422,7 @@ class MapUnit : IsPartOfGameInfoSerialization { baseUnit = ruleset.units[name] ?: throw java.lang.Exception("Unit $name is not found!") - updateUniques(ruleset) + updateUniques() } fun useMovementPoints(amount: Float) { diff --git a/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt b/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt new file mode 100644 index 0000000000..edd4c7c674 --- /dev/null +++ b/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt @@ -0,0 +1,128 @@ +package com.unciv.logic.map.mapunit + +import com.unciv.Constants +import com.unciv.models.ruleset.tile.TerrainType +import com.unciv.models.ruleset.unique.StateForConditionals +import com.unciv.models.ruleset.unique.UniqueType + +class MapUnitCache(val mapUnit: MapUnit) { + // These are for performance improvements to getMovementCostBetweenAdjacentTiles, + // a major component of getDistanceToTilesWithinTurn, + // which in turn is a component of getShortestPath and canReach + @Transient + var ignoresTerrainCost = false + private set + + @Transient + var ignoresZoneOfControl = false + private set + + @Transient + var allTilesCosts1 = false + private set + + @Transient + var canPassThroughImpassableTiles = false + private set + + @Transient + var roughTerrainPenalty = false + private set + + /** If set causes an early exit in getMovementCostBetweenAdjacentTiles + * - means no double movement uniques, roughTerrainPenalty or ignoreHillMovementCost */ + @Transient + var noTerrainMovementUniques = false + private set + + /** If set causes a second early exit in getMovementCostBetweenAdjacentTiles */ + @Transient + var noBaseTerrainOrHillDoubleMovementUniques = false + private set + + /** If set skips tile.matchesFilter tests for double movement in getMovementCostBetweenAdjacentTiles */ + @Transient + var noFilteredDoubleMovementUniques = false + private set + + /** Used for getMovementCostBetweenAdjacentTiles only, based on order of testing */ + enum class DoubleMovementTerrainTarget { Feature, Base, Hill, Filter } + /** Mod-friendly cache of double-movement terrains */ + @Transient + val doubleMovementInTerrain = HashMap() + + @Transient + var canEnterIceTiles = false + + @Transient + var cannotEnterOceanTiles = false + + @Transient + var canEnterForeignTerrain: Boolean = false + + @Transient + var costToDisembark: Float? = null + + @Transient + var costToEmbark: Float? = null + + @Transient + var paradropRange = 0 + + @Transient + var hasUniqueToBuildImprovements = false // not canBuildImprovements to avoid confusion + + @Transient + var hasStrengthBonusInRadiusUnique = false + @Transient + var hasCitadelPlacementUnique = false + + fun updateUniques(){ + + allTilesCosts1 = mapUnit.hasUnique(UniqueType.AllTilesCost1Move) + canPassThroughImpassableTiles = mapUnit.hasUnique(UniqueType.CanPassImpassable) + ignoresTerrainCost = mapUnit.hasUnique(UniqueType.IgnoresTerrainCost) + ignoresZoneOfControl = mapUnit.hasUnique(UniqueType.IgnoresZOC) + roughTerrainPenalty = mapUnit.hasUnique(UniqueType.RoughTerrainPenalty) + + doubleMovementInTerrain.clear() + for (unique in mapUnit.getMatchingUniques(UniqueType.DoubleMovementOnTerrain)) { + val param = unique.params[0] + val terrain = mapUnit.currentTile.ruleset.terrains[param] + doubleMovementInTerrain[param] = when { + terrain == null -> DoubleMovementTerrainTarget.Filter + terrain.name == Constants.hill -> DoubleMovementTerrainTarget.Hill + terrain.type == TerrainType.TerrainFeature -> DoubleMovementTerrainTarget.Feature + terrain.type.isBaseTerrain -> DoubleMovementTerrainTarget.Base + else -> DoubleMovementTerrainTarget.Filter + } + } + // Init shortcut flags + noTerrainMovementUniques = doubleMovementInTerrain.isEmpty() && + !roughTerrainPenalty && !mapUnit.civ.nation.ignoreHillMovementCost + noBaseTerrainOrHillDoubleMovementUniques = doubleMovementInTerrain + .none { it.value != DoubleMovementTerrainTarget.Feature } + noFilteredDoubleMovementUniques = doubleMovementInTerrain + .none { it.value == DoubleMovementTerrainTarget.Filter } + costToDisembark = (mapUnit.getMatchingUniques(UniqueType.ReducedDisembarkCost, checkCivInfoUniques = true)) + .minOfOrNull { it.params[0].toFloat() } + costToEmbark = mapUnit.getMatchingUniques(UniqueType.ReducedEmbarkCost, checkCivInfoUniques = true) + .minOfOrNull { it.params[0].toFloat() } + + //todo: consider parameterizing [terrainFilter] in some of the following: + canEnterIceTiles = mapUnit.hasUnique(UniqueType.CanEnterIceTiles) + cannotEnterOceanTiles = mapUnit.hasUnique( + UniqueType.CannotEnterOcean, + StateForConditionals(civInfo = mapUnit.civ, unit = mapUnit) + ) + + hasUniqueToBuildImprovements = mapUnit.hasUnique(UniqueType.BuildImprovements) + canEnterForeignTerrain = mapUnit.hasUnique(UniqueType.CanEnterForeignTiles) + || mapUnit.hasUnique(UniqueType.CanEnterForeignTilesButLosesReligiousStrength) + + hasStrengthBonusInRadiusUnique = mapUnit.hasUnique(UniqueType.StrengthBonusInRadius) + hasCitadelPlacementUnique = mapUnit.getMatchingUniques(UniqueType.ConstructImprovementConsumingUnit) + .mapNotNull { mapUnit.civ.gameInfo.ruleSet.tileImprovements[it.params[0]] } + .any { it.hasUnique(UniqueType.TakesOverAdjacentTiles) } + } +} diff --git a/core/src/com/unciv/logic/map/mapunit/UnitMovementAlgorithms.kt b/core/src/com/unciv/logic/map/mapunit/UnitMovementAlgorithms.kt index 1235d29b8d..5fd84f2fcc 100644 --- a/core/src/com/unciv/logic/map/mapunit/UnitMovementAlgorithms.kt +++ b/core/src/com/unciv/logic/map/mapunit/UnitMovementAlgorithms.kt @@ -35,15 +35,15 @@ class UnitMovementAlgorithms(val unit: MapUnit) { ): Float { if (from.isLand != to.isLand && unit.baseUnit.isLandUnit()) - return if (from.isWater && to.isLand) unit.costToDisembark ?: 100f - else unit.costToEmbark ?: 100f + return if (from.isWater && to.isLand) unit.cache.costToDisembark ?: 100f + else unit.cache.costToEmbark ?: 100f // If the movement is affected by a Zone of Control, all movement points are expended if (considerZoneOfControl && isMovementAffectedByZoneOfControl(from, to, civInfo)) return 100f // land units will still spend all movement points to embark even with this unique - if (unit.allTilesCosts1) + if (unit.cache.allTilesCosts1) return 1f val toOwner = to.getOwner() @@ -65,36 +65,36 @@ class UnitMovementAlgorithms(val unit: MapUnit) { if (areConnectedByRoad && (!areConnectedByRiver || civInfo.tech.roadsConnectAcrossRivers)) return unit.civ.tech.movementSpeedOnRoads + extraCost - if (unit.ignoresTerrainCost) return 1f + extraCost + if (unit.cache.ignoresTerrainCost) return 1f + extraCost if (areConnectedByRiver) return 100f // Rivers take the entire turn to cross val terrainCost = to.getLastTerrain().movementCost.toFloat() - if (unit.noTerrainMovementUniques) + if (unit.cache.noTerrainMovementUniques) return terrainCost + extraCost - if (to.terrainFeatures.any { unit.doubleMovementInTerrain[it] == MapUnit.DoubleMovementTerrainTarget.Feature }) + if (to.terrainFeatures.any { unit.cache.doubleMovementInTerrain[it] == MapUnitCache.DoubleMovementTerrainTarget.Feature }) return terrainCost * 0.5f + extraCost - if (unit.roughTerrainPenalty && to.isRoughTerrain()) + if (unit.cache.roughTerrainPenalty && to.isRoughTerrain()) return 100f // units that have to spend all movement in rough terrain, have to spend all movement in rough terrain // Placement of this 'if' based on testing, see #4232 if (civInfo.nation.ignoreHillMovementCost && to.isHill()) return 1f + extraCost // usually hills take 2 movements, so here it is 1 - if (unit.noBaseTerrainOrHillDoubleMovementUniques) + if (unit.cache.noBaseTerrainOrHillDoubleMovementUniques) return terrainCost + extraCost - if (unit.doubleMovementInTerrain[to.baseTerrain] == MapUnit.DoubleMovementTerrainTarget.Base) + if (unit.cache.doubleMovementInTerrain[to.baseTerrain] == MapUnitCache.DoubleMovementTerrainTarget.Base) return terrainCost * 0.5f + extraCost - if (unit.doubleMovementInTerrain[Constants.hill] == MapUnit.DoubleMovementTerrainTarget.Hill && to.isHill()) + if (unit.cache.doubleMovementInTerrain[Constants.hill] == MapUnitCache.DoubleMovementTerrainTarget.Hill && to.isHill()) return terrainCost * 0.5f + extraCost - if (unit.noFilteredDoubleMovementUniques) + if (unit.cache.noFilteredDoubleMovementUniques) return terrainCost + extraCost - if (unit.doubleMovementInTerrain.any { - it.value == MapUnit.DoubleMovementTerrainTarget.Filter && + if (unit.cache.doubleMovementInTerrain.any { + it.value == MapUnitCache.DoubleMovementTerrainTarget.Filter && to.matchesFilter(it.key) }) return terrainCost * 0.5f + extraCost @@ -140,7 +140,7 @@ class UnitMovementAlgorithms(val unit: MapUnit) { // ignore zone of control, so the previous check has a much higher chance of yielding an // early "false". If this function is going to return "true", the order doesn't matter // anyway. - if (unit.ignoresZoneOfControl) + if (unit.cache.ignoresZoneOfControl) return false return true } @@ -355,7 +355,7 @@ class UnitMovementAlgorithms(val unit: MapUnit) { if (unit.baseUnit.movesLikeAirUnits()) return unit.currentTile.aerialDistanceTo(destination) <= unit.getMaxMovementForAirUnits() if (unit.isPreparingParadrop()) - return getDistance(unit.currentTile.position, destination.position) <= unit.paradropRange && canParadropOn(destination) + return getDistance(unit.currentTile.position, destination.position) <= unit.cache.paradropRange && canParadropOn(destination) return getDistanceToTiles().containsKey(destination) } @@ -365,7 +365,7 @@ class UnitMovementAlgorithms(val unit: MapUnit) { unit.baseUnit.movesLikeAirUnits() -> unit.getTile().getTilesInDistanceRange(IntRange(1, unit.getMaxMovementForAirUnits())) unit.isPreparingParadrop() -> - unit.getTile().getTilesInDistance(unit.paradropRange) + unit.getTile().getTilesInDistance(unit.cache.paradropRange) .filter { unit.movement.canParadropOn(it) } else -> unit.movement.getDistanceToTiles().keys.asSequence() @@ -589,7 +589,7 @@ class UnitMovementAlgorithms(val unit: MapUnit) { moveToTile(destination, considerZoneOfControl) } - unit.updateUniques(unit.currentTile.ruleset) + unit.updateUniques() } /** @@ -698,7 +698,7 @@ class UnitMovementAlgorithms(val unit: MapUnit) { if (tile.isImpassible()) { // special exception - ice tiles are technically impassible, but some units can move through them anyway // helicopters can pass through impassable tiles like mountains - if (!unit.canPassThroughImpassableTiles && !(unit.canEnterIceTiles && tile.terrainFeatures.contains(Constants.ice)) + if (!unit.cache.canPassThroughImpassableTiles && !(unit.cache.canEnterIceTiles && tile.terrainFeatures.contains(Constants.ice)) // carthage-like uniques sometimes allow passage through impassible tiles && !(unit.civ.passThroughImpassableUnlocked && unit.civ.passableImpassables.contains(tile.getLastTerrain().name))) return false @@ -719,10 +719,10 @@ class UnitMovementAlgorithms(val unit: MapUnit) { return false } if (tile.isOcean && !unit.civ.tech.allUnitsCanEnterOcean) { // Apparently all Polynesian naval units can enter oceans - if (!unitSpecificAllowOcean && unit.cannotEnterOceanTiles) return false + if (!unitSpecificAllowOcean && unit.cache.cannotEnterOceanTiles) return false } - if (!unit.canEnterForeignTerrain && !tile.canCivPassThrough(unit.civ)) return false + if (!unit.cache.canEnterForeignTerrain && !tile.canCivPassThrough(unit.civ)) return false // The first unit is: // 1. Either military unit @@ -802,11 +802,11 @@ class UnitMovementAlgorithms(val unit: MapUnit) { * however ignores the diplomatic aspects of such movement like crossing closed borders. */ private fun getPathBetweenTiles(from: Tile, to: Tile): MutableSet { - val tmp = unit.canEnterForeignTerrain - unit.canEnterForeignTerrain = true // the trick to ignore tiles owners + val tmp = unit.cache.canEnterForeignTerrain + unit.cache.canEnterForeignTerrain = true // the trick to ignore tiles owners val bfs = BFS(from) { canPassThrough(it) } bfs.stepUntilDestination(to) - unit.canEnterForeignTerrain = tmp + unit.cache.canEnterForeignTerrain = tmp return bfs.getReachedTiles() } diff --git a/core/src/com/unciv/logic/map/mapunit/UnitPromotions.kt b/core/src/com/unciv/logic/map/mapunit/UnitPromotions.kt index d73a011ba0..1d1994eb6e 100644 --- a/core/src/com/unciv/logic/map/mapunit/UnitPromotions.kt +++ b/core/src/com/unciv/logic/map/mapunit/UnitPromotions.kt @@ -75,7 +75,7 @@ class UnitPromotions : IsPartOfGameInfoSerialization { // so this has to go after the `promotions.add(promotionname)` line. doDirectPromotionEffects(promotion) - unit.updateUniques(ruleset) + unit.updateUniques() // Since some units get promotions upon construction, they will get the addPromotion from the unit.postBuildEvent // upon creation, BEFORE they are assigned to a tile, so the updateVisibleTiles() would crash. diff --git a/core/src/com/unciv/logic/map/mapunit/UnitTurnManager.kt b/core/src/com/unciv/logic/map/mapunit/UnitTurnManager.kt index 797bc7a118..79369fb593 100644 --- a/core/src/com/unciv/logic/map/mapunit/UnitTurnManager.kt +++ b/core/src/com/unciv/logic/map/mapunit/UnitTurnManager.kt @@ -160,7 +160,7 @@ class UnitTurnManager(val unit: MapUnit) { val tileOwner = unit.getTile().getOwner() if (tileOwner != null - && !unit.canEnterForeignTerrain + && !unit.cache.canEnterForeignTerrain && !unit.civ.diplomacyFunctions.canPassThroughTiles(tileOwner) && !tileOwner.isCityState()) // if an enemy city expanded onto this tile while I was in it unit.movement.teleportToClosestMoveableTile() diff --git a/core/src/com/unciv/logic/trade/TradeLogic.kt b/core/src/com/unciv/logic/trade/TradeLogic.kt index 5d04b0d189..5594e42be6 100644 --- a/core/src/com/unciv/logic/trade/TradeLogic.kt +++ b/core/src/com/unciv/logic/trade/TradeLogic.kt @@ -99,7 +99,7 @@ class TradeLogic(val ourCivilization:Civilization, val otherCivilization: Civili .forEach { it.movement.teleportToClosestMoveableTile() } for (tile in city.getTiles()) { for (unit in tile.getUnits().toList()) { - if (!unit.civ.diplomacyFunctions.canPassThroughTiles(to) && !unit.canEnterForeignTerrain) + if (!unit.civ.diplomacyFunctions.canPassThroughTiles(to) && !unit.cache.canEnterForeignTerrain) unit.movement.teleportToClosestMoveableTile() } } diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index dc41c5963a..03ec885f24 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -65,8 +65,8 @@ import com.unciv.ui.worldscreen.status.MultiplayerStatusButton import com.unciv.ui.worldscreen.status.NextTurnAction import com.unciv.ui.worldscreen.status.NextTurnButton import com.unciv.ui.worldscreen.status.StatusButtons -import com.unciv.ui.worldscreen.unit.actions.UnitActionsTable import com.unciv.ui.worldscreen.unit.UnitTable +import com.unciv.ui.worldscreen.unit.actions.UnitActionsTable import com.unciv.utils.concurrency.Concurrency import com.unciv.utils.concurrency.launchOnGLThread import com.unciv.utils.concurrency.launchOnThreadPool @@ -543,7 +543,7 @@ class WorldScreen( displayTutorial(TutorialTrigger.Workers) { gameInfo.getCurrentPlayerCivilization().units.getCivUnits().any { - it.hasUniqueToBuildImprovements && it.isCivilian() && !it.isGreatPerson() + it.cache.hasUniqueToBuildImprovements && it.isCivilian() && !it.isGreatPerson() } } } diff --git a/core/src/com/unciv/ui/worldscreen/unit/actions/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/actions/UnitActions.kt index 3222649c76..d66cbe2bf2 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/actions/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/actions/UnitActions.kt @@ -58,7 +58,7 @@ object UnitActions { addAirSweepAction(unit, actionList) addSetupAction(unit, actionList) addFoundCityAction(unit, actionList, tile) - addBuildingImprovementsAction(unit, actionList, tile, worldScreen, unitTable) + addBuildingImprovementsAction(unit, actionList, tile, worldScreen) addRepairAction(unit, actionList) addCreateWaterImprovements(unit, actionList) UnitActionsGreatPerson.addGreatPersonActions(unit, actionList, tile) @@ -255,7 +255,7 @@ object UnitActions { val paradropUniques = unit.getMatchingUniques(UniqueType.MayParadrop) if (!paradropUniques.any() || unit.isEmbarked()) return - unit.paradropRange = paradropUniques.maxOfOrNull { it.params[0] }!!.toInt() + unit.cache.paradropRange = paradropUniques.maxOfOrNull { it.params[0] }!!.toInt() actionList += UnitAction(UnitActionType.Paradrop, isCurrentAction = unit.isPreparingParadrop(), action = { @@ -356,8 +356,13 @@ object UnitActions { return transformList } - private fun addBuildingImprovementsAction(unit: MapUnit, actionList: ArrayList, tile: Tile, worldScreen: WorldScreen, unitTable: UnitTable) { - if (!unit.hasUniqueToBuildImprovements) return + private fun addBuildingImprovementsAction( + unit: MapUnit, + actionList: ArrayList, + tile: Tile, + worldScreen: WorldScreen + ) { + if (!unit.cache.hasUniqueToBuildImprovements) return if (unit.isEmbarked()) return val couldConstruct = unit.currentMovement > 0 @@ -390,7 +395,7 @@ object UnitActions { private fun addRepairAction(unit: MapUnit, actionList: ArrayList) { if (unit.currentTile.ruleset.tileImprovements[Constants.repair] == null) return - if (!unit.hasUniqueToBuildImprovements) return + if (!unit.cache.hasUniqueToBuildImprovements) return if (unit.isEmbarked()) return val tile = unit.getTile() if (tile.isCityCenter()) return @@ -416,7 +421,7 @@ object UnitActions { } private fun addAutomateBuildingImprovementsAction(unit: MapUnit, actionList: ArrayList) { - if (!unit.hasUniqueToBuildImprovements) return + if (!unit.cache.hasUniqueToBuildImprovements) return if (unit.isAutomated()) return actionList += UnitAction(UnitActionType.Automate, diff --git a/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt b/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt index 3cd390e74f..0fb403d702 100644 --- a/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt +++ b/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt @@ -119,7 +119,7 @@ class UnitMovementAlgorithmsTests { for (type in ruleSet.unitTypes) { unit.baseUnit = BaseUnit().apply { unitType = type.key; ruleset = ruleSet } - unit.updateUniques(ruleSet) + unit.updateUniques() Assert.assertTrue( "$type cannot be in Ice", @@ -191,7 +191,7 @@ class UnitMovementAlgorithmsTests { if (this.isRanged()) uniques.add("Cannot enter ocean tiles ") } - unit.updateUniques(ruleSet) + unit.updateUniques() Assert.assertTrue("$type cannot be in Ocean", (unit.baseUnit.isMelee()) != unit.movement.canPassThrough(tile)) @@ -496,7 +496,7 @@ class UnitMovementAlgorithmsTests { unit.owner = civInfo.civName unit.civ = civInfo unit.baseUnit.uniques.add("Can carry [2] [Aircraft] units") - unit.updateUniques(ruleSet) + unit.updateUniques() civInfo.units.addUnit(unit, false) val fighters = ArrayList()