From ddf32367f1be2a055a1ec9c1d6f02a7c0ee6b0e7 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 Apr 2026 10:28:23 -0400 Subject: [PATCH 1/3] Added Logic For Deadeye Duelist at the End of turn --- CLAUDE.md | 0 forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java | 7 ++++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java index d4a7781c389..9b9ba2f5a31 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -740,7 +740,12 @@ private boolean damageChoosingTargets(final Player ai, final SpellAbility sa, fi || immediately) { boolean pingAfterAttack = "PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai); boolean isPWAbility = sa.isPwAbility() && sa.getPayCosts().hasSpecificCostType(CostPutCounter.class); - if (isPWAbility || (pingAfterAttack && !avoidTargetP(ai, sa)) || shouldTgtP(ai, sa, dmg, noPrevention)) { + // Activated abilities that target only players (not creatures) should fire at end of opponent's turn + boolean freePingPlayerOnly = !source.isSpell() && sa.isAbility() + && tgt.canTgtPlayer() && !tgt.canTgtCreature() + && phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai) + && !avoidTargetP(ai, sa); + if (isPWAbility || (pingAfterAttack && !avoidTargetP(ai, sa)) || shouldTgtP(ai, sa, dmg, noPrevention) || freePingPlayerOnly) { tcs.add(enemy); if (divided) { sa.addDividedAllocation(enemy, dmg); From cab7307ef5cec56400be0dbbc88fed2bcfad393f Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 Apr 2026 10:33:13 -0400 Subject: [PATCH 2/3] Remove CLAUDE.md from PR --- CLAUDE.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index e69de29bb2d..00000000000 From 66cc45a42a85b7ef4363deba8e0fc46d5b89c15d Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 12 Apr 2026 16:22:03 -0400 Subject: [PATCH 3/3] Move freePing logic to shared outer block for all player-targeting abilities --- .../java/forge/ai/ability/DamageDealAi.java | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java index 9b9ba2f5a31..bc3e019747c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -675,29 +675,6 @@ private boolean damageChoosingTargets(final Player ai, final SpellAbility sa, fi // TODO: add check here if card is about to die from something // on the stack or from taking combat damage - - final Cost abCost = sa.getPayCosts(); - boolean freePing = immediately || abCost == null || sa.getTargets().size() > 0; - - if (!source.isSpell()) { - if (phase.is(PhaseType.END_OF_TURN) && sa.isAbility() && abCost.isReusuableResource()) { - if (phase.getNextTurn().equals(ai)) - freePing = true; - } - - if (phase.is(PhaseType.MAIN2) && sa.isAbility()) { - if (sa.isPwAbility() || source.hasSVar("EndOfTurnLeavePlay")) - freePing = true; - } - } - - if (freePing && sa.canTarget(enemy) && !avoidTargetP(ai, sa)) { - tcs.add(enemy); - if (divided) { - sa.addDividedAllocation(enemy, dmg); - break; - } - } } else if (tgt.canTgtCreature() || tgt.canTgtPlaneswalker()) { final Card c = dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, mandatory); if (c != null) { @@ -734,18 +711,28 @@ private boolean damageChoosingTargets(final Player ai, final SpellAbility sa, fi return false; } if (sa.canTarget(enemy) && sa.canAddMoreTarget()) { + final Cost abCost = sa.getPayCosts(); + boolean freePing = immediately || abCost == null || sa.getTargets().size() > 0; + + if (!source.isSpell()) { + if (phase.is(PhaseType.END_OF_TURN) && sa.isAbility() && abCost.isReusuableResource()) { + if (phase.getNextTurn().equals(ai)) + freePing = true; + } + + if (phase.is(PhaseType.MAIN2) && sa.isAbility()) { + if (sa.isPwAbility() || source.hasSVar("EndOfTurnLeavePlay")) + freePing = true; + } + } + if ((phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai)) || (isSorcerySpeed(sa, ai) && phase.is(PhaseType.MAIN2)) || ("BurnCreatures".equals(logic) && !enemy.getCreaturesInPlay().isEmpty()) || immediately) { boolean pingAfterAttack = "PingAfterAttack".equals(logic) && phase.getPhase().isAfter(PhaseType.COMBAT_DECLARE_ATTACKERS) && phase.isPlayerTurn(ai); boolean isPWAbility = sa.isPwAbility() && sa.getPayCosts().hasSpecificCostType(CostPutCounter.class); - // Activated abilities that target only players (not creatures) should fire at end of opponent's turn - boolean freePingPlayerOnly = !source.isSpell() && sa.isAbility() - && tgt.canTgtPlayer() && !tgt.canTgtCreature() - && phase.is(PhaseType.END_OF_TURN) && phase.getNextTurn().equals(ai) - && !avoidTargetP(ai, sa); - if (isPWAbility || (pingAfterAttack && !avoidTargetP(ai, sa)) || shouldTgtP(ai, sa, dmg, noPrevention) || freePingPlayerOnly) { + if ((freePing && !avoidTargetP(ai, sa)) || isPWAbility || (pingAfterAttack && !avoidTargetP(ai, sa)) || shouldTgtP(ai, sa, dmg, noPrevention)) { tcs.add(enemy); if (divided) { sa.addDividedAllocation(enemy, dmg);