From 8579ca8d879a992e98326b67c545b4edb0e609ec Mon Sep 17 00:00:00 2001 From: PhiRite Date: Mon, 15 Sep 2025 22:52:54 +0800 Subject: [PATCH 1/2] Added AOE Logic to Battle.kt --- core/src/com/unciv/logic/battle/Battle.kt | 85 ++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index 45eedf06bf..844fb8ec2d 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -183,10 +183,93 @@ object Battle { .firstOrNull { it.text == "Your city [${attacker.getName()}] can bombard the enemy!" } attacker.getCivInfo().notifications.remove(cityCanBombardNotification) } - + + //Aoe attack + if (attacker is MapUnitCombatant && (attacker.unit.hasUnique(UniqueType.AoeDegradeAttack) || attacker.unit.hasUnique(UniqueType.AoeFlatAttack))) { + applyAoeAttack(attacker, defender) + } + return damageDealt + interceptDamage } + //Aoe Logic function + fun applyAoeAttack(attacker: MapUnitCombatant, defender: ICombatant) { + + //degrading AOE is used if both AoeDegradeAttack and AoeFlatAttack uniques are present + val degradeUnique = attacker.unit.getMatchingUniques(UniqueType.AoeDegradeAttack).firstOrNull() + val flatUnique = attacker.unit.getMatchingUniques(UniqueType.AoeFlatAttack).firstOrNull() + val aoeUnique = degradeUnique ?: flatUnique ?: return + val isDegrade = degradeUnique != null + + val radius = aoeUnique.params.getOrNull(0)?.toIntOrNull() ?: return + if (radius <= 0) return + + val includeAllies = attacker.unit.hasUnique(UniqueType.CanDamageAlliesInAOE) + val excludeSelf = !attacker.unit.hasUnique(UniqueType.CanDamageSelfInAOE) + val receivesCounterDamage = attacker.unit.hasUnique(UniqueType.TakeCounterDamageFromAOE) + val centerTile = defender.getTile() + val attackerCiv = attacker.getCivInfo() + + for (tile in centerTile.getTilesInDistance(radius)) { + val distance = centerTile.aerialDistanceTo(tile) + val distanceFactor = if (isDegrade) + (1.0 - distance.toDouble() / (radius + 1)).coerceAtLeast(0.0) + else 1.0 + + for (unit in tile.getUnits()) { + if (excludeSelf && unit == attacker.unit) continue + if (unit == (defender as? MapUnitCombatant)?.unit && unit != attacker.unit) continue + + val isAlly = !unit.civ.isAtWarWith(attackerCiv) + if (!includeAllies && isAlly && unit != attacker.unit) continue + + val aoeDefender = MapUnitCombatant(unit) + val damage = (BattleDamage.calculateDamageToDefender(attacker, aoeDefender) * distanceFactor).toInt().coerceAtLeast(1) + + if (aoeDefender.isCivilian() && attacker.isMelee()) { + // Capture civilian units if attacker can capture + BattleUnitCapture.captureCivilianUnit(attacker, aoeDefender) + continue + } + + // Apply combat effects but no counter-damage + triggerCombatUniques(attacker, aoeDefender, tile) + aoeDefender.takeDamage(damage) + triggerDamageUniquesForUnit(attacker, aoeDefender, tile, CombatAction.Attack) + + if (aoeDefender.isDefeated() && !aoeDefender.isCivilian()) { + // Try to capture first - if successful, skip other defeat effects + val captured = BattleUnitCapture.tryCaptureMilitaryUnit(attacker, aoeDefender, tile) + if (!captured) { + // If not captured, proceed with normal defeat effects + triggerPostKillingUniques(aoeDefender, attacker, tile) + } + } + + if (receivesCounterDamage && !aoeDefender.isCivilian()) { + val isMainTarget = + (defender is MapUnitCombatant && unit == defender.unit) || + (defender is CityCombatant && unit == defender.city) + + val isSelf = (unit == attacker.unit) + + // Counter damage from every valid target except main defender + self + if (!isMainTarget && !isSelf) { + val baseCounterDamage = BattleDamage.calculateDamageToAttacker(attacker, aoeDefender) + val finalCounterDamage = + if (isDegrade) (baseCounterDamage * distanceFactor).toInt().coerceAtLeast(1) + else baseCounterDamage + + attacker.takeDamage(finalCounterDamage) + + if (attacker.isDefeated()) return + } + } + if (attacker.isDefeated()) return + } + } + } + private fun triggerPostKillingUniques( defender: ICombatant, attacker: ICombatant, From 45d251ce7e3442342bb5eb18ceaefd82a9d528cd Mon Sep 17 00:00:00 2001 From: PhiRite Date: Mon, 15 Sep 2025 22:54:09 +0800 Subject: [PATCH 2/2] Added AOE uniques in UniqueType.kt --- .../com/unciv/models/ruleset/unique/UniqueType.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt index 9350b9cb39..fa5bd0dace 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueType.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueType.kt @@ -426,6 +426,21 @@ enum class UniqueType( IndirectFire("Ranged attacks may be performed over obstacles", UniqueTarget.Unit, UniqueTarget.Global), NuclearWeapon("Nuclear weapon of Strength [amount]", UniqueTarget.Unit), + //Aoe Attacks + AoeFlatAttack("Attacks also target all units within [positiveAmount] tiles", UniqueTarget.Unit, + docDescription = "Performs an attack against every unit in the radius, dealing equal damage. Status effects and on-hit abilities apply to all affected units. " + + "If both this and decreasing area attacks are present, only decreasing area attacks will be used."), + AoeDegradeAttack("Attacks also target units within [positiveAmount] tiles, with damage decreasing by distance", UniqueTarget.Unit, + docDescription = "Performs an attack against every unit in the radius. Status effects and on-hit abilities apply. " + + "Damage decreases with distance from the main target. If both this and equal area attacks are present, only this will be used. " + + "Damage formula: Damage = (1 - (distance / radius)) * baseDamage"), + CanDamageSelfInAOE("Damages self with Aoe attacks", UniqueTarget.Unit, + docDescription = "This unit takes damage from its own Aoe attacks, does not mean it will take damage from allied Aoe attacks."), + CanDamageAlliesInAOE("Damages allied units with Aoe attacks", UniqueTarget.Unit, + docDescription = "This unit damages allied units with Aoe attacks, does not mean it will take damage from its own Aoe attacks unless Damages self with Aoe attacks is also set."), + TakeCounterDamageFromAOE("Takes counter damage from each unit hit by its area attacks", UniqueTarget.Unit, + docDescription = "Only works for melee units, counter damage does not activate on self if \"Damages self with Aoe attacks\" unique is also present."), + NoDefensiveTerrainBonus("No defensive terrain bonus", UniqueTarget.Unit, UniqueTarget.Global), NoDefensiveTerrainPenalty("No defensive terrain penalty", UniqueTarget.Unit, UniqueTarget.Global), NoDamagePenaltyWoundedUnits("No damage penalty for wounded units", UniqueTarget.Unit, UniqueTarget.Global),