mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-22 22:00:24 +07:00
Better "Withdraws before melee combat" unique
This commit is contained in:
@ -125,11 +125,21 @@ object Battle {
|
||||
|
||||
// Withdraw from melee ability
|
||||
if (attacker is MapUnitCombatant && attacker.isMelee() && defender is MapUnitCombatant) {
|
||||
val withdrawUniques = defender.unit.getMatchingUniques(UniqueType.MayWithdraw)
|
||||
val combinedProbabilityToStayPut = withdrawUniques.fold(100) { probabilityToStayPut, unique -> probabilityToStayPut * (100-unique.params[0].toInt()) / 100 }
|
||||
val baseWithdrawChance = 100 - combinedProbabilityToStayPut
|
||||
// If a mod allows multiple withdraw properties, they stack multiplicatively
|
||||
if (baseWithdrawChance != 0 && doWithdrawFromMeleeAbility(attacker, defender, baseWithdrawChance))
|
||||
val withdrawChance =
|
||||
if (defender.unit.hasUnique(UniqueType.WithdrawsBeforeMeleeCombat, stateForConditionals = StateForConditionals(
|
||||
civInfo = defender.getCivInfo(),
|
||||
ourCombatant = defender,
|
||||
theirCombatant = attacker,
|
||||
tile = attackedTile
|
||||
))
|
||||
) 100
|
||||
|
||||
else 100 - defender.unit.getMatchingUniques(UniqueType.MayWithdraw)
|
||||
.fold(100) { probabilityToWithdraw, unique ->
|
||||
probabilityToWithdraw * (100 - unique.params[0].toInt()) / 100
|
||||
}
|
||||
|
||||
if (withdrawChance != 0 && doWithdrawFromMeleeAbility(attacker, defender, withdrawChance))
|
||||
return DamageDealt.None
|
||||
}
|
||||
|
||||
@ -615,44 +625,29 @@ object Battle {
|
||||
}
|
||||
}
|
||||
|
||||
private fun doWithdrawFromMeleeAbility(attacker: ICombatant, defender: ICombatant, baseWithdrawChance: Int): Boolean {
|
||||
if (baseWithdrawChance == 0) return false
|
||||
// Some notes...
|
||||
// unit.getUniques() is a union of BaseUnit uniques and Promotion effects.
|
||||
// according to some strategy guide the Slinger's withdraw ability is inherited on upgrade,
|
||||
// according to the Ironclad entry of the wiki the Caravel's is lost on upgrade.
|
||||
// therefore: Implement the flag as unique for the Caravel and Destroyer, as promotion for the Slinger.
|
||||
if (attacker !is MapUnitCombatant) return false // allow simple access to unit property
|
||||
if (defender !is MapUnitCombatant) return false
|
||||
private fun doWithdrawFromMeleeAbility(attacker: MapUnitCombatant, defender: MapUnitCombatant, withdrawChance: Int): Boolean {
|
||||
if (withdrawChance == 0) return false
|
||||
if (defender.unit.isEmbarked()) return false
|
||||
if (defender.unit.cache.cannotMove) return false
|
||||
|
||||
// This is where the chance comes into play
|
||||
if (Random( // 'randomness' is consistent for turn and tile, to avoid save-scumming
|
||||
attacker.getCivInfo().gameInfo.turns * defender.getTile().position.hashCode().toLong()
|
||||
).nextInt(100) > withdrawChance) 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
|
||||
val fromTile = defender.getTile()
|
||||
val attTile = attacker.getTile()
|
||||
val attackerTile = attacker.getTile()
|
||||
|
||||
fun canNotWithdrawTo(tile: Tile): Boolean { // if the tile is what the defender can't withdraw to, this fun will return true
|
||||
return !defender.unit.movement.canMoveTo(tile)
|
||||
|| defendBaseUnit.isLandUnit() && !tile.isLand // forbid retreat from land to sea - embarked already excluded
|
||||
|| defender.isLandUnit() && !tile.isLand // forbid retreat from land to sea - embarked already excluded
|
||||
|| tile.isCityCenter() && tile.getOwner() != defender.getCivInfo() // forbid retreat into the city which doesn't belong to the defender
|
||||
}
|
||||
/* Calculate success chance: Base chance from json, calculation method from https://www.bilibili.com/read/cv2216728
|
||||
In general, except attacker's tile, 5 tiles neighbors the defender :
|
||||
2 of which are also attacker's neighbors ( we call them 2-Tiles) and the other 3 aren't (we call them 3-Tiles).
|
||||
Withdraw chance depends on 2 factors : attacker's movement and how many tiles in 3-Tiles the defender can't withdraw to.
|
||||
If the defender can withdraw, at first we choose a tile as toTile from 3-Tiles the defender can withdraw to.
|
||||
If 3-Tiles the defender can withdraw to is null, we choose this from 2-Tiles the defender can withdraw to.
|
||||
If 2-Tiles the defender can withdraw to is also null, we return false.
|
||||
*/
|
||||
val percentChance = baseWithdrawChance - max(0, (attackBaseUnit.movement-2)) * 20 -
|
||||
fromTile.neighbors.filterNot { it == attTile || it in attTile.neighbors }.count { canNotWithdrawTo(it) } * 20
|
||||
// Get a random number in [0,100) : if the number <= percentChance, defender will withdraw from melee
|
||||
if (Random( // 'randomness' is consistent for turn and tile, to avoid save-scumming
|
||||
(attacker.getCivInfo().gameInfo.turns * defender.getTile().hashCode()).toLong()
|
||||
).nextInt(100) > percentChance) return false
|
||||
val firstCandidateTiles = fromTile.neighbors.filterNot { it == attTile || it in attTile.neighbors }
|
||||
|
||||
val firstCandidateTiles = fromTile.neighbors.filterNot { it == attackerTile || it in attackerTile.neighbors }
|
||||
.filterNot { canNotWithdrawTo(it) }
|
||||
val secondCandidateTiles = fromTile.neighbors.filter { it in attTile.neighbors }
|
||||
val secondCandidateTiles = fromTile.neighbors.filter { it in attackerTile.neighbors }
|
||||
.filterNot { canNotWithdrawTo(it) }
|
||||
val toTile: Tile = when {
|
||||
firstCandidateTiles.any() -> firstCandidateTiles.toList().random()
|
||||
@ -667,11 +662,12 @@ object Battle {
|
||||
// and count 1 attack for attacker but leave it in place
|
||||
reduceAttackerMovementPointsAndAttacks(attacker, defender)
|
||||
|
||||
val attackingUnit = attackBaseUnit.name; val defendingUnit = defendBaseUnit.name
|
||||
val notificationString = "[$defendingUnit] withdrew from a [$attackingUnit]"
|
||||
val attackerName = attacker.getName()
|
||||
val defenderName = defender.getName()
|
||||
val notificationString = "[$defenderName] withdrew from a [$attackerName]"
|
||||
val locations = LocationAction(toTile.position, attacker.getTile().position)
|
||||
defender.getCivInfo().addNotification(notificationString, locations, NotificationCategory.War, defendingUnit, NotificationIcon.War, attackingUnit)
|
||||
attacker.getCivInfo().addNotification(notificationString, locations, NotificationCategory.War, defendingUnit, NotificationIcon.War, attackingUnit)
|
||||
defender.getCivInfo().addNotification(notificationString, locations, NotificationCategory.War, defenderName, NotificationIcon.War, attackerName)
|
||||
attacker.getCivInfo().addNotification(notificationString, locations, NotificationCategory.War, defenderName, NotificationIcon.War, attackerName)
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -411,7 +411,8 @@ enum class UniqueType(
|
||||
@Deprecated("As of 4.12.4", ReplaceWith("No damage penalty for wounded units"))
|
||||
NoDamagePenalty("Damage is ignored when determining unit Strength", UniqueTarget.Unit, UniqueTarget.Global),
|
||||
Uncapturable("Uncapturable", UniqueTarget.Unit),
|
||||
// Replace with "Withdraws before melee combat <with [amount]% chance>"?
|
||||
WithdrawsBeforeMeleeCombat("Withdraws before melee combat", UniqueTarget.Unit),
|
||||
@Deprecated("As of 4.12.4", ReplaceWith("Withdraws before melee combat <with [amount]% chance>"))
|
||||
MayWithdraw("May withdraw before melee ([amount]%)", UniqueTarget.Unit),
|
||||
CannotCaptureCities("Unable to capture cities", UniqueTarget.Unit),
|
||||
CannotPillage("Unable to pillage tiles", UniqueTarget.Unit),
|
||||
|
Reference in New Issue
Block a user