diff --git a/core/src/com/unciv/logic/battle/BattleDamage.kt b/core/src/com/unciv/logic/battle/BattleDamage.kt index 4028d05909..fca4595a34 100644 --- a/core/src/com/unciv/logic/battle/BattleDamage.kt +++ b/core/src/com/unciv/logic/battle/BattleDamage.kt @@ -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 { + private fun getGeneralModifiers(combatant: ICombatant, enemy: ICombatant, combatAction: CombatAction, tileToAttackFrom:Tile): Counter { val modifiers = Counter() 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 { - 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 { - val modifiers = getGeneralModifiers(defender, attacker, CombatAction.Defend) + fun getDefenceModifiers(attacker: ICombatant, defender: ICombatant, tileToAttackFrom: Tile): Counter { + 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() } diff --git a/core/src/com/unciv/ui/screens/worldscreen/bottombar/BattleTable.kt b/core/src/com/unciv/ui/screens/worldscreen/bottombar/BattleTable.kt index df442b1352..faaacfbb50 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/bottombar/BattleTable.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/bottombar/BattleTable.kt @@ -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()) { diff --git a/docs/Developers/Building-Locally.md b/docs/Developers/Building-Locally.md index dbda29c431..b69d7dd7f4 100644 --- a/docs/Developers/Building-Locally.md +++ b/docs/Developers/Building-Locally.md @@ -30,12 +30,12 @@ So first things first - the initial "No assumptions" setup to have Unciv run fro - If you get a `../../docs/uniques.md (No such file or directory)` error that means you forgot to set the working directory! - Select the Desktop configuration (or however you chose to name it) and click the green arrow button to run! Or you can use the next button -the green critter with six legs and two feelers - to start debugging. - A few Android Studio settings that are recommended: - - Going to Settings > Version Control > Commit and turning off 'Before commit - perform code analysis' - - Settings > Editor > Code Style > Kotlin > Tabs and Indents > Continuation Indent: 4 + - Going to Settings > Version Control > Commit and turning off 'Before commit - perform code analysis' + - Settings > Editor > Code Style > Kotlin > Tabs and Indents > Continuation Indent: 4 ![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 + - 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 diff --git a/tests/src/com/unciv/uniques/TriggeredUniquesTests.kt b/tests/src/com/unciv/uniques/TriggeredUniquesTests.kt index 723999d268..986e00a411 100644 --- a/tests/src/com/unciv/uniques/TriggeredUniquesTests.kt +++ b/tests/src/com/unciv/uniques/TriggeredUniquesTests.kt @@ -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) } }