Resolved #8563 - ultra-rare 'destroy civilian unit before attack' crash

This commit is contained in:
Yair Morgenstern 2023-02-06 00:23:05 +02:00
parent bf1850cf2e
commit 328cf56a22
5 changed files with 26 additions and 17 deletions

View File

@ -1,6 +1,8 @@
package com.unciv.logic.automation.unit
import com.unciv.logic.battle.ICombatant
import com.unciv.logic.map.tile.Tile
class AttackableTile(val tileToAttackFrom: Tile, val tileToAttack: Tile,
val movementLeftAfterMovingToAttackTile:Float)
val movementLeftAfterMovingToAttackTile:Float,
/** This is only for debug purposes */ val combatant:ICombatant)

View File

@ -73,18 +73,23 @@ object BattleHelper {
if (tile in tilesWithEnemies) attackableTiles += AttackableTile(
reachableTile,
tile,
movementLeft
movementLeft,
Battle.getMapCombatantOfTile(tile)!!
)
else if (tile in tilesWithoutEnemies) continue // avoid checking the same empty tile multiple times
else if (checkTile(unit, tile, tilesToCheck)) {
tilesWithEnemies += tile
attackableTiles += AttackableTile(reachableTile, tile, movementLeft)
} else if (unit.isPreparingAirSweep()){
attackableTiles += AttackableTile(
reachableTile, tile, movementLeft,
Battle.getMapCombatantOfTile(tile)!!
)
} else if (unit.isPreparingAirSweep()) {
tilesWithEnemies += tile
attackableTiles += AttackableTile(reachableTile, tile, movementLeft)
} else {
tilesWithoutEnemies += tile
}
attackableTiles += AttackableTile(
reachableTile, tile, movementLeft,
Battle.getMapCombatantOfTile(tile)!!
)
} else tilesWithoutEnemies += tile
}
}
return attackableTiles

View File

@ -62,12 +62,14 @@ object Battle {
*/
if (attacker.getTile() != attackableTile.tileToAttackFrom) return false
/** Rarely, a melee unit will target a civilian then move through the civilian to get
* to attackableTile.tileToAttackFrom, meaning that they take the civilian. This check stops
* the melee unit from trying to capture their own unit if this happens */
if (getMapCombatantOfTile(attackableTile.tileToAttack)!!.getCivInfo() == attacker.getCivInfo()) return false
* to attackableTile.tileToAttackFrom, meaning that they take the civilian.
* This can lead to:
* A. the melee unit from trying to capture their own unit (see #7282)
* B. The civilian unit disappearing entirely (e.g. Great Person) and trying to capture a non-existent unit (see #8563) */
val combatant = getMapCombatantOfTile(attackableTile.tileToAttack)
if (combatant == null || combatant.getCivInfo() == attacker.getCivInfo()) return false
/** Alternatively, maybe we DID reach that tile, but it turned out to be a hill or something,
* so we expended all of our movement points!
*/
* so we expended all of our movement points! */
if (attacker.hasUnique(UniqueType.MustSetUp)
&& !attacker.unit.isSetUpForSiege()
&& attacker.unit.currentMovement > 0f

View File

@ -341,9 +341,9 @@ class UncivFiles(
/** @throws IncompatibleGameInfoVersionException if the [gameData] was created by a version of this game that is incompatible with the current one. */
fun gameInfoFromString(gameData: String): GameInfo {
val unzippedJson = try {
Gzip.unzip(gameData)
Gzip.unzip(gameData.trim())
} catch (ex: Exception) {
gameData
gameData.trim()
}
val gameInfo = try {
json().fromJson(GameInfo::class.java, unzippedJson)

View File

@ -5,6 +5,7 @@ import com.badlogic.gdx.scenes.scene2d.Touchable
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.UncivGame
import com.unciv.logic.automation.unit.AttackableTile
import com.unciv.logic.automation.unit.BattleHelper
import com.unciv.logic.automation.unit.UnitAutomation
import com.unciv.logic.battle.Battle
@ -13,7 +14,6 @@ import com.unciv.logic.battle.CityCombatant
import com.unciv.logic.battle.ICombatant
import com.unciv.logic.battle.MapUnitCombatant
import com.unciv.logic.map.tile.Tile
import com.unciv.logic.automation.unit.AttackableTile
import com.unciv.models.UncivSound
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.translations.tr
@ -267,7 +267,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
} else if (attacker is CityCombatant) {
val canBombard = UnitAutomation.getBombardableTiles(attacker.city).contains(defender.getTile())
if (canBombard) {
attackableTile = AttackableTile(attacker.getTile(), defender.getTile(), 0f)
attackableTile = AttackableTile(attacker.getTile(), defender.getTile(), 0f, defender)
}
}
}