diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index 365872caaf..67400260d2 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -361,11 +361,6 @@ object Battle { if (!attacker.isMelee()) return if (!defender.isDefeated() && defender.getCivInfo() != attacker.getCivInfo()) return - // we destroyed an enemy military unit and there was a civilian unit in the same tile as well - // this has to be checked before canMoveTo, otherwise it will return false - if (attackedTile.civilianUnit != null && attackedTile.civilianUnit!!.civInfo != attacker.getCivInfo()) - captureCivilianUnit(attacker, MapUnitCombatant(attackedTile.civilianUnit!!)) - // 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)) { // Units that can move after attacking are not affected by zone of control if the @@ -504,7 +499,7 @@ object Battle { return null } - private fun captureCivilianUnit(attacker: ICombatant, defender: MapUnitCombatant, checkDefeat: Boolean = true) { + fun captureCivilianUnit(attacker: ICombatant, defender: MapUnitCombatant, checkDefeat: Boolean = true) { // need to save this because if the unit is captured its owner wil be overwritten val defenderCiv = defender.getCivInfo() diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index 44dcf8aa40..191bcde9e9 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -5,6 +5,8 @@ import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.automation.UnitAutomation import com.unciv.logic.automation.WorkerAutomation +import com.unciv.logic.battle.Battle +import com.unciv.logic.battle.MapUnitCombatant import com.unciv.logic.city.CityInfo import com.unciv.logic.city.RejectionReason import com.unciv.logic.civilization.CivilizationInfo @@ -876,6 +878,10 @@ class MapUnit { } if (tile.improvement == Constants.barbarianEncampment && !civInfo.isBarbarian()) clearEncampment(tile) + // Capture Enemy Civilian Unit if you move on top of it + if (tile.getUnguardedCivilian() != null && civInfo.isAtWarWith(tile.getUnguardedCivilian()!!.civInfo)) { + Battle.captureCivilianUnit(MapUnitCombatant(this), MapUnitCombatant(tile.civilianUnit!!)) + } val promotionUniques = tile.neighbors .flatMap { it.getAllTerrains() } diff --git a/core/src/com/unciv/logic/map/TileInfo.kt b/core/src/com/unciv/logic/map/TileInfo.kt index 9e6052d2b3..d60f09fa01 100644 --- a/core/src/com/unciv/logic/map/TileInfo.kt +++ b/core/src/com/unciv/logic/map/TileInfo.kt @@ -128,6 +128,14 @@ open class TileInfo { return null } + /** Return null if military/air units on tile, or no civilian */ + fun getUnguardedCivilian(): MapUnit? { + if (militaryUnit != null) return null + if (airUnits.isNotEmpty()) return null + if (civilianUnit != null) return civilianUnit!! + return null + } + fun getCity(): CityInfo? = owningCity fun getLastTerrain(): Terrain = when { diff --git a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt index 93f5e3f71a..895894c12b 100644 --- a/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt +++ b/core/src/com/unciv/logic/map/UnitMovementAlgorithms.kt @@ -553,7 +553,8 @@ class UnitMovementAlgorithms(val unit:MapUnit) { return if (unit.isCivilian()) tile.civilianUnit == null && (tile.militaryUnit == null || tile.militaryUnit!!.owner == unit.owner) else - tile.militaryUnit == null && (tile.civilianUnit == null || tile.civilianUnit!!.owner == unit.owner) + // can skip checking for airUnit since not a city + tile.militaryUnit == null && (tile.civilianUnit == null || tile.civilianUnit!!.owner == unit.owner || unit.civInfo.isAtWarWith(tile.civilianUnit!!.civInfo)) } private fun canAirUnitMoveTo(tile: TileInfo, unit: MapUnit): Boolean { @@ -618,8 +619,14 @@ class UnitMovementAlgorithms(val unit:MapUnit) { if (!unit.canEnterForeignTerrain && !tile.canCivPassThrough(unit.civInfo)) return false val firstUnit = tile.getFirstUnit() - if (firstUnit != null && firstUnit.civInfo != unit.civInfo && unit.civInfo.isAtWarWith(firstUnit.civInfo)) - return false + if (firstUnit != null) { + // Allow movement through unguarded, at-war Civilian Unit. Capture on the way + if (tile.getUnguardedCivilian() != null && unit.civInfo != firstUnit.civInfo && unit.civInfo.isAtWarWith(tile.civilianUnit!!.civInfo)) + return true + // Cannot enter hostile tile with any unit in there + if (firstUnit.civInfo != unit.civInfo && unit.civInfo.isAtWarWith(firstUnit.civInfo)) + return false + } return true } diff --git a/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt b/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt index 562ccf5f6d..9922ceb916 100644 --- a/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt +++ b/tests/src/com/unciv/logic/map/UnitMovementAlgorithmsTests.kt @@ -215,6 +215,18 @@ class UnitMovementAlgorithmsTests { otherCiv.nation = Nation().apply { name = Constants.barbarians } val otherUnit = MapUnit() otherUnit.civInfo = otherCiv + otherUnit.baseUnit = BaseUnit() + // melee check + otherUnit.baseUnit.strength = 1 + tile.militaryUnit = otherUnit + + for (type in ruleSet.unitTypes) { + unit.baseUnit = BaseUnit().apply { unitType = type.key; ruleset = ruleSet } + + Assert.assertFalse("$type must not enter occupied tile", unit.movement.canPassThrough(tile)) + } + // ranged check + otherUnit.baseUnit.rangedStrength = 1 // make non-Civilian ranged tile.militaryUnit = otherUnit for (type in ruleSet.unitTypes) {