Battle table displays bonuses according to tile that unit will attack from, not the current tile it's on

This commit is contained in:
Yair Morgenstern
2023-04-08 21:34:55 +03:00
parent ca06d7e54a
commit cb20d91822
4 changed files with 48 additions and 35 deletions

View File

@ -1,5 +1,6 @@
package com.unciv.logic.battle
import com.unciv.logic.map.tile.Tile
import com.unciv.models.Counter
import com.unciv.models.ruleset.GlobalUniques
import com.unciv.models.ruleset.unique.StateForConditionals
@ -29,7 +30,7 @@ object BattleDamage {
return "$source - $conditionalsText"
}
private fun getGeneralModifiers(combatant: ICombatant, enemy: ICombatant, combatAction: CombatAction): Counter<String> {
private fun getGeneralModifiers(combatant: ICombatant, enemy: ICombatant, combatAction: CombatAction, tileToAttackFrom:Tile): Counter<String> {
val modifiers = Counter<String>()
val civInfo = combatant.getCivInfo()
@ -58,7 +59,10 @@ object BattleDamage {
}
//https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php
val adjacentUnits = combatant.getTile().neighbors.flatMap { it.getUnits() }
var adjacentUnits = combatant.getTile().neighbors.flatMap { it.getUnits() }
if (enemy.getTile() !in combatant.getTile().neighbors && tileToAttackFrom in combatant.getTile().neighbors
&& enemy is MapUnitCombatant)
adjacentUnits += sequenceOf(enemy.unit)
val strengthMalus = adjacentUnits.filter { it.civ.isAtWarWith(civInfo) }
.flatMap { it.getMatchingUniques(UniqueType.StrengthForAdjacentEnemies) }
.filter { combatant.matchesCategory(it.params[1]) && combatant.getTile().matchesFilter(it.params[2]) }
@ -105,9 +109,9 @@ object BattleDamage {
fun getAttackModifiers(
attacker: ICombatant,
defender: ICombatant
defender: ICombatant, tileToAttackFrom: Tile
): Counter<String> {
val modifiers = getGeneralModifiers(attacker, defender, CombatAction.Attack)
val modifiers = getGeneralModifiers(attacker, defender, CombatAction.Attack, tileToAttackFrom)
if (attacker is MapUnitCombatant) {
if (attacker.unit.isEmbarked()
@ -139,11 +143,11 @@ object BattleDamage {
modifiers["Flanking"] =
(flankingBonus * numberOfOtherAttackersSurroundingDefender).toInt()
}
if (attacker.getTile().aerialDistanceTo(defender.getTile()) == 1 &&
attacker.getTile().isConnectedByRiver(defender.getTile()) &&
if (tileToAttackFrom.aerialDistanceTo(defender.getTile()) == 1 &&
tileToAttackFrom.isConnectedByRiver(defender.getTile()) &&
!attacker.unit.hasUnique(UniqueType.AttackAcrossRiver)
) {
if (!attacker.getTile()
if (!tileToAttackFrom
.hasConnection(attacker.getCivInfo()) // meaning, the tiles are not road-connected for this civ
|| !defender.getTile().hasConnection(attacker.getCivInfo())
|| !attacker.getCivInfo().tech.roadsConnectAcrossRivers
@ -172,8 +176,8 @@ object BattleDamage {
return modifiers
}
fun getDefenceModifiers(attacker: ICombatant, defender: ICombatant): Counter<String> {
val modifiers = getGeneralModifiers(defender, attacker, CombatAction.Defend)
fun getDefenceModifiers(attacker: ICombatant, defender: ICombatant, tileToAttackFrom: Tile): Counter<String> {
val modifiers = getGeneralModifiers(defender, attacker, CombatAction.Defend, tileToAttackFrom)
val tile = defender.getTile()
if (defender is MapUnitCombatant) {
@ -222,9 +226,10 @@ object BattleDamage {
*/
fun getAttackingStrength(
attacker: ICombatant,
defender: ICombatant
defender: ICombatant,
tileToAttackFrom: Tile
): Float {
val attackModifier = modifiersToFinalBonus(getAttackModifiers(attacker, defender))
val attackModifier = modifiersToFinalBonus(getAttackModifiers(attacker, defender, tileToAttackFrom))
return max(1f, attacker.getAttackingStrength() * attackModifier)
}
@ -232,34 +237,36 @@ object BattleDamage {
/**
* Includes defence modifiers
*/
fun getDefendingStrength(attacker: ICombatant, defender: ICombatant): Float {
val defenceModifier = modifiersToFinalBonus(getDefenceModifiers(attacker, defender))
fun getDefendingStrength(attacker: ICombatant, defender: ICombatant, tileToAttackFrom: Tile): Float {
val defenceModifier = modifiersToFinalBonus(getDefenceModifiers(attacker, defender, tileToAttackFrom))
return max(1f, defender.getDefendingStrength(attacker.isRanged()) * defenceModifier)
}
fun calculateDamageToAttacker(
attacker: ICombatant,
defender: ICombatant,
tileToAttackFrom: Tile = defender.getTile(),
/** Between 0 and 1. */
randomnessFactor: Float = Random(attacker.getCivInfo().gameInfo.turns * attacker.getTile().position.hashCode().toLong()).nextFloat()
): Int {
if (attacker.isRanged() && !attacker.isAirUnit()) return 0
if (defender.isCivilian()) return 0
val ratio = getAttackingStrength(attacker, defender) / getDefendingStrength(
attacker, defender)
val ratio = getAttackingStrength(attacker, defender, tileToAttackFrom) / getDefendingStrength(
attacker, defender, tileToAttackFrom)
return (damageModifier(ratio, true, randomnessFactor) * getHealthDependantDamageRatio(defender)).roundToInt()
}
fun calculateDamageToDefender(
attacker: ICombatant,
defender: ICombatant,
tileToAttackFrom: Tile = defender.getTile(),
/** Between 0 and 1. Defaults to turn and location-based random to avoid save scumming */
randomnessFactor: Float = Random(attacker.getCivInfo().gameInfo.turns * attacker.getTile().position.hashCode().toLong()).nextFloat()
,
): Int {
if (defender.isCivilian()) return 40
val ratio = getAttackingStrength(attacker, defender) / getDefendingStrength(
attacker, defender)
val ratio = getAttackingStrength(attacker, defender, tileToAttackFrom) /
getDefendingStrength(attacker, defender, tileToAttackFrom)
return (damageModifier(ratio, false, randomnessFactor) * getHealthDependantDamageRatio(attacker)).roundToInt()
}

View File

@ -4,7 +4,6 @@ import com.badlogic.gdx.graphics.Color
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
@ -72,7 +71,14 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
} else {
val defender = tryGetDefender() ?: return hide()
if (attacker is CityCombatant && defender is CityCombatant) return hide()
simulateBattle(attacker, defender)
val tileToAttackFrom = if (attacker is MapUnitCombatant)
BattleHelper.getAttackableEnemies(
attacker.unit,
attacker.unit.movement.getDistanceToTiles()
)
.firstOrNull { it.tileToAttack == defender.getTile() }?.tileToAttackFrom ?: attacker.getTile()
else attacker.getTile()
simulateBattle(attacker, defender, tileToAttackFrom)
}
isVisible = true
@ -134,7 +140,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
add(modifierLabel).width(quarterScreen - upOrDownLabel.minWidth)
}
private fun simulateBattle(attacker: ICombatant, defender: ICombatant){
private fun simulateBattle(attacker: ICombatant, defender: ICombatant, tileToAttackFrom: Tile){
clear()
val attackerNameWrapper = Table()
@ -161,12 +167,12 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
add(defender.getDefendingStrength(attacker.isRanged()).toString() + defenceIcon).row()
val attackerModifiers =
BattleDamage.getAttackModifiers(attacker, defender).map {
BattleDamage.getAttackModifiers(attacker, defender, tileToAttackFrom).map {
getModifierTable(it.key, it.value)
}
val defenderModifiers =
if (defender is MapUnitCombatant)
BattleDamage.getDefenceModifiers(attacker, defender).map {
BattleDamage.getDefenceModifiers(attacker, defender, tileToAttackFrom).map {
getModifierTable(it.key, it.value)
}
else listOf()
@ -179,8 +185,8 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
if (attackerModifiers.any() || defenderModifiers.any()){
addSeparator()
val attackerStrength = BattleDamage.getAttackingStrength(attacker, defender).roundToInt()
val defenderStrength = BattleDamage.getDefendingStrength(attacker, defender).roundToInt()
val attackerStrength = BattleDamage.getAttackingStrength(attacker, defender, tileToAttackFrom).roundToInt()
val defenderStrength = BattleDamage.getDefendingStrength(attacker, defender, tileToAttackFrom).roundToInt()
add(attackerStrength.toString() + attackIcon)
add(defenderStrength.toString() + attackIcon).row()
}
@ -193,12 +199,12 @@ class BattleTable(val worldScreen: WorldScreen): Table() {
row()
}
val maxDamageToDefender = BattleDamage.calculateDamageToDefender(attacker, defender, 1f)
val minDamageToDefender = BattleDamage.calculateDamageToDefender(attacker, defender, 0f)
val maxDamageToDefender = BattleDamage.calculateDamageToDefender(attacker, defender, tileToAttackFrom, 1f)
val minDamageToDefender = BattleDamage.calculateDamageToDefender(attacker, defender, tileToAttackFrom, 0f)
var expectedDamageToDefenderForHealthbar = (maxDamageToDefender + minDamageToDefender) / 2
val maxDamageToAttacker = BattleDamage.calculateDamageToAttacker(attacker, defender, 1f)
val minDamageToAttacker = BattleDamage.calculateDamageToAttacker(attacker, defender, 0f)
val maxDamageToAttacker = BattleDamage.calculateDamageToAttacker(attacker, defender, tileToAttackFrom, 1f)
val minDamageToAttacker = BattleDamage.calculateDamageToAttacker(attacker, defender, tileToAttackFrom, 0f)
var expectedDamageToAttackerForHealthbar = (maxDamageToAttacker + minDamageToAttacker) / 2
if (expectedDamageToAttackerForHealthbar > attacker.getHealth() && expectedDamageToDefenderForHealthbar > defender.getHealth()) {

View File

@ -35,7 +35,7 @@ So first things first - the initial "No assumptions" setup to have Unciv run fro
![image](https://user-images.githubusercontent.com/44038014/169315352-9ba0c4cf-307c-44d1-b3bc-2a58752c6854.png)
- Settings > Editor > General > On Save > Uncheck Remove trailing spaces on: [...] to prevent it from removing necessary trailing whitespace in template.properties for translation files
![image](https://user-images.githubusercontent.com/44038014/169316243-07e36b8e-4c9e-44c4-941c-47e634c68b4c.png)
- If you download mods, right-click the `android/assets/mods` folder , "Mark directory as" > Excluded, to [disable indexing on mods](https://www.jetbrains.com/help/idea/indexing.html#exclude) for performance
Unciv uses Gradle to specify dependencies and how to run. In the background, the Gradle gnomes will be off fetching the packages (a one-time effort) and, once that's done, will build the project!
Unciv uses Gradle 7.5 and the Android Gradle Plugin 7.3.1. Can check in File > Project Structure > Project

View File

@ -26,13 +26,13 @@ class TriggeredUniquesTests {
@Test
fun testConditionalTimedUniqueIsTriggerable() {
val unique = policy.uniqueObjects.first{ it.type == UniqueType.Strength }
Assert.assertTrue("Unique with timed conditional must be triggerable", unique!!.isTriggerable)
Assert.assertTrue("Unique with timed conditional must be triggerable", unique.isTriggerable)
}
@Test
fun testConditionalTimedUniqueStrength() {
civInfo.policies.adopt(policy, true)
val modifiers = BattleDamage.getAttackModifiers(attacker, defender)
val modifiers = BattleDamage.getAttackModifiers(attacker, defender, attacker.getTile())
Assert.assertTrue("Timed Strength should work right after triggering", modifiers.sumValues() == 42)
}
@ -43,7 +43,7 @@ class TriggeredUniquesTests {
// and right now that attacker is not in the civ's unit list
civInfo.units.addUnit(attacker.unit, false)
TurnManager(civInfo).endTurn()
val modifiers = BattleDamage.getAttackModifiers(attacker, defender)
val modifiers = BattleDamage.getAttackModifiers(attacker, defender, attacker.getTile())
Assert.assertTrue("Timed Strength should no longer work after endTurn", modifiers.sumValues() == 0)
}
}