diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index 69cda57cbd..f4134d1c30 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -485,7 +485,7 @@ object Battle { private fun postBattleMoveToAttackedTile(attacker: ICombatant, defender: ICombatant, attackedTile: Tile) { if (!attacker.isMelee()) return if (!defender.isDefeated() && defender.getCivInfo() != attacker.getCivInfo()) return - if (attacker is MapUnitCombatant && attacker.hasUnique(UniqueType.CannotMove)) return + if (attacker is MapUnitCombatant && attacker.unit.cache.cannotMove) return // This is so that if we attack e.g. a barbarian in enemy territory that we can't enter, we won't enter it if ((attacker as MapUnitCombatant).unit.movement.canMoveTo(attackedTile)) { @@ -1107,7 +1107,7 @@ object Battle { if (attacker !is MapUnitCombatant) return false // allow simple access to unit property if (defender !is MapUnitCombatant) return false if (defender.unit.isEmbarked()) return false - if (defender.hasUnique(UniqueType.CannotMove)) return false + if (defender.unit.cache.cannotMove) return false // Promotions have no effect as per what I could find in available documentation val attackBaseUnit = attacker.unit.baseUnit val defendBaseUnit = defender.unit.baseUnit diff --git a/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt b/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt index a82aa8af06..ea4c235f58 100644 --- a/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt +++ b/core/src/com/unciv/logic/map/mapunit/MapUnitCache.kt @@ -6,43 +6,40 @@ import com.unciv.models.ruleset.unique.StateForConditionals import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.UniqueType -class MapUnitCache(val mapUnit: MapUnit) { +// Note: Single use in MapUnit and it's @Transient there, so no need for that here +class MapUnitCache(private 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 + /** `true` if movement 0 _or_ has CannotMove unique */ + var cannotMove = 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 @@ -50,33 +47,24 @@ class MapUnitCache(val mapUnit: MapUnit) { enum class DoubleMovementTerrainTarget { Feature, Base, Hill, Filter } class DoubleMovement(val terrainTarget: DoubleMovementTerrainTarget, val unique: Unique) /** 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(){ @@ -86,6 +74,7 @@ class MapUnitCache(val mapUnit: MapUnit) { ignoresTerrainCost = mapUnit.hasUnique(UniqueType.IgnoresTerrainCost) ignoresZoneOfControl = mapUnit.hasUnique(UniqueType.IgnoresZOC) roughTerrainPenalty = mapUnit.hasUnique(UniqueType.RoughTerrainPenalty) + cannotMove = mapUnit.hasUnique(UniqueType.CannotMove) || mapUnit.baseUnit.movement == 0 doubleMovementInTerrain.clear() for (unique in mapUnit.getMatchingUniques(UniqueType.DoubleMovementOnTerrain, stateForConditionals = StateForConditionals.IgnoreConditionals)) { diff --git a/core/src/com/unciv/logic/map/mapunit/UnitMovement.kt b/core/src/com/unciv/logic/map/mapunit/UnitMovement.kt index 9cc589b76b..e158400f0b 100644 --- a/core/src/com/unciv/logic/map/mapunit/UnitMovement.kt +++ b/core/src/com/unciv/logic/map/mapunit/UnitMovement.kt @@ -34,6 +34,7 @@ class UnitMovement(val unit: MapUnit) { civInfo: Civilization, considerZoneOfControl: Boolean = true ): Float { + if (unit.cache.cannotMove) return 100f if (from.isLand != to.isLand && unit.baseUnit.isLandUnit()) return if (from.isWater && to.isLand) unit.cache.costToDisembark ?: 100f @@ -187,12 +188,15 @@ class UnitMovement(val unit: MapUnit) { movementCostCache: HashMap, Float> = HashMap() ): PathsToTilesWithinTurn { val distanceToTiles = PathsToTilesWithinTurn() - if (unitMovement == 0f) return distanceToTiles val currentUnitTile = unit.currentTile // This is for performance, because this is called all the time val unitTile = if (origin == currentUnitTile.position) currentUnitTile else currentUnitTile.tileMap[origin] distanceToTiles[unitTile] = ParentTileAndTotalDistance(unitTile, unitTile, 0f) + + // If I can't move my only option is to stay... + if (unitMovement == 0f || unit.cache.cannotMove) return distanceToTiles + var tilesToCheck = listOf(unitTile) while (tilesToCheck.isNotEmpty()) { @@ -240,7 +244,8 @@ class UnitMovement(val unit: MapUnit) { * Returns an empty list if there's no way to get to the destination. */ fun getShortestPath(destination: Tile, avoidDamagingTerrain: Boolean = false): List { - if (unit.hasUnique(UniqueType.CannotMove)) return listOf() + if (unit.cache.cannotMove) return listOf() + // First try and find a path without damaging terrain if (!avoidDamagingTerrain && unit.civ.passThroughImpassableUnlocked && unit.baseUnit.isLandUnit()) { val damageFreePath = getShortestPath(destination, true) @@ -392,14 +397,14 @@ class UnitMovement(val unit: MapUnit) { /** This is performance-heavy - use as last resort, only after checking everything else! * Also note that REACHABLE tiles are not necessarily tiles that the unit CAN ENTER */ fun canReach(destination: Tile): Boolean { - if (unit.hasUnique(UniqueType.CannotMove)) return false + if (unit.cache.cannotMove) return destination == unit.getTile() if (unit.baseUnit.movesLikeAirUnits() || unit.isPreparingParadrop()) return canReachInCurrentTurn(destination) return getShortestPath(destination).any() } private fun canReachInCurrentTurn(destination: Tile): Boolean { - if (unit.hasUnique(UniqueType.CannotMove)) return false + if (unit.cache.cannotMove) return destination == unit.getTile() if (unit.baseUnit.movesLikeAirUnits()) return unit.currentTile.aerialDistanceTo(destination) <= unit.getMaxMovementForAirUnits() if (unit.isPreparingParadrop()) @@ -409,7 +414,7 @@ class UnitMovement(val unit: MapUnit) { fun getReachableTilesInCurrentTurn(): Sequence { return when { - unit.hasUnique(UniqueType.CannotMove) -> emptySequence() + unit.cache.cannotMove -> sequenceOf(unit.getTile()) unit.baseUnit.movesLikeAirUnits() -> unit.getTile().getTilesInDistanceRange(IntRange(1, unit.getMaxMovementForAirUnits())) unit.isPreparingParadrop() -> @@ -439,7 +444,7 @@ class UnitMovement(val unit: MapUnit) { if (unit.baseUnit.movesLikeAirUnits()) return false // We can't swap with ourself if (reachableTile == unit.getTile()) return false - if (unit.hasUnique(UniqueType.CannotMove)) return false + if (unit.cache.cannotMove) return false // Check whether the tile contains a unit of the same type as us that we own and that can // also reach our tile in its current turn. val otherUnit = ( @@ -450,7 +455,7 @@ class UnitMovement(val unit: MapUnit) { ) ?: return false val ourPosition = unit.getTile() if (otherUnit.owner != unit.owner - || otherUnit.hasUnique(UniqueType.CannotMove) + || otherUnit.cache.cannotMove // redundant, line below would cover it too || !otherUnit.movement.canReachInCurrentTurn(ourPosition)) return false // Check if we could enter their tile if they wouldn't be there otherUnit.removeFromTile() @@ -728,7 +733,7 @@ class UnitMovement(val unit: MapUnit) { // Can a paratrooper land at this tile? fun canParadropOn(destination: Tile): Boolean { - if (unit.hasUnique(UniqueType.CannotMove)) return false + if (unit.cache.cannotMove) return false // Can only move to land tiles within range that are visible and not impassible // Based on some testing done in the base game if (!destination.isLand || destination.isImpassible() || !unit.civ.viewableTiles.contains(destination)) return false diff --git a/core/src/com/unciv/ui/screens/worldscreen/WorldMapHolder.kt b/core/src/com/unciv/ui/screens/worldscreen/WorldMapHolder.kt index f522ca0bd7..b18e647607 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/WorldMapHolder.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/WorldMapHolder.kt @@ -663,7 +663,7 @@ class WorldMapHolder( } // Add back in the red markers for Air Unit Attack range since they can't move, but can still attack - if (unit.hasUnique(UniqueType.CannotMove) && isAirUnit && !unit.isPreparingAirSweep()) { + if (unit.cache.cannotMove && isAirUnit && !unit.isPreparingAirSweep()) { val tilesInAttackRange = unit.getTile().getTilesInDistanceRange(IntRange(1, unit.getRange())) for (tile in tilesInAttackRange) { // The tile is within attack range