mirror of
https://github.com/yairm210/Unciv.git
synced 2025-01-10 23:37:31 +07:00
chore: Separated capturing logic out from Battle
This commit is contained in:
parent
e3a5972648
commit
510fd7927a
@ -11,10 +11,8 @@ import com.unciv.logic.civilization.LocationAction
|
|||||||
import com.unciv.logic.civilization.MapUnitAction
|
import com.unciv.logic.civilization.MapUnitAction
|
||||||
import com.unciv.logic.civilization.NotificationCategory
|
import com.unciv.logic.civilization.NotificationCategory
|
||||||
import com.unciv.logic.civilization.NotificationIcon
|
import com.unciv.logic.civilization.NotificationIcon
|
||||||
import com.unciv.logic.civilization.PlayerType
|
|
||||||
import com.unciv.logic.civilization.PopupAlert
|
import com.unciv.logic.civilization.PopupAlert
|
||||||
import com.unciv.logic.civilization.PromoteUnitAction
|
import com.unciv.logic.civilization.PromoteUnitAction
|
||||||
import com.unciv.logic.map.mapunit.MapUnit
|
|
||||||
import com.unciv.logic.map.tile.Tile
|
import com.unciv.logic.map.tile.Tile
|
||||||
import com.unciv.models.UnitActionType
|
import com.unciv.models.UnitActionType
|
||||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||||
@ -36,7 +34,7 @@ object Battle {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves [attacker] to [attackableTile], handles siege setup then attacks if still possible
|
* Moves [attacker] to [attackableTile], handles siege setup then attacks if still possible
|
||||||
* (by calling [attack] or [NUKE]). Does _not_ play the attack sound!
|
* (by calling [attack] or [Nuke.NUKE]). Does _not_ play the attack sound!
|
||||||
*
|
*
|
||||||
* Currently not used by UI, only by automation via [BattleHelper.tryAttackNearbyEnemy][com.unciv.logic.automation.unit.BattleHelper.tryAttackNearbyEnemy]
|
* Currently not used by UI, only by automation via [BattleHelper.tryAttackNearbyEnemy][com.unciv.logic.automation.unit.BattleHelper.tryAttackNearbyEnemy]
|
||||||
*/
|
*/
|
||||||
@ -126,7 +124,7 @@ object Battle {
|
|||||||
|
|
||||||
// check if unit is captured by the attacker (prize ships unique)
|
// check if unit is captured by the attacker (prize ships unique)
|
||||||
// As ravignir clarified in issue #4374, this only works for aggressor
|
// As ravignir clarified in issue #4374, this only works for aggressor
|
||||||
val captureMilitaryUnitSuccess = tryCaptureUnit(attacker, defender, attackedTile)
|
val captureMilitaryUnitSuccess = BattleUnitCapture.tryCaptureMilitaryUnit(attacker, defender, attackedTile)
|
||||||
|
|
||||||
if (!captureMilitaryUnitSuccess) // capture creates a new unit, but `defender` still is the original, so this function would still show a kill message
|
if (!captureMilitaryUnitSuccess) // capture creates a new unit, but `defender` still is the original, so this function would still show a kill message
|
||||||
postBattleNotifications(attacker, defender, attackedTile, attacker.getTile(), damageDealt)
|
postBattleNotifications(attacker, defender, attackedTile, attacker.getTile(), damageDealt)
|
||||||
@ -203,7 +201,7 @@ object Battle {
|
|||||||
return damageDealt + interceptDamage
|
return damageDealt + interceptDamage
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun triggerDefeatUniques(ourUnit: MapUnitCombatant, enemy: ICombatant, attackedTile: Tile){
|
internal fun triggerDefeatUniques(ourUnit: MapUnitCombatant, enemy: ICombatant, attackedTile: Tile){
|
||||||
val stateForConditionals = StateForConditionals(civInfo = ourUnit.getCivInfo(),
|
val stateForConditionals = StateForConditionals(civInfo = ourUnit.getCivInfo(),
|
||||||
ourCombatant = ourUnit, theirCombatant=enemy, tile = attackedTile)
|
ourCombatant = ourUnit, theirCombatant=enemy, tile = attackedTile)
|
||||||
for (unique in ourUnit.unit.getTriggeredUniques(UniqueType.TriggerUponDefeat, stateForConditionals))
|
for (unique in ourUnit.unit.getTriggeredUniques(UniqueType.TriggerUponDefeat, stateForConditionals))
|
||||||
@ -259,90 +257,6 @@ object Battle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryCaptureUnit(attacker: ICombatant, defender: ICombatant, attackedTile: Tile): Boolean {
|
|
||||||
// https://forums.civfanatics.com/threads/prize-ships-for-land-units.650196/
|
|
||||||
// https://civilization.fandom.com/wiki/Module:Data/Civ5/GK/Defines\
|
|
||||||
// There are 3 ways of capturing a unit, we separate them for cleaner code but we also need to ensure a unit isn't captured twice
|
|
||||||
|
|
||||||
if (defender !is MapUnitCombatant || attacker !is MapUnitCombatant) return false
|
|
||||||
|
|
||||||
if (!defender.isDefeated() || defender.unit.isCivilian()) return false
|
|
||||||
|
|
||||||
fun unitCapturedPrizeShipsUnique(): Boolean {
|
|
||||||
if (attacker.unit.getMatchingUniques(UniqueType.KillUnitCapture)
|
|
||||||
.none { defender.matchesCategory(it.params[0]) }
|
|
||||||
) return false
|
|
||||||
|
|
||||||
val captureChance = min(
|
|
||||||
0.8f,
|
|
||||||
0.1f + attacker.getAttackingStrength().toFloat() / defender.getDefendingStrength()
|
|
||||||
.toFloat() * 0.4f
|
|
||||||
)
|
|
||||||
/** Between 0 and 1. Defaults to turn and location-based random to avoid save scumming */
|
|
||||||
val random = Random((attacker.getCivInfo().gameInfo.turns * defender.getTile().position.hashCode()).toLong())
|
|
||||||
return random.nextFloat() <= captureChance
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unitGainFromEncampment(): Boolean {
|
|
||||||
if (!defender.getCivInfo().isBarbarian()) return false
|
|
||||||
if (attackedTile.improvement != Constants.barbarianEncampment) return false
|
|
||||||
|
|
||||||
var unitCaptured = false
|
|
||||||
// German unique - needs to be checked before we try to move to the enemy tile, since the encampment disappears after we move in
|
|
||||||
|
|
||||||
for (unique in attacker.getCivInfo()
|
|
||||||
.getMatchingUniques(UniqueType.GainFromEncampment)) {
|
|
||||||
attacker.getCivInfo().addGold(unique.params[0].toInt())
|
|
||||||
unitCaptured = true
|
|
||||||
}
|
|
||||||
return unitCaptured
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun unitGainFromDefeatingUnit(): Boolean {
|
|
||||||
if (!attacker.isMelee()) return false
|
|
||||||
var unitCaptured = false
|
|
||||||
val state = StateForConditionals(attacker.getCivInfo(), ourCombatant = attacker, theirCombatant = defender)
|
|
||||||
for (unique in attacker.getMatchingUniques(UniqueType.GainFromDefeatingUnit, state, true)) {
|
|
||||||
if (defender.unit.matchesFilter(unique.params[0])) {
|
|
||||||
attacker.getCivInfo().addGold(unique.params[1].toInt())
|
|
||||||
unitCaptured = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return unitCaptured
|
|
||||||
}
|
|
||||||
|
|
||||||
// Due to the way OR operators short-circuit, calling just A() || B() means B isn't called if A is true.
|
|
||||||
// Therefore we run all functions before checking if one is true.
|
|
||||||
val wasUnitCaptured = listOf(
|
|
||||||
unitCapturedPrizeShipsUnique(),
|
|
||||||
unitGainFromEncampment(),
|
|
||||||
unitGainFromDefeatingUnit()
|
|
||||||
).any { it }
|
|
||||||
|
|
||||||
if (!wasUnitCaptured) return false
|
|
||||||
|
|
||||||
// This is called after takeDamage and so the defeated defender is already destroyed and
|
|
||||||
// thus removed from the tile - but MapUnit.destroy() will not clear the unit's currentTile.
|
|
||||||
// Therefore placeUnitNearTile _will_ place the new unit exactly where the defender was
|
|
||||||
return spawnCapturedUnit(defender.getName(), attacker, defender.getTile())
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Places a [unitName] unit near [tile] after being attacked by [attacker].
|
|
||||||
* Adds a notification to [attacker]'s civInfo and returns whether the captured unit could be placed */
|
|
||||||
private fun spawnCapturedUnit(unitName: String, attacker: ICombatant, tile: Tile): Boolean {
|
|
||||||
val addedUnit = attacker.getCivInfo().units.placeUnitNearTile(tile.position, unitName) ?: return false
|
|
||||||
addedUnit.currentMovement = 0f
|
|
||||||
addedUnit.health = 50
|
|
||||||
attacker.getCivInfo().addNotification("An enemy [${unitName}] has joined us!", addedUnit.getTile().position, NotificationCategory.War, unitName)
|
|
||||||
|
|
||||||
val civilianUnit = tile.civilianUnit
|
|
||||||
// placeUnitNearTile might not have spawned the unit in exactly this tile, in which case no capture would have happened on this tile. So we need to do that here.
|
|
||||||
if (addedUnit.getTile() != tile && civilianUnit != null) {
|
|
||||||
captureCivilianUnit(attacker, MapUnitCombatant(civilianUnit))
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Holder for battle result - actual damage.
|
/** Holder for battle result - actual damage.
|
||||||
* @param attackerDealt Damage done by attacker to defender
|
* @param attackerDealt Damage done by attacker to defender
|
||||||
@ -364,7 +278,7 @@ object Battle {
|
|||||||
val defenderHealthBefore = defender.getHealth()
|
val defenderHealthBefore = defender.getHealth()
|
||||||
|
|
||||||
if (defender is MapUnitCombatant && defender.unit.isCivilian() && attacker.isMelee()) {
|
if (defender is MapUnitCombatant && defender.unit.isCivilian() && attacker.isMelee()) {
|
||||||
captureCivilianUnit(attacker, defender)
|
BattleUnitCapture.captureCivilianUnit(attacker, defender)
|
||||||
} else if (attacker.isRanged() && !attacker.isAirUnit()) { // Air Units are Ranged, but take damage as well
|
} else if (attacker.isRanged() && !attacker.isAirUnit()) { // Air Units are Ranged, but take damage as well
|
||||||
defender.takeDamage(potentialDamageToDefender) // straight up
|
defender.takeDamage(potentialDamageToDefender) // straight up
|
||||||
} else {
|
} else {
|
||||||
@ -593,7 +507,7 @@ object Battle {
|
|||||||
city.hasJustBeenConquered = true
|
city.hasJustBeenConquered = true
|
||||||
city.getCenterTile().apply {
|
city.getCenterTile().apply {
|
||||||
if (militaryUnit != null) militaryUnit!!.destroy()
|
if (militaryUnit != null) militaryUnit!!.destroy()
|
||||||
if (civilianUnit != null) captureCivilianUnit(attacker, MapUnitCombatant(civilianUnit!!), checkDefeat = false)
|
if (civilianUnit != null) BattleUnitCapture.captureCivilianUnit(attacker, MapUnitCombatant(civilianUnit!!), checkDefeat = false)
|
||||||
for (airUnit in airUnits.toList()) airUnit.destroy()
|
for (airUnit in airUnits.toList()) airUnit.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -660,103 +574,6 @@ object Battle {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws IllegalArgumentException if the [attacker] and [defender] belong to the same civ.
|
|
||||||
*/
|
|
||||||
fun captureCivilianUnit(attacker: ICombatant, defender: MapUnitCombatant, checkDefeat: Boolean = true) {
|
|
||||||
require(attacker.getCivInfo() != defender.getCivInfo()) {
|
|
||||||
"Can't capture our own unit!"
|
|
||||||
}
|
|
||||||
|
|
||||||
// need to save this because if the unit is captured its owner wil be overwritten
|
|
||||||
val defenderCiv = defender.getCivInfo()
|
|
||||||
|
|
||||||
val capturedUnit = defender.unit
|
|
||||||
// Stop current action
|
|
||||||
capturedUnit.action = null
|
|
||||||
|
|
||||||
val capturedUnitTile = capturedUnit.getTile()
|
|
||||||
val originalOwner = if (capturedUnit.originalOwner != null)
|
|
||||||
capturedUnit.civ.gameInfo.getCivilization(capturedUnit.originalOwner!!)
|
|
||||||
else null
|
|
||||||
|
|
||||||
var wasDestroyedInstead = false
|
|
||||||
when {
|
|
||||||
// Uncapturable units are destroyed
|
|
||||||
defender.unit.hasUnique(UniqueType.Uncapturable) -> {
|
|
||||||
capturedUnit.destroy()
|
|
||||||
wasDestroyedInstead = true
|
|
||||||
}
|
|
||||||
// City states can never capture settlers at all
|
|
||||||
capturedUnit.hasUnique(UniqueType.FoundCity) && attacker.getCivInfo().isCityState() -> {
|
|
||||||
capturedUnit.destroy()
|
|
||||||
wasDestroyedInstead = true
|
|
||||||
}
|
|
||||||
// Is it our old unit?
|
|
||||||
attacker.getCivInfo() == originalOwner -> {
|
|
||||||
// Then it is recaptured without converting settlers to workers
|
|
||||||
capturedUnit.capturedBy(attacker.getCivInfo())
|
|
||||||
}
|
|
||||||
// Return captured civilian to its original owner?
|
|
||||||
defender.getCivInfo().isBarbarian()
|
|
||||||
&& originalOwner != null
|
|
||||||
&& !originalOwner.isBarbarian()
|
|
||||||
&& attacker.getCivInfo() != originalOwner
|
|
||||||
&& attacker.getCivInfo().knows(originalOwner)
|
|
||||||
&& originalOwner.isAlive()
|
|
||||||
&& !attacker.getCivInfo().isAtWarWith(originalOwner)
|
|
||||||
&& attacker.getCivInfo().playerType == PlayerType.Human // Only humans get the choice
|
|
||||||
-> {
|
|
||||||
capturedUnit.capturedBy(attacker.getCivInfo())
|
|
||||||
attacker.getCivInfo().popupAlerts.add(
|
|
||||||
PopupAlert(
|
|
||||||
AlertType.RecapturedCivilian,
|
|
||||||
capturedUnitTile.position.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> captureOrConvertToWorker(capturedUnit, attacker.getCivInfo())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!wasDestroyedInstead)
|
|
||||||
defenderCiv.addNotification(
|
|
||||||
"An enemy [${attacker.getName()}] has captured our [${defender.getName()}]",
|
|
||||||
defender.getTile().position, NotificationCategory.War, attacker.getName(),
|
|
||||||
NotificationIcon.War, defender.getName()
|
|
||||||
)
|
|
||||||
else {
|
|
||||||
defenderCiv.addNotification(
|
|
||||||
"An enemy [${attacker.getName()}] has destroyed our [${defender.getName()}]",
|
|
||||||
defender.getTile().position, NotificationCategory.War, attacker.getName(),
|
|
||||||
NotificationIcon.War, defender.getName()
|
|
||||||
)
|
|
||||||
triggerDefeatUniques(defender, attacker, capturedUnitTile)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkDefeat)
|
|
||||||
destroyIfDefeated(defenderCiv, attacker.getCivInfo())
|
|
||||||
capturedUnit.updateVisibleTiles()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun captureOrConvertToWorker(capturedUnit: MapUnit, capturingCiv: Civilization){
|
|
||||||
// Captured settlers are converted to workers unless captured by barbarians (so they can be returned later).
|
|
||||||
if (capturedUnit.hasUnique(UniqueType.FoundCity) && !capturingCiv.isBarbarian()) {
|
|
||||||
capturedUnit.destroy()
|
|
||||||
// This is so that future checks which check if a unit has been captured are caught give the right answer
|
|
||||||
// For example, in postBattleMoveToAttackedTile
|
|
||||||
capturedUnit.civ = capturingCiv
|
|
||||||
|
|
||||||
val workerTypeUnit = capturingCiv.gameInfo.ruleset.units.values
|
|
||||||
.firstOrNull { it.isCivilian() && it.getMatchingUniques(UniqueType.BuildImprovements)
|
|
||||||
.any { unique -> unique.params[0] == "Land" } }
|
|
||||||
|
|
||||||
if (workerTypeUnit != null)
|
|
||||||
capturingCiv.units.placeUnitNearTile(capturedUnit.currentTile.position, workerTypeUnit)
|
|
||||||
}
|
|
||||||
else capturedUnit.capturedBy(capturingCiv)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun destroyIfDefeated(attackedCiv: Civilization, attacker: Civilization) {
|
fun destroyIfDefeated(attackedCiv: Civilization, attacker: Civilization) {
|
||||||
if (attackedCiv.isDefeated()) {
|
if (attackedCiv.isDefeated()) {
|
||||||
if (attackedCiv.isCityState())
|
if (attackedCiv.isCityState())
|
||||||
|
204
core/src/com/unciv/logic/battle/BattleUnitCapture.kt
Normal file
204
core/src/com/unciv/logic/battle/BattleUnitCapture.kt
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
package com.unciv.logic.battle
|
||||||
|
|
||||||
|
import com.unciv.Constants
|
||||||
|
import com.unciv.logic.civilization.AlertType
|
||||||
|
import com.unciv.logic.civilization.Civilization
|
||||||
|
import com.unciv.logic.civilization.NotificationCategory
|
||||||
|
import com.unciv.logic.civilization.NotificationIcon
|
||||||
|
import com.unciv.logic.civilization.PlayerType
|
||||||
|
import com.unciv.logic.civilization.PopupAlert
|
||||||
|
import com.unciv.logic.map.mapunit.MapUnit
|
||||||
|
import com.unciv.logic.map.tile.Tile
|
||||||
|
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||||
|
import com.unciv.models.ruleset.unique.UniqueType
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
object BattleUnitCapture {
|
||||||
|
|
||||||
|
fun tryCaptureMilitaryUnit(attacker: ICombatant, defender: ICombatant, attackedTile: Tile): Boolean {
|
||||||
|
// https://forums.civfanatics.com/threads/prize-ships-for-land-units.650196/
|
||||||
|
// https://civilization.fandom.com/wiki/Module:Data/Civ5/GK/Defines\
|
||||||
|
// There are 3 ways of capturing a unit, we separate them for cleaner code but we also need to ensure a unit isn't captured twice
|
||||||
|
|
||||||
|
if (defender !is MapUnitCombatant || attacker !is MapUnitCombatant) return false
|
||||||
|
|
||||||
|
if (!defender.isDefeated() || defender.unit.isCivilian()) return false
|
||||||
|
|
||||||
|
// Due to the way OR operators short-circuit, calling just A() || B() means B isn't called if A is true.
|
||||||
|
// Therefore we run all functions before checking if one is true.
|
||||||
|
val wasUnitCaptured = listOf(
|
||||||
|
unitCapturedPrizeShipsUnique(attacker, defender),
|
||||||
|
unitCapturedFromEncampment(attacker, defender, attackedTile),
|
||||||
|
unitGainFromDefeatingUnit(attacker, defender)
|
||||||
|
).any { it }
|
||||||
|
|
||||||
|
if (!wasUnitCaptured) return false
|
||||||
|
|
||||||
|
// This is called after takeDamage and so the defeated defender is already destroyed and
|
||||||
|
// thus removed from the tile - but MapUnit.destroy() will not clear the unit's currentTile.
|
||||||
|
// Therefore placeUnitNearTile _will_ place the new unit exactly where the defender was
|
||||||
|
return spawnCapturedUnit(defender.getName(), attacker, defender.getTile())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private fun unitCapturedPrizeShipsUnique(attacker: MapUnitCombatant, defender: MapUnitCombatant): Boolean {
|
||||||
|
if (attacker.unit.getMatchingUniques(UniqueType.KillUnitCapture)
|
||||||
|
.none { defender.matchesCategory(it.params[0]) }
|
||||||
|
) return false
|
||||||
|
|
||||||
|
val captureChance = min(
|
||||||
|
0.8f,
|
||||||
|
0.1f + attacker.getAttackingStrength().toFloat() / defender.getDefendingStrength()
|
||||||
|
.toFloat() * 0.4f
|
||||||
|
)
|
||||||
|
/** Between 0 and 1. Defaults to turn and location-based random to avoid save scumming */
|
||||||
|
val random = Random((attacker.getCivInfo().gameInfo.turns * defender.getTile().position.hashCode()).toLong())
|
||||||
|
return random.nextFloat() <= captureChance
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun unitGainFromDefeatingUnit(attacker: MapUnitCombatant, defender: MapUnitCombatant): Boolean {
|
||||||
|
if (!attacker.isMelee()) return false
|
||||||
|
var unitCaptured = false
|
||||||
|
val state = StateForConditionals(attacker.getCivInfo(), ourCombatant = attacker, theirCombatant = defender)
|
||||||
|
for (unique in attacker.getMatchingUniques(UniqueType.GainFromDefeatingUnit, state, true)) {
|
||||||
|
if (defender.unit.matchesFilter(unique.params[0])) {
|
||||||
|
attacker.getCivInfo().addGold(unique.params[1].toInt())
|
||||||
|
unitCaptured = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unitCaptured
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unitCapturedFromEncampment(attacker: MapUnitCombatant, defender: MapUnitCombatant, attackedTile: Tile): Boolean {
|
||||||
|
if (!defender.getCivInfo().isBarbarian()) return false
|
||||||
|
if (attackedTile.improvement != Constants.barbarianEncampment) return false
|
||||||
|
|
||||||
|
var unitCaptured = false
|
||||||
|
// German unique - needs to be checked before we try to move to the enemy tile, since the encampment disappears after we move in
|
||||||
|
|
||||||
|
for (unique in attacker.getCivInfo()
|
||||||
|
.getMatchingUniques(UniqueType.GainFromEncampment)) {
|
||||||
|
attacker.getCivInfo().addGold(unique.params[0].toInt())
|
||||||
|
unitCaptured = true
|
||||||
|
}
|
||||||
|
return unitCaptured
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Places a [unitName] unit near [tile] after being attacked by [attacker].
|
||||||
|
* Adds a notification to [attacker]'s civInfo and returns whether the captured unit could be placed */
|
||||||
|
private fun spawnCapturedUnit(unitName: String, attacker: ICombatant, tile: Tile): Boolean {
|
||||||
|
val addedUnit = attacker.getCivInfo().units.placeUnitNearTile(tile.position, unitName) ?: return false
|
||||||
|
addedUnit.currentMovement = 0f
|
||||||
|
addedUnit.health = 50
|
||||||
|
attacker.getCivInfo().addNotification("An enemy [${unitName}] has joined us!", addedUnit.getTile().position, NotificationCategory.War, unitName)
|
||||||
|
|
||||||
|
val civilianUnit = tile.civilianUnit
|
||||||
|
// placeUnitNearTile might not have spawned the unit in exactly this tile, in which case no capture would have happened on this tile. So we need to do that here.
|
||||||
|
if (addedUnit.getTile() != tile && civilianUnit != null) {
|
||||||
|
captureCivilianUnit(attacker, MapUnitCombatant(civilianUnit))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalArgumentException if the [attacker] and [defender] belong to the same civ.
|
||||||
|
*/
|
||||||
|
fun captureCivilianUnit(attacker: ICombatant, defender: MapUnitCombatant, checkDefeat: Boolean = true) {
|
||||||
|
require(attacker.getCivInfo() != defender.getCivInfo()) {
|
||||||
|
"Can't capture our own unit!"
|
||||||
|
}
|
||||||
|
|
||||||
|
// need to save this because if the unit is captured its owner wil be overwritten
|
||||||
|
val defenderCiv = defender.getCivInfo()
|
||||||
|
|
||||||
|
val capturedUnit = defender.unit
|
||||||
|
// Stop current action
|
||||||
|
capturedUnit.action = null
|
||||||
|
|
||||||
|
val capturedUnitTile = capturedUnit.getTile()
|
||||||
|
val originalOwner = if (capturedUnit.originalOwner != null)
|
||||||
|
capturedUnit.civ.gameInfo.getCivilization(capturedUnit.originalOwner!!)
|
||||||
|
else null
|
||||||
|
|
||||||
|
var wasDestroyedInstead = false
|
||||||
|
when {
|
||||||
|
// Uncapturable units are destroyed
|
||||||
|
defender.unit.hasUnique(UniqueType.Uncapturable) -> {
|
||||||
|
capturedUnit.destroy()
|
||||||
|
wasDestroyedInstead = true
|
||||||
|
}
|
||||||
|
// City states can never capture settlers at all
|
||||||
|
capturedUnit.hasUnique(UniqueType.FoundCity) && attacker.getCivInfo().isCityState() -> {
|
||||||
|
capturedUnit.destroy()
|
||||||
|
wasDestroyedInstead = true
|
||||||
|
}
|
||||||
|
// Is it our old unit?
|
||||||
|
attacker.getCivInfo() == originalOwner -> {
|
||||||
|
// Then it is recaptured without converting settlers to workers
|
||||||
|
capturedUnit.capturedBy(attacker.getCivInfo())
|
||||||
|
}
|
||||||
|
// Return captured civilian to its original owner?
|
||||||
|
defender.getCivInfo().isBarbarian()
|
||||||
|
&& originalOwner != null
|
||||||
|
&& !originalOwner.isBarbarian()
|
||||||
|
&& attacker.getCivInfo() != originalOwner
|
||||||
|
&& attacker.getCivInfo().knows(originalOwner)
|
||||||
|
&& originalOwner.isAlive()
|
||||||
|
&& !attacker.getCivInfo().isAtWarWith(originalOwner)
|
||||||
|
&& attacker.getCivInfo().playerType == PlayerType.Human // Only humans get the choice
|
||||||
|
-> {
|
||||||
|
capturedUnit.capturedBy(attacker.getCivInfo())
|
||||||
|
attacker.getCivInfo().popupAlerts.add(
|
||||||
|
PopupAlert(
|
||||||
|
AlertType.RecapturedCivilian,
|
||||||
|
capturedUnitTile.position.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> captureOrConvertToWorker(capturedUnit, attacker.getCivInfo())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wasDestroyedInstead)
|
||||||
|
defenderCiv.addNotification(
|
||||||
|
"An enemy [${attacker.getName()}] has captured our [${defender.getName()}]",
|
||||||
|
defender.getTile().position, NotificationCategory.War, attacker.getName(),
|
||||||
|
NotificationIcon.War, defender.getName()
|
||||||
|
)
|
||||||
|
else {
|
||||||
|
defenderCiv.addNotification(
|
||||||
|
"An enemy [${attacker.getName()}] has destroyed our [${defender.getName()}]",
|
||||||
|
defender.getTile().position, NotificationCategory.War, attacker.getName(),
|
||||||
|
NotificationIcon.War, defender.getName()
|
||||||
|
)
|
||||||
|
Battle.triggerDefeatUniques(defender, attacker, capturedUnitTile)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkDefeat)
|
||||||
|
Battle.destroyIfDefeated(defenderCiv, attacker.getCivInfo())
|
||||||
|
capturedUnit.updateVisibleTiles()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun captureOrConvertToWorker(capturedUnit: MapUnit, capturingCiv: Civilization){
|
||||||
|
// Captured settlers are converted to workers unless captured by barbarians (so they can be returned later).
|
||||||
|
if (capturedUnit.hasUnique(UniqueType.FoundCity) && !capturingCiv.isBarbarian()) {
|
||||||
|
capturedUnit.destroy()
|
||||||
|
// This is so that future checks which check if a unit has been captured are caught give the right answer
|
||||||
|
// For example, in postBattleMoveToAttackedTile
|
||||||
|
capturedUnit.civ = capturingCiv
|
||||||
|
|
||||||
|
val workerTypeUnit = capturingCiv.gameInfo.ruleset.units.values
|
||||||
|
.firstOrNull { it.isCivilian() && it.getMatchingUniques(UniqueType.BuildImprovements)
|
||||||
|
.any { unique -> unique.params[0] == "Land" } }
|
||||||
|
|
||||||
|
if (workerTypeUnit != null)
|
||||||
|
capturingCiv.units.placeUnitNearTile(capturedUnit.currentTile.position, workerTypeUnit)
|
||||||
|
}
|
||||||
|
else capturedUnit.capturedBy(capturingCiv)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user