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) {
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

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.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<String, DoubleMovement>()
@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)) {

View File

@ -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<Pair<Tile, Tile>, 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<Tile> {
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<Tile> {
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

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
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