mirror of
https://github.com/yairm210/Unciv.git
synced 2025-01-10 23:37:31 +07:00
Withdraw before melee is the same as original game (#3674)
This commit is contained in:
parent
f6e188144e
commit
b3a62f4b1a
@ -467,6 +467,6 @@
|
||||
},
|
||||
{
|
||||
"name": "Slinger Withdraw", // only for Slinger and subsequent upgrades
|
||||
"effect": "May withdraw before melee ([133]%)"
|
||||
"effect": "May withdraw before melee ([80]%)"
|
||||
}
|
||||
]
|
@ -680,7 +680,7 @@
|
||||
"requiredTech": "Astronomy",
|
||||
"upgradesTo": "Ironclad",
|
||||
"obsoleteTech": "Combustion",
|
||||
"uniques": ["+1 Visibility Range","May withdraw before melee ([50]%)"],
|
||||
"uniques": ["+1 Visibility Range","May withdraw before melee ([80]%)"],
|
||||
"hurryCostModifier": 30
|
||||
},
|
||||
{
|
||||
@ -1102,7 +1102,7 @@
|
||||
"cost": 375,
|
||||
"requiredTech": "Combustion",
|
||||
"uniques": ["Can attack submarines", "[40]% chance to intercept air attacks",
|
||||
"May withdraw before melee ([50]%)", "Bonus vs WaterSubmarine 100%"]
|
||||
"May withdraw before melee ([80]%)", "Bonus vs WaterSubmarine 100%"]
|
||||
},
|
||||
{
|
||||
"name": "Battleship",
|
||||
|
@ -456,49 +456,44 @@ object Battle {
|
||||
// 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.
|
||||
// I want base chance for Slingers to be 133% (so they still get 76% against the Brute)
|
||||
// but I want base chance for navals to be 50% (assuming their attacker will often be the same baseunit)
|
||||
// the diverging base chance is coded into the effect string as (133%) for now, with 50% as default
|
||||
if (attacker !is MapUnitCombatant) return false // allow simple access to unit property
|
||||
if (defender !is MapUnitCombatant) return false
|
||||
if (defender.unit.isEmbarked()) return false
|
||||
// Calculate success chance: Base chance from json, then ratios of *base* strength and mobility
|
||||
// Promotions have no effect as per what I could find in available documentation
|
||||
val attackBaseUnit = attacker.unit.baseUnit
|
||||
val defendBaseUnit = defender.unit.baseUnit
|
||||
val baseChance = withdrawUnique.params[0].toFloat()
|
||||
val percentChance = (baseChance
|
||||
* defendBaseUnit.strength / attackBaseUnit.strength
|
||||
* defendBaseUnit.movement / attackBaseUnit.movement).toInt()
|
||||
// Roll the dice - note the effect of the surroundings, namely how much room there is to evade to,
|
||||
// isn't yet factored in. But it should, and that's factored in by allowing the dice to choose
|
||||
// any geometrically fitting tile first and *then* fail when checking the tile for viability.
|
||||
val dice = Random().nextInt(100)
|
||||
if (dice > percentChance) return false
|
||||
// Calculate candidate tiles, geometry only
|
||||
val fromTile = defender.getTile()
|
||||
val attTile = attacker.getTile()
|
||||
//assert(fromTile in attTile.neighbors) // function should never be called with attacker not adjacent to defender
|
||||
// the following yields almost always exactly three tiles in a half-moon shape (exception: edge of map)
|
||||
val candidateTiles = fromTile.neighbors.filterNot { it == attTile || it in attTile.neighbors }
|
||||
if (candidateTiles.none()) return false // impossible on our map shapes? No - corner of a rectangular map
|
||||
val toTile = candidateTiles.toList().random()
|
||||
// Now make sure the move is allowed - if not, sorry, bad luck
|
||||
if (!defender.unit.movement.canMoveTo(toTile)) { // forbid impassable or blocked
|
||||
val blocker = toTile.militaryUnit
|
||||
if (blocker != null) {
|
||||
val notificationString = "[" + defendBaseUnit.name + "] could not withdraw from a [" +
|
||||
attackBaseUnit.name + "] - blocked."
|
||||
defender.getCivInfo().addNotification(notificationString, toTile.position, Color.RED)
|
||||
attacker.getCivInfo().addNotification(notificationString, toTile.position, Color.GREEN)
|
||||
}
|
||||
return false
|
||||
fun canNotWithdrawTo(tile: TileInfo): Boolean { // if the tile is what the defender can't withdraw to, this fun will return true
|
||||
return !defender.unit.movement.canMoveTo(tile)
|
||||
|| defendBaseUnit.unitType.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
|
||||
}
|
||||
// base chance for all units is set to 80%
|
||||
val baseChance = withdrawUnique.params[0].toFloat()
|
||||
/* 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 = baseChance - 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().nextInt(100) > percentChance) return false
|
||||
val firstCandidateTiles = fromTile.neighbors.filterNot { it == attTile || it in attTile.neighbors }
|
||||
.filterNot { canNotWithdrawTo(it) }
|
||||
val secondCandidateTiles = fromTile.neighbors.filter { it in attTile.neighbors }
|
||||
.filterNot { canNotWithdrawTo(it) }
|
||||
val toTile: TileInfo = when {
|
||||
firstCandidateTiles.any() -> firstCandidateTiles.toList().random()
|
||||
secondCandidateTiles.any() -> secondCandidateTiles.toList().random()
|
||||
else -> return false
|
||||
}
|
||||
if (defendBaseUnit.unitType.isLandUnit() && !toTile.isLand) return false // forbid retreat from land to sea - embarked already excluded
|
||||
if (toTile.isCityCenter()) return false // forbid retreat into city
|
||||
// Withdraw success: Do it - move defender to toTile for no cost
|
||||
// NOT defender.unit.movement.moveToTile(toTile) - we want a free teleport
|
||||
// no need for any stats recalculation as neither fromTile nor toTile can be a city
|
||||
defender.unit.removeFromTile()
|
||||
defender.unit.putInTile(toTile)
|
||||
// and count 1 attack for attacker but leave it in place
|
||||
|
@ -20,9 +20,9 @@ interface ICombatant{
|
||||
fun matchesCategory(category:String): Boolean
|
||||
|
||||
fun isMelee(): Boolean {
|
||||
return this.getUnitType().isMelee()
|
||||
return getUnitType().isMelee()
|
||||
}
|
||||
fun isRanged(): Boolean {
|
||||
return this.getUnitType().isRanged()
|
||||
return getUnitType().isRanged()
|
||||
}
|
||||
}
|
@ -22,14 +22,13 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant {
|
||||
}
|
||||
|
||||
override fun getAttackingStrength(): Int {
|
||||
if (isRanged()) return unit.baseUnit().rangedStrength
|
||||
else return unit.baseUnit().strength
|
||||
return if (isRanged()) unit.baseUnit().rangedStrength
|
||||
else unit.baseUnit().strength
|
||||
}
|
||||
|
||||
override fun getDefendingStrength(): Int {
|
||||
if(unit.isEmbarked() && !unit.type.isCivilian())
|
||||
return 5 * getCivInfo().getEraNumber()
|
||||
return unit.baseUnit().strength
|
||||
return if (unit.isEmbarked() && !unit.type.isCivilian()) 5 * getCivInfo().getEraNumber()
|
||||
else unit.baseUnit().strength
|
||||
}
|
||||
|
||||
override fun getUnitType(): UnitType {
|
||||
|
Loading…
Reference in New Issue
Block a user