mirror of
https://github.com/yairm210/Unciv.git
synced 2025-08-04 09:09:21 +07:00
Battle table displays bonuses according to tile that unit will attack from, not the current tile it's on
This commit is contained in:
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -35,7 +35,7 @@ So first things first - the initial "No assumptions" setup to have Unciv run fro
|
||||

|
||||
- Settings > Editor > General > On Save > Uncheck Remove trailing spaces on: [...] to prevent it from removing necessary trailing whitespace in template.properties for translation files
|
||||

|
||||
|
||||
- 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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user