Fix and cache CannotMove unique (#9596)

This commit is contained in:
SomeTroglodyte 2023-06-16 09:37:26 +02:00 committed by GitHub
parent ea505e3e97
commit 7f626083ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 24 additions and 30 deletions

View File

@ -485,7 +485,7 @@ object Battle {
private fun postBattleMoveToAttackedTile(attacker: ICombatant, defender: ICombatant, attackedTile: Tile) { private fun postBattleMoveToAttackedTile(attacker: ICombatant, defender: ICombatant, attackedTile: Tile) {
if (!attacker.isMelee()) return if (!attacker.isMelee()) return
if (!defender.isDefeated() && defender.getCivInfo() != attacker.getCivInfo()) 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 // 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)) { 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 (attacker !is MapUnitCombatant) return false // allow simple access to unit property
if (defender !is MapUnitCombatant) return false if (defender !is MapUnitCombatant) return false
if (defender.unit.isEmbarked()) 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 // Promotions have no effect as per what I could find in available documentation
val attackBaseUnit = attacker.unit.baseUnit val attackBaseUnit = attacker.unit.baseUnit
val defendBaseUnit = defender.unit.baseUnit val defendBaseUnit = defender.unit.baseUnit

View File

@ -6,43 +6,40 @@ import com.unciv.models.ruleset.unique.StateForConditionals
import com.unciv.models.ruleset.unique.Unique import com.unciv.models.ruleset.unique.Unique
import com.unciv.models.ruleset.unique.UniqueType 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, // These are for performance improvements to getMovementCostBetweenAdjacentTiles,
// a major component of getDistanceToTilesWithinTurn, // a major component of getDistanceToTilesWithinTurn,
// which in turn is a component of getShortestPath and canReach // which in turn is a component of getShortestPath and canReach
@Transient
var ignoresTerrainCost = false var ignoresTerrainCost = false
private set private set
@Transient
var ignoresZoneOfControl = false var ignoresZoneOfControl = false
private set private set
@Transient
var allTilesCosts1 = false var allTilesCosts1 = false
private set private set
@Transient
var canPassThroughImpassableTiles = false var canPassThroughImpassableTiles = false
private set private set
@Transient
var roughTerrainPenalty = false var roughTerrainPenalty = false
private set private set
/** `true` if movement 0 _or_ has CannotMove unique */
var cannotMove = false
private set
/** If set causes an early exit in getMovementCostBetweenAdjacentTiles /** If set causes an early exit in getMovementCostBetweenAdjacentTiles
* - means no double movement uniques, roughTerrainPenalty or ignoreHillMovementCost */ * - means no double movement uniques, roughTerrainPenalty or ignoreHillMovementCost */
@Transient
var noTerrainMovementUniques = false var noTerrainMovementUniques = false
private set private set
/** If set causes a second early exit in getMovementCostBetweenAdjacentTiles */ /** If set causes a second early exit in getMovementCostBetweenAdjacentTiles */
@Transient
var noBaseTerrainOrHillDoubleMovementUniques = false var noBaseTerrainOrHillDoubleMovementUniques = false
private set private set
/** If set skips tile.matchesFilter tests for double movement in getMovementCostBetweenAdjacentTiles */ /** If set skips tile.matchesFilter tests for double movement in getMovementCostBetweenAdjacentTiles */
@Transient
var noFilteredDoubleMovementUniques = false var noFilteredDoubleMovementUniques = false
private set private set
@ -50,33 +47,24 @@ class MapUnitCache(val mapUnit: MapUnit) {
enum class DoubleMovementTerrainTarget { Feature, Base, Hill, Filter } enum class DoubleMovementTerrainTarget { Feature, Base, Hill, Filter }
class DoubleMovement(val terrainTarget: DoubleMovementTerrainTarget, val unique: Unique) class DoubleMovement(val terrainTarget: DoubleMovementTerrainTarget, val unique: Unique)
/** Mod-friendly cache of double-movement terrains */ /** Mod-friendly cache of double-movement terrains */
@Transient
val doubleMovementInTerrain = HashMap<String, DoubleMovement>() val doubleMovementInTerrain = HashMap<String, DoubleMovement>()
@Transient
var canEnterIceTiles = false var canEnterIceTiles = false
@Transient
var cannotEnterOceanTiles = false var cannotEnterOceanTiles = false
@Transient
var canEnterForeignTerrain: Boolean = false var canEnterForeignTerrain: Boolean = false
@Transient
var costToDisembark: Float? = null var costToDisembark: Float? = null
@Transient
var costToEmbark: Float? = null var costToEmbark: Float? = null
@Transient
var paradropRange = 0 var paradropRange = 0
@Transient
var hasUniqueToBuildImprovements = false // not canBuildImprovements to avoid confusion var hasUniqueToBuildImprovements = false // not canBuildImprovements to avoid confusion
@Transient
var hasStrengthBonusInRadiusUnique = false var hasStrengthBonusInRadiusUnique = false
@Transient
var hasCitadelPlacementUnique = false var hasCitadelPlacementUnique = false
fun updateUniques(){ fun updateUniques(){
@ -86,6 +74,7 @@ class MapUnitCache(val mapUnit: MapUnit) {
ignoresTerrainCost = mapUnit.hasUnique(UniqueType.IgnoresTerrainCost) ignoresTerrainCost = mapUnit.hasUnique(UniqueType.IgnoresTerrainCost)
ignoresZoneOfControl = mapUnit.hasUnique(UniqueType.IgnoresZOC) ignoresZoneOfControl = mapUnit.hasUnique(UniqueType.IgnoresZOC)
roughTerrainPenalty = mapUnit.hasUnique(UniqueType.RoughTerrainPenalty) roughTerrainPenalty = mapUnit.hasUnique(UniqueType.RoughTerrainPenalty)
cannotMove = mapUnit.hasUnique(UniqueType.CannotMove) || mapUnit.baseUnit.movement == 0
doubleMovementInTerrain.clear() doubleMovementInTerrain.clear()
for (unique in mapUnit.getMatchingUniques(UniqueType.DoubleMovementOnTerrain, stateForConditionals = StateForConditionals.IgnoreConditionals)) { for (unique in mapUnit.getMatchingUniques(UniqueType.DoubleMovementOnTerrain, stateForConditionals = StateForConditionals.IgnoreConditionals)) {

View File

@ -34,6 +34,7 @@ class UnitMovement(val unit: MapUnit) {
civInfo: Civilization, civInfo: Civilization,
considerZoneOfControl: Boolean = true considerZoneOfControl: Boolean = true
): Float { ): Float {
if (unit.cache.cannotMove) return 100f
if (from.isLand != to.isLand && unit.baseUnit.isLandUnit()) if (from.isLand != to.isLand && unit.baseUnit.isLandUnit())
return if (from.isWater && to.isLand) unit.cache.costToDisembark ?: 100f return if (from.isWater && to.isLand) unit.cache.costToDisembark ?: 100f
@ -187,12 +188,15 @@ class UnitMovement(val unit: MapUnit) {
movementCostCache: HashMap<Pair<Tile, Tile>, Float> = HashMap() movementCostCache: HashMap<Pair<Tile, Tile>, Float> = HashMap()
): PathsToTilesWithinTurn { ): PathsToTilesWithinTurn {
val distanceToTiles = PathsToTilesWithinTurn() val distanceToTiles = PathsToTilesWithinTurn()
if (unitMovement == 0f) return distanceToTiles
val currentUnitTile = unit.currentTile val currentUnitTile = unit.currentTile
// This is for performance, because this is called all the time // This is for performance, because this is called all the time
val unitTile = if (origin == currentUnitTile.position) currentUnitTile else currentUnitTile.tileMap[origin] val unitTile = if (origin == currentUnitTile.position) currentUnitTile else currentUnitTile.tileMap[origin]
distanceToTiles[unitTile] = ParentTileAndTotalDistance(unitTile, unitTile, 0f) 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) var tilesToCheck = listOf(unitTile)
while (tilesToCheck.isNotEmpty()) { 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. * Returns an empty list if there's no way to get to the destination.
*/ */
fun getShortestPath(destination: Tile, avoidDamagingTerrain: Boolean = false): List<Tile> { fun getShortestPath(destination: Tile, avoidDamagingTerrain: Boolean = false): List<Tile> {
if (unit.hasUnique(UniqueType.CannotMove)) return listOf() if (unit.cache.cannotMove) return listOf()
// First try and find a path without damaging terrain // First try and find a path without damaging terrain
if (!avoidDamagingTerrain && unit.civ.passThroughImpassableUnlocked && unit.baseUnit.isLandUnit()) { if (!avoidDamagingTerrain && unit.civ.passThroughImpassableUnlocked && unit.baseUnit.isLandUnit()) {
val damageFreePath = getShortestPath(destination, true) 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! /** 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 */ * Also note that REACHABLE tiles are not necessarily tiles that the unit CAN ENTER */
fun canReach(destination: Tile): Boolean { 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()) if (unit.baseUnit.movesLikeAirUnits() || unit.isPreparingParadrop())
return canReachInCurrentTurn(destination) return canReachInCurrentTurn(destination)
return getShortestPath(destination).any() return getShortestPath(destination).any()
} }
private fun canReachInCurrentTurn(destination: Tile): Boolean { 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()) if (unit.baseUnit.movesLikeAirUnits())
return unit.currentTile.aerialDistanceTo(destination) <= unit.getMaxMovementForAirUnits() return unit.currentTile.aerialDistanceTo(destination) <= unit.getMaxMovementForAirUnits()
if (unit.isPreparingParadrop()) if (unit.isPreparingParadrop())
@ -409,7 +414,7 @@ class UnitMovement(val unit: MapUnit) {
fun getReachableTilesInCurrentTurn(): Sequence<Tile> { fun getReachableTilesInCurrentTurn(): Sequence<Tile> {
return when { return when {
unit.hasUnique(UniqueType.CannotMove) -> emptySequence() unit.cache.cannotMove -> sequenceOf(unit.getTile())
unit.baseUnit.movesLikeAirUnits() -> unit.baseUnit.movesLikeAirUnits() ->
unit.getTile().getTilesInDistanceRange(IntRange(1, unit.getMaxMovementForAirUnits())) unit.getTile().getTilesInDistanceRange(IntRange(1, unit.getMaxMovementForAirUnits()))
unit.isPreparingParadrop() -> unit.isPreparingParadrop() ->
@ -439,7 +444,7 @@ class UnitMovement(val unit: MapUnit) {
if (unit.baseUnit.movesLikeAirUnits()) return false if (unit.baseUnit.movesLikeAirUnits()) return false
// We can't swap with ourself // We can't swap with ourself
if (reachableTile == unit.getTile()) return false 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 // 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. // also reach our tile in its current turn.
val otherUnit = ( val otherUnit = (
@ -450,7 +455,7 @@ class UnitMovement(val unit: MapUnit) {
) ?: return false ) ?: return false
val ourPosition = unit.getTile() val ourPosition = unit.getTile()
if (otherUnit.owner != unit.owner 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 || !otherUnit.movement.canReachInCurrentTurn(ourPosition)) return false
// Check if we could enter their tile if they wouldn't be there // Check if we could enter their tile if they wouldn't be there
otherUnit.removeFromTile() otherUnit.removeFromTile()
@ -728,7 +733,7 @@ class UnitMovement(val unit: MapUnit) {
// Can a paratrooper land at this tile? // Can a paratrooper land at this tile?
fun canParadropOn(destination: Tile): Boolean { 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 // Can only move to land tiles within range that are visible and not impassible
// Based on some testing done in the base game // Based on some testing done in the base game
if (!destination.isLand || destination.isImpassible() || !unit.civ.viewableTiles.contains(destination)) return false if (!destination.isLand || destination.isImpassible() || !unit.civ.viewableTiles.contains(destination)) return false

View File

@ -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 // 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())) val tilesInAttackRange = unit.getTile().getTilesInDistanceRange(IntRange(1, unit.getRange()))
for (tile in tilesInAttackRange) { for (tile in tilesInAttackRange) {
// The tile is within attack range // The tile is within attack range