From 6f93fb29b415b9a9cbfcf46bf0b6d408d75c4359 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 11 Apr 2018 07:16:58 +0300 Subject: [PATCH 01/12] - Initial implementation: for the AI, consider dealing damage to planeswalkers directly when applicable. --- .../main/java/forge/ai/ComputerUtilCard.java | 14 ++ .../java/forge/ai/ability/DamageDealAi.java | 121 +++++++++++++++--- 2 files changed, 115 insertions(+), 20 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index ccd53683d25..d158414a879 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -104,6 +104,20 @@ public class ComputerUtilCard { return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc); } + /** + * Returns the worst Planeswalker from a given list + * @param list list of cards to evaluate + * @return best Planeswalker + */ + public static Card getWorstPlaneswalkerAI(final List list) { + List all = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS); + if (all.size() == 0) { + return null; + } + // no AI logic, just return least expensive + return Aggregates.itemWithMin(all, CardPredicates.Accessors.fnGetCmc); + } + // The AI doesn't really pick the best enchantment, just the most expensive. /** *

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 9aaf676ebb6..56aaed39853 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -271,25 +271,7 @@ public class DamageDealAi extends DamageAiBase { final Player activator = sa.getActivatingPlayer(); final Card source = sa.getHostCard(); final Game game = source.getGame(); - List hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa); - - if (activator.equals(ai)) { - hPlay = CardLists.filterControlledBy(hPlay, pl); - } - - final List objects = Lists.newArrayList(sa.getTargets().getTargets()); - if (sa.hasParam("TargetUnique")) { - objects.addAll(sa.getUniqueTargets()); - } - for (final Object o : objects) { - if (o instanceof Card) { - final Card c = (Card) o; - if (hPlay.contains(c)) { - hPlay.remove(c); - } - } - } - hPlay = CardLists.getTargetableCards(hPlay, sa); + List hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game); List killables = CardLists.filter(hPlay, new Predicate() { @Override @@ -338,6 +320,84 @@ public class DamageDealAi extends DamageAiBase { return null; } + /** + *

+ * dealDamageChooseTgtPW. + *

+ * + * @param d + * a int. + * @param noPrevention + * a boolean. + * @param pl + * a {@link forge.game.player.Player} object. + * @param mandatory + * a boolean. + * @return a {@link forge.game.card.Card} object. + */ + private Card dealDamageChooseTgtPW(final Player ai, final SpellAbility sa, final int d, final boolean noPrevention, + final Player pl, final boolean mandatory) { + + final TargetRestrictions tgt = sa.getTargetRestrictions(); + final Player activator = sa.getActivatingPlayer(); + final Card source = sa.getHostCard(); + final Game game = source.getGame(); + List hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game); + + List killables = CardLists.filter(hPlay, new Predicate() { + @Override + public boolean apply(final Card c) { + return c.getSVar("Targeting").equals("Dies") + || (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d) + && !ComputerUtil.canRegenerate(ai, c) + && !(c.getSVar("SacMe").length() > 0); + } + }); + + // Filter AI-specific targets if provided + killables = ComputerUtil.filterAITgts(sa, ai, new CardCollection(killables), true); + + Card targetCard = null; + if (pl.isOpponentOf(ai) && activator.equals(ai) && !killables.isEmpty()) { + return ComputerUtilCard.getBestPlaneswalkerAI(killables); + } + + if (!hPlay.isEmpty()) { + if (pl.isOpponentOf(ai) && activator.equals(ai)) { + return ComputerUtilCard.getBestPlaneswalkerAI(hPlay); + } else if (mandatory) { + return ComputerUtilCard.getWorstPlaneswalkerAI(hPlay); + } + + return targetCard; + } + + return null; + } + + private List getTargetableCards(Player ai, SpellAbility sa, Player pl, TargetRestrictions tgt, Player activator, Card source, Game game) { + List hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa); + + if (activator.equals(ai)) { + hPlay = CardLists.filterControlledBy(hPlay, pl); + } + + final List objects = Lists.newArrayList(sa.getTargets().getTargets()); + if (sa.hasParam("TargetUnique")) { + objects.addAll(sa.getUniqueTargets()); + } + for (final Object o : objects) { + if (o instanceof Card) { + final Card c = (Card) o; + if (hPlay.contains(c)) { + hPlay.remove(c); + } + } + } + hPlay = CardLists.getTargetableCards(hPlay, sa); + return hPlay; + } + /** *

* damageTargetAI. @@ -475,7 +535,27 @@ public class DamageDealAi extends DamageAiBase { return targetingPlayer.getController().chooseTargetsFor(sa); } + if (tgt.canTgtPlaneswalker()) { + // We can damage planeswalkers with this, consider targeting. + Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, enemy, false); + if (c != null) { + tcs.add(c); + if (divided) { + final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention); + if (assignedDamage <= dmg) { + tgt.addDividedAllocation(c, assignedDamage); + } + dmg = dmg - assignedDamage; + if (dmg <= 0) { + break; + } + } + continue; + } + } + if (tgt.canTgtCreatureAndPlayer()) { + Card c = null; if (this.shouldTgtP(ai, sa, dmg, noPrevention)) { tcs.add(enemy); @@ -489,7 +569,8 @@ public class DamageDealAi extends DamageAiBase { dmg = dmg * sa.getTargets().getNumTargeted() / (sa.getTargets().getNumTargeted() +1); } - final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, false); + // look for creature targets; currently also catches planeswalkers that can be killed immediately + c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, false); if (c != null) { //option to hold removal instead only applies for single targeted removal if (sa.isSpell() && !divided && !immediately && tgt.getMaxTargets(sa.getHostCard(), sa) == 1) { From 73b075d0e7760da2654e61d3e9e9b59016d70b14 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 11 Apr 2018 07:31:00 +0300 Subject: [PATCH 02/12] - DealDamageAi for triggered direct damage to planeswalkers. --- .../java/forge/ai/ability/DamageDealAi.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 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 56aaed39853..f207d4f61f5 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -357,7 +357,6 @@ public class DamageDealAi extends DamageAiBase { // Filter AI-specific targets if provided killables = ComputerUtil.filterAITgts(sa, ai, new CardCollection(killables), true); - Card targetCard = null; if (pl.isOpponentOf(ai) && activator.equals(ai) && !killables.isEmpty()) { return ComputerUtilCard.getBestPlaneswalkerAI(killables); } @@ -365,11 +364,7 @@ public class DamageDealAi extends DamageAiBase { if (!hPlay.isEmpty()) { if (pl.isOpponentOf(ai) && activator.equals(ai)) { return ComputerUtilCard.getBestPlaneswalkerAI(hPlay); - } else if (mandatory) { - return ComputerUtilCard.getWorstPlaneswalkerAI(hPlay); } - - return targetCard; } return null; @@ -768,7 +763,19 @@ public class DamageDealAi extends DamageAiBase { final Player opp = ComputerUtil.getOpponentFor(ai); while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) { - // TODO: Consider targeting the planeswalker + if (tgt.canTgtPlaneswalker()) { + final Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, ai, mandatory); + if (c != null) { + sa.getTargets().add(c); + if (divided) { + tgt.addDividedAllocation(c, dmg); + break; + } + continue; + } + } + + // TODO: This currently also catches planeswalkers that can be killed (still necessary? Or can be removed?) if (tgt.canTgtCreature()) { final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, ai, mandatory); if (c != null) { From 4d33823f28a95a8bcf24c879a462888b700db422 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 11 Apr 2018 07:48:48 +0300 Subject: [PATCH 03/12] - Only process planeswalkers in dealDamageChooseTgtPW. --- forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f207d4f61f5..3cc715e5b32 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -342,7 +342,7 @@ public class DamageDealAi extends DamageAiBase { final Player activator = sa.getActivatingPlayer(); final Card source = sa.getHostCard(); final Game game = source.getGame(); - List hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game); + List hPlay = CardLists.filter(getTargetableCards(ai, sa, pl, tgt, activator, source, game), CardPredicates.Presets.PLANEWALKERS); List killables = CardLists.filter(hPlay, new Predicate() { @Override From 2db32d289f6046fa2d9f9d59595f7df24c0683d8 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 11 Apr 2018 08:05:47 +0300 Subject: [PATCH 04/12] - Fixed a typo in the PLANESWALKERS card predicate preset. --- forge-ai/src/main/java/forge/ai/AiAttackController.java | 2 +- forge-ai/src/main/java/forge/ai/ComputerUtilCard.java | 4 ++-- .../src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java | 2 +- forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java | 4 ++-- forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java | 2 +- forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java | 2 +- forge-ai/src/main/java/forge/ai/ability/DrawAi.java | 2 +- forge-ai/src/main/java/forge/ai/ability/PermanentAi.java | 2 +- forge-game/src/main/java/forge/game/GameAction.java | 2 +- forge-game/src/main/java/forge/game/card/CardPredicates.java | 2 +- .../src/main/java/forge/game/combat/AttackRequirement.java | 2 +- forge-game/src/main/java/forge/game/combat/CombatUtil.java | 2 +- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index f497502b85b..c9468d703cc 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -421,7 +421,7 @@ public class AiAttackController { CardCollectionView oppBattlefield = c.getController().getCardsIn(ZoneType.Battlefield); if (c.getName().equals("Heart of Kiran")) { - if (!CardLists.filter(oppBattlefield, CardPredicates.Presets.PLANEWALKERS).isEmpty()) { + if (!CardLists.filter(oppBattlefield, CardPredicates.Presets.PLANESWALKERS).isEmpty()) { // can be activated by removing a loyalty counter instead of tapping a creature continue; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index d158414a879..68ac9a8faf8 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -96,7 +96,7 @@ public class ComputerUtilCard { * @return best Planeswalker */ public static Card getBestPlaneswalkerAI(final List list) { - List all = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS); + List all = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS); if (all.size() == 0) { return null; } @@ -110,7 +110,7 @@ public class ComputerUtilCard { * @return best Planeswalker */ public static Card getWorstPlaneswalkerAI(final List list) { - List all = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS); + List all = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS); if (all.size() == 0) { return null; } diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java index 90b11371580..6df50568446 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java @@ -112,7 +112,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi { // with one touch CardCollection planeswalkerList = CardLists.filter( CardLists.filterControlledBy(countersList, ai.getOpponents()), - CardPredicates.Presets.PLANEWALKERS, + CardPredicates.Presets.PLANESWALKERS, CardPredicates.hasLessCounter(CounterType.LOYALTY, amount)); if (!planeswalkerList.isEmpty()) { diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java index 4cd549a9187..ba2043efa14 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java @@ -121,7 +121,7 @@ public class CountersRemoveAi extends SpellAbilityAi { list = ai.getOpponents().getCardsIn(ZoneType.Battlefield); list = CardLists.filter(list, CardPredicates.isTargetableBy(sa)); - CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS, + CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS, CardPredicates.hasCounter(CounterType.LOYALTY, 5)); if (!planeswalkerList.isEmpty()) { @@ -169,7 +169,7 @@ public class CountersRemoveAi extends SpellAbilityAi { list = CardLists.filter(list, CardPredicates.isTargetableBy(sa)); CardCollection planeswalkerList = CardLists.filter(list, - Predicates.and(CardPredicates.Presets.PLANEWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())), + Predicates.and(CardPredicates.Presets.PLANESWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())), CardPredicates.hasLessCounter(CounterType.LOYALTY, amount)); if (!planeswalkerList.isEmpty()) { diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java index 738a6529ead..29920d40736 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java @@ -78,7 +78,7 @@ public abstract class DamageAiBase extends SpellAbilityAi { } // burn Planeswalkers - if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) { + if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) { return true; } 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 3cc715e5b32..65aab027ede 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -342,7 +342,7 @@ public class DamageDealAi extends DamageAiBase { final Player activator = sa.getActivatingPlayer(); final Card source = sa.getHostCard(); final Game game = source.getGame(); - List hPlay = CardLists.filter(getTargetableCards(ai, sa, pl, tgt, activator, source, game), CardPredicates.Presets.PLANEWALKERS); + List hPlay = CardLists.filter(getTargetableCards(ai, sa, pl, tgt, activator, source, game), CardPredicates.Presets.PLANESWALKERS); List killables = CardLists.filter(hPlay, new Predicate() { @Override diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index 9923a7b92bb..0c020491679 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -180,7 +180,7 @@ public class DrawAi extends SpellAbilityAi { int numHand = ai.getCardsIn(ZoneType.Hand).size(); if ("Jace, Vryn's Prodigy".equals(sourceName) && ai.getCardsIn(ZoneType.Graveyard).size() > 3) { - return CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS, + return CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS, CardPredicates.isType("Jace")).size() <= 0; } if (source.isSpell() && ai.getCardsIn(ZoneType.Hand).contains(source)) { diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java index cc0ff7d3c41..24c989fc936 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java @@ -69,7 +69,7 @@ public class PermanentAi extends SpellAbilityAi { /* -- not used anymore after Ixalan (Planeswalkers are now legendary, not unique by subtype) -- if (card.isPlaneswalker()) { CardCollection list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), - CardPredicates.Presets.PLANEWALKERS); + CardPredicates.Presets.PLANESWALKERS); for (String type : card.getType().getSubtypes()) { // determine // planewalker // subtype diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 9353a236261..5d7260853ea 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -1303,7 +1303,7 @@ public class GameAction { private boolean handlePlaneswalkerRule(Player p) { // get all Planeswalkers - final List list = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS); + final List list = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS); boolean recheck = false; //final Multimap uniqueWalkers = ArrayListMultimap.create(); // Not used as of Ixalan diff --git a/forge-game/src/main/java/forge/game/card/CardPredicates.java b/forge-game/src/main/java/forge/game/card/CardPredicates.java index ecae20bb89e..ec22b7d74d0 100644 --- a/forge-game/src/main/java/forge/game/card/CardPredicates.java +++ b/forge-game/src/main/java/forge/game/card/CardPredicates.java @@ -587,7 +587,7 @@ public final class CardPredicates { return c.isLand() && c.isSnow(); } }; - public static final Predicate PLANEWALKERS = new Predicate() { + public static final Predicate PLANESWALKERS = new Predicate() { @Override public boolean apply(final Card c) { return c.isPlaneswalker(); diff --git a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java index 2d109fa6ace..c02701595d3 100644 --- a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java +++ b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java @@ -77,7 +77,7 @@ public class AttackRequirement { if (c.hasKeyword("Each opponent must attack you or a planeswalker you control with at least one creature each combat if able.")) { if (attacker.getController().isOpponentOf(c.getController()) && !defenderOrPWSpecific.containsKey(c.getController())) { defenderOrPWSpecific.put(c.getController(), 1); - for (Card pw : CardLists.filter(c.getController().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) { + for (Card pw : CardLists.filter(c.getController().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) { // Add the attack alternatives that suffice (planeswalkers that can be attacked instead of the player) if (!defenderSpecificAlternatives.containsKey(c.getController())) { defenderSpecificAlternatives.put(c.getController(), Lists.newArrayList()); diff --git a/forge-game/src/main/java/forge/game/combat/CombatUtil.java b/forge-game/src/main/java/forge/game/combat/CombatUtil.java index 91533392b28..387b64cd70e 100644 --- a/forge-game/src/main/java/forge/game/combat/CombatUtil.java +++ b/forge-game/src/main/java/forge/game/combat/CombatUtil.java @@ -62,7 +62,7 @@ public class CombatUtil { final FCollection defenders = new FCollection(); for (final Player defender : playerWhoAttacks.getOpponents()) { defenders.add(defender); - final CardCollection planeswalkers = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS); + final CardCollection planeswalkers = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS); defenders.addAll(planeswalkers); } return defenders; From 43a7e895a41160806c3251a68e7109484e9c4ae8 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 11 Apr 2018 08:56:14 +0300 Subject: [PATCH 05/12] - Do not consider "indirect Planeswalker burn" when deliberately evaluating a planeswalker vs. a player target (the clause must be removed and set as default later when the planeswalker redirection rule is fully removed). --- .../src/main/java/forge/ai/ability/DamageAiBase.java | 11 +++++++++-- .../src/main/java/forge/ai/ability/DamageDealAi.java | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java index 29920d40736..ec4ade1a03c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java @@ -44,6 +44,11 @@ public abstract class DamageAiBase extends SpellAbilityAi { } protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) { + // TODO: once the "planeswalker redirection" rule is removed completely, just remove this code and + // remove the "burn Planeswalkers" code in the called method. + return shouldTgtP(comp, sa, d, noPrevention, false); + } + protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention, final boolean noPlaneswalkerRedirection) { int restDamage = d; final Game game = comp.getGame(); Player enemy = ComputerUtil.getOpponentFor(comp); @@ -78,8 +83,10 @@ public abstract class DamageAiBase extends SpellAbilityAi { } // burn Planeswalkers - if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) { - return true; + if (!noPlaneswalkerRedirection) { + if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) { + return true; + } } if (avoidTargetP(comp, sa)) { 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 65aab027ede..c918723848b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -533,7 +533,7 @@ public class DamageDealAi extends DamageAiBase { if (tgt.canTgtPlaneswalker()) { // We can damage planeswalkers with this, consider targeting. Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, enemy, false); - if (c != null) { + if (c != null && !this.shouldTgtP(ai, sa, dmg, noPrevention, true)) { tcs.add(c); if (divided) { final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention); From 3c5e4dd944fd80ef1e72ab5bf53887f243e302dc Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 11 Apr 2018 08:58:27 +0300 Subject: [PATCH 06/12] - Added a TODO entry. --- forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java index ec4ade1a03c..62a35eaa133 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java @@ -83,6 +83,7 @@ public abstract class DamageAiBase extends SpellAbilityAi { } // burn Planeswalkers + // TODO: Must be removed completely when the "planeswalker redirection" rule is removed. if (!noPlaneswalkerRedirection) { if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) { return true; From d3fe888238abb0d896b6ece5fb4bae68ca7fd7bc Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 11 Apr 2018 07:16:58 +0300 Subject: [PATCH 07/12] - Initial implementation: for the AI, consider dealing damage to planeswalkers directly when applicable. --- .../main/java/forge/ai/ComputerUtilCard.java | 14 ++ .../java/forge/ai/ability/DamageDealAi.java | 121 +++++++++++++++--- 2 files changed, 115 insertions(+), 20 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index ccd53683d25..d158414a879 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -104,6 +104,20 @@ public class ComputerUtilCard { return Aggregates.itemWithMax(all, CardPredicates.Accessors.fnGetCmc); } + /** + * Returns the worst Planeswalker from a given list + * @param list list of cards to evaluate + * @return best Planeswalker + */ + public static Card getWorstPlaneswalkerAI(final List list) { + List all = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS); + if (all.size() == 0) { + return null; + } + // no AI logic, just return least expensive + return Aggregates.itemWithMin(all, CardPredicates.Accessors.fnGetCmc); + } + // The AI doesn't really pick the best enchantment, just the most expensive. /** *

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 9aaf676ebb6..56aaed39853 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -271,25 +271,7 @@ public class DamageDealAi extends DamageAiBase { final Player activator = sa.getActivatingPlayer(); final Card source = sa.getHostCard(); final Game game = source.getGame(); - List hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa); - - if (activator.equals(ai)) { - hPlay = CardLists.filterControlledBy(hPlay, pl); - } - - final List objects = Lists.newArrayList(sa.getTargets().getTargets()); - if (sa.hasParam("TargetUnique")) { - objects.addAll(sa.getUniqueTargets()); - } - for (final Object o : objects) { - if (o instanceof Card) { - final Card c = (Card) o; - if (hPlay.contains(c)) { - hPlay.remove(c); - } - } - } - hPlay = CardLists.getTargetableCards(hPlay, sa); + List hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game); List killables = CardLists.filter(hPlay, new Predicate() { @Override @@ -338,6 +320,84 @@ public class DamageDealAi extends DamageAiBase { return null; } + /** + *

+ * dealDamageChooseTgtPW. + *

+ * + * @param d + * a int. + * @param noPrevention + * a boolean. + * @param pl + * a {@link forge.game.player.Player} object. + * @param mandatory + * a boolean. + * @return a {@link forge.game.card.Card} object. + */ + private Card dealDamageChooseTgtPW(final Player ai, final SpellAbility sa, final int d, final boolean noPrevention, + final Player pl, final boolean mandatory) { + + final TargetRestrictions tgt = sa.getTargetRestrictions(); + final Player activator = sa.getActivatingPlayer(); + final Card source = sa.getHostCard(); + final Game game = source.getGame(); + List hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game); + + List killables = CardLists.filter(hPlay, new Predicate() { + @Override + public boolean apply(final Card c) { + return c.getSVar("Targeting").equals("Dies") + || (ComputerUtilCombat.getEnoughDamageToKill(c, d, source, false, noPrevention) <= d) + && !ComputerUtil.canRegenerate(ai, c) + && !(c.getSVar("SacMe").length() > 0); + } + }); + + // Filter AI-specific targets if provided + killables = ComputerUtil.filterAITgts(sa, ai, new CardCollection(killables), true); + + Card targetCard = null; + if (pl.isOpponentOf(ai) && activator.equals(ai) && !killables.isEmpty()) { + return ComputerUtilCard.getBestPlaneswalkerAI(killables); + } + + if (!hPlay.isEmpty()) { + if (pl.isOpponentOf(ai) && activator.equals(ai)) { + return ComputerUtilCard.getBestPlaneswalkerAI(hPlay); + } else if (mandatory) { + return ComputerUtilCard.getWorstPlaneswalkerAI(hPlay); + } + + return targetCard; + } + + return null; + } + + private List getTargetableCards(Player ai, SpellAbility sa, Player pl, TargetRestrictions tgt, Player activator, Card source, Game game) { + List hPlay = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), activator, source, sa); + + if (activator.equals(ai)) { + hPlay = CardLists.filterControlledBy(hPlay, pl); + } + + final List objects = Lists.newArrayList(sa.getTargets().getTargets()); + if (sa.hasParam("TargetUnique")) { + objects.addAll(sa.getUniqueTargets()); + } + for (final Object o : objects) { + if (o instanceof Card) { + final Card c = (Card) o; + if (hPlay.contains(c)) { + hPlay.remove(c); + } + } + } + hPlay = CardLists.getTargetableCards(hPlay, sa); + return hPlay; + } + /** *

* damageTargetAI. @@ -475,7 +535,27 @@ public class DamageDealAi extends DamageAiBase { return targetingPlayer.getController().chooseTargetsFor(sa); } + if (tgt.canTgtPlaneswalker()) { + // We can damage planeswalkers with this, consider targeting. + Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, enemy, false); + if (c != null) { + tcs.add(c); + if (divided) { + final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention); + if (assignedDamage <= dmg) { + tgt.addDividedAllocation(c, assignedDamage); + } + dmg = dmg - assignedDamage; + if (dmg <= 0) { + break; + } + } + continue; + } + } + if (tgt.canTgtCreatureAndPlayer()) { + Card c = null; if (this.shouldTgtP(ai, sa, dmg, noPrevention)) { tcs.add(enemy); @@ -489,7 +569,8 @@ public class DamageDealAi extends DamageAiBase { dmg = dmg * sa.getTargets().getNumTargeted() / (sa.getTargets().getNumTargeted() +1); } - final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, false); + // look for creature targets; currently also catches planeswalkers that can be killed immediately + c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, enemy, false); if (c != null) { //option to hold removal instead only applies for single targeted removal if (sa.isSpell() && !divided && !immediately && tgt.getMaxTargets(sa.getHostCard(), sa) == 1) { From 8c53689e17e605587086ad65552f5d98dcee2f12 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 11 Apr 2018 07:31:00 +0300 Subject: [PATCH 08/12] - DealDamageAi for triggered direct damage to planeswalkers. --- .../java/forge/ai/ability/DamageDealAi.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 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 56aaed39853..f207d4f61f5 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -357,7 +357,6 @@ public class DamageDealAi extends DamageAiBase { // Filter AI-specific targets if provided killables = ComputerUtil.filterAITgts(sa, ai, new CardCollection(killables), true); - Card targetCard = null; if (pl.isOpponentOf(ai) && activator.equals(ai) && !killables.isEmpty()) { return ComputerUtilCard.getBestPlaneswalkerAI(killables); } @@ -365,11 +364,7 @@ public class DamageDealAi extends DamageAiBase { if (!hPlay.isEmpty()) { if (pl.isOpponentOf(ai) && activator.equals(ai)) { return ComputerUtilCard.getBestPlaneswalkerAI(hPlay); - } else if (mandatory) { - return ComputerUtilCard.getWorstPlaneswalkerAI(hPlay); } - - return targetCard; } return null; @@ -768,7 +763,19 @@ public class DamageDealAi extends DamageAiBase { final Player opp = ComputerUtil.getOpponentFor(ai); while (sa.getTargets().getNumTargeted() < tgt.getMinTargets(sa.getHostCard(), sa)) { - // TODO: Consider targeting the planeswalker + if (tgt.canTgtPlaneswalker()) { + final Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, ai, mandatory); + if (c != null) { + sa.getTargets().add(c); + if (divided) { + tgt.addDividedAllocation(c, dmg); + break; + } + continue; + } + } + + // TODO: This currently also catches planeswalkers that can be killed (still necessary? Or can be removed?) if (tgt.canTgtCreature()) { final Card c = this.dealDamageChooseTgtC(ai, sa, dmg, noPrevention, ai, mandatory); if (c != null) { From cd1c0cc526562947c92ed59be3c8c295f4f3555b Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 11 Apr 2018 07:48:48 +0300 Subject: [PATCH 09/12] - Only process planeswalkers in dealDamageChooseTgtPW. --- forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f207d4f61f5..3cc715e5b32 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -342,7 +342,7 @@ public class DamageDealAi extends DamageAiBase { final Player activator = sa.getActivatingPlayer(); final Card source = sa.getHostCard(); final Game game = source.getGame(); - List hPlay = getTargetableCards(ai, sa, pl, tgt, activator, source, game); + List hPlay = CardLists.filter(getTargetableCards(ai, sa, pl, tgt, activator, source, game), CardPredicates.Presets.PLANEWALKERS); List killables = CardLists.filter(hPlay, new Predicate() { @Override From 115720cc53d45def69e0c6a0a91e18fd3903007b Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 11 Apr 2018 08:05:47 +0300 Subject: [PATCH 10/12] - Fixed a typo in the PLANESWALKERS card predicate preset. --- forge-ai/src/main/java/forge/ai/AiAttackController.java | 2 +- forge-ai/src/main/java/forge/ai/ComputerUtilCard.java | 4 ++-- .../src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java | 2 +- forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java | 4 ++-- forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java | 2 +- forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java | 2 +- forge-ai/src/main/java/forge/ai/ability/DrawAi.java | 2 +- forge-ai/src/main/java/forge/ai/ability/PermanentAi.java | 2 +- forge-game/src/main/java/forge/game/GameAction.java | 2 +- forge-game/src/main/java/forge/game/card/CardPredicates.java | 2 +- .../src/main/java/forge/game/combat/AttackRequirement.java | 2 +- forge-game/src/main/java/forge/game/combat/CombatUtil.java | 2 +- 12 files changed, 14 insertions(+), 14 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index f497502b85b..c9468d703cc 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -421,7 +421,7 @@ public class AiAttackController { CardCollectionView oppBattlefield = c.getController().getCardsIn(ZoneType.Battlefield); if (c.getName().equals("Heart of Kiran")) { - if (!CardLists.filter(oppBattlefield, CardPredicates.Presets.PLANEWALKERS).isEmpty()) { + if (!CardLists.filter(oppBattlefield, CardPredicates.Presets.PLANESWALKERS).isEmpty()) { // can be activated by removing a loyalty counter instead of tapping a creature continue; } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index d158414a879..68ac9a8faf8 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -96,7 +96,7 @@ public class ComputerUtilCard { * @return best Planeswalker */ public static Card getBestPlaneswalkerAI(final List list) { - List all = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS); + List all = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS); if (all.size() == 0) { return null; } @@ -110,7 +110,7 @@ public class ComputerUtilCard { * @return best Planeswalker */ public static Card getWorstPlaneswalkerAI(final List list) { - List all = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS); + List all = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS); if (all.size() == 0) { return null; } diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java index 90b11371580..6df50568446 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java @@ -112,7 +112,7 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi { // with one touch CardCollection planeswalkerList = CardLists.filter( CardLists.filterControlledBy(countersList, ai.getOpponents()), - CardPredicates.Presets.PLANEWALKERS, + CardPredicates.Presets.PLANESWALKERS, CardPredicates.hasLessCounter(CounterType.LOYALTY, amount)); if (!planeswalkerList.isEmpty()) { diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java index 4cd549a9187..ba2043efa14 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java @@ -121,7 +121,7 @@ public class CountersRemoveAi extends SpellAbilityAi { list = ai.getOpponents().getCardsIn(ZoneType.Battlefield); list = CardLists.filter(list, CardPredicates.isTargetableBy(sa)); - CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS, + CardCollection planeswalkerList = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS, CardPredicates.hasCounter(CounterType.LOYALTY, 5)); if (!planeswalkerList.isEmpty()) { @@ -169,7 +169,7 @@ public class CountersRemoveAi extends SpellAbilityAi { list = CardLists.filter(list, CardPredicates.isTargetableBy(sa)); CardCollection planeswalkerList = CardLists.filter(list, - Predicates.and(CardPredicates.Presets.PLANEWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())), + Predicates.and(CardPredicates.Presets.PLANESWALKERS, CardPredicates.isControlledByAnyOf(ai.getOpponents())), CardPredicates.hasLessCounter(CounterType.LOYALTY, amount)); if (!planeswalkerList.isEmpty()) { diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java index 738a6529ead..29920d40736 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java @@ -78,7 +78,7 @@ public abstract class DamageAiBase extends SpellAbilityAi { } // burn Planeswalkers - if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) { + if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) { return true; } 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 3cc715e5b32..65aab027ede 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -342,7 +342,7 @@ public class DamageDealAi extends DamageAiBase { final Player activator = sa.getActivatingPlayer(); final Card source = sa.getHostCard(); final Game game = source.getGame(); - List hPlay = CardLists.filter(getTargetableCards(ai, sa, pl, tgt, activator, source, game), CardPredicates.Presets.PLANEWALKERS); + List hPlay = CardLists.filter(getTargetableCards(ai, sa, pl, tgt, activator, source, game), CardPredicates.Presets.PLANESWALKERS); List killables = CardLists.filter(hPlay, new Predicate() { @Override diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java index 9923a7b92bb..0c020491679 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -180,7 +180,7 @@ public class DrawAi extends SpellAbilityAi { int numHand = ai.getCardsIn(ZoneType.Hand).size(); if ("Jace, Vryn's Prodigy".equals(sourceName) && ai.getCardsIn(ZoneType.Graveyard).size() > 3) { - return CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS, + return CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS, CardPredicates.isType("Jace")).size() <= 0; } if (source.isSpell() && ai.getCardsIn(ZoneType.Hand).contains(source)) { diff --git a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java index cc0ff7d3c41..24c989fc936 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java @@ -69,7 +69,7 @@ public class PermanentAi extends SpellAbilityAi { /* -- not used anymore after Ixalan (Planeswalkers are now legendary, not unique by subtype) -- if (card.isPlaneswalker()) { CardCollection list = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), - CardPredicates.Presets.PLANEWALKERS); + CardPredicates.Presets.PLANESWALKERS); for (String type : card.getType().getSubtypes()) { // determine // planewalker // subtype diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 9353a236261..5d7260853ea 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -1303,7 +1303,7 @@ public class GameAction { private boolean handlePlaneswalkerRule(Player p) { // get all Planeswalkers - final List list = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS); + final List list = CardLists.filter(p.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS); boolean recheck = false; //final Multimap uniqueWalkers = ArrayListMultimap.create(); // Not used as of Ixalan diff --git a/forge-game/src/main/java/forge/game/card/CardPredicates.java b/forge-game/src/main/java/forge/game/card/CardPredicates.java index ecae20bb89e..ec22b7d74d0 100644 --- a/forge-game/src/main/java/forge/game/card/CardPredicates.java +++ b/forge-game/src/main/java/forge/game/card/CardPredicates.java @@ -587,7 +587,7 @@ public final class CardPredicates { return c.isLand() && c.isSnow(); } }; - public static final Predicate PLANEWALKERS = new Predicate() { + public static final Predicate PLANESWALKERS = new Predicate() { @Override public boolean apply(final Card c) { return c.isPlaneswalker(); diff --git a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java index 2d109fa6ace..c02701595d3 100644 --- a/forge-game/src/main/java/forge/game/combat/AttackRequirement.java +++ b/forge-game/src/main/java/forge/game/combat/AttackRequirement.java @@ -77,7 +77,7 @@ public class AttackRequirement { if (c.hasKeyword("Each opponent must attack you or a planeswalker you control with at least one creature each combat if able.")) { if (attacker.getController().isOpponentOf(c.getController()) && !defenderOrPWSpecific.containsKey(c.getController())) { defenderOrPWSpecific.put(c.getController(), 1); - for (Card pw : CardLists.filter(c.getController().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) { + for (Card pw : CardLists.filter(c.getController().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) { // Add the attack alternatives that suffice (planeswalkers that can be attacked instead of the player) if (!defenderSpecificAlternatives.containsKey(c.getController())) { defenderSpecificAlternatives.put(c.getController(), Lists.newArrayList()); diff --git a/forge-game/src/main/java/forge/game/combat/CombatUtil.java b/forge-game/src/main/java/forge/game/combat/CombatUtil.java index ac16b73e584..1c3a7326463 100644 --- a/forge-game/src/main/java/forge/game/combat/CombatUtil.java +++ b/forge-game/src/main/java/forge/game/combat/CombatUtil.java @@ -62,7 +62,7 @@ public class CombatUtil { final FCollection defenders = new FCollection(); for (final Player defender : playerWhoAttacks.getOpponents()) { defenders.add(defender); - final CardCollection planeswalkers = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS); + final CardCollection planeswalkers = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS); defenders.addAll(planeswalkers); } return defenders; From c20093280c2117d72a71c87ef766fd91fedad0d3 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 11 Apr 2018 08:56:14 +0300 Subject: [PATCH 11/12] - Do not consider "indirect Planeswalker burn" when deliberately evaluating a planeswalker vs. a player target (the clause must be removed and set as default later when the planeswalker redirection rule is fully removed). --- .../src/main/java/forge/ai/ability/DamageAiBase.java | 11 +++++++++-- .../src/main/java/forge/ai/ability/DamageDealAi.java | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java index 29920d40736..ec4ade1a03c 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java @@ -44,6 +44,11 @@ public abstract class DamageAiBase extends SpellAbilityAi { } protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention) { + // TODO: once the "planeswalker redirection" rule is removed completely, just remove this code and + // remove the "burn Planeswalkers" code in the called method. + return shouldTgtP(comp, sa, d, noPrevention, false); + } + protected boolean shouldTgtP(final Player comp, final SpellAbility sa, final int d, final boolean noPrevention, final boolean noPlaneswalkerRedirection) { int restDamage = d; final Game game = comp.getGame(); Player enemy = ComputerUtil.getOpponentFor(comp); @@ -78,8 +83,10 @@ public abstract class DamageAiBase extends SpellAbilityAi { } // burn Planeswalkers - if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) { - return true; + if (!noPlaneswalkerRedirection) { + if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) { + return true; + } } if (avoidTargetP(comp, sa)) { 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 65aab027ede..c918723848b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java @@ -533,7 +533,7 @@ public class DamageDealAi extends DamageAiBase { if (tgt.canTgtPlaneswalker()) { // We can damage planeswalkers with this, consider targeting. Card c = this.dealDamageChooseTgtPW(ai, sa, dmg, noPrevention, enemy, false); - if (c != null) { + if (c != null && !this.shouldTgtP(ai, sa, dmg, noPrevention, true)) { tcs.add(c); if (divided) { final int assignedDamage = ComputerUtilCombat.getEnoughDamageToKill(c, dmg, source, false, noPrevention); From a3dc449cc96f766410584409e5ed73d6ed80c2d3 Mon Sep 17 00:00:00 2001 From: Agetian Date: Wed, 11 Apr 2018 08:58:27 +0300 Subject: [PATCH 12/12] - Added a TODO entry. --- forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java | 1 + 1 file changed, 1 insertion(+) diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java index ec4ade1a03c..62a35eaa133 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java +++ b/forge-ai/src/main/java/forge/ai/ability/DamageAiBase.java @@ -83,6 +83,7 @@ public abstract class DamageAiBase extends SpellAbilityAi { } // burn Planeswalkers + // TODO: Must be removed completely when the "planeswalker redirection" rule is removed. if (!noPlaneswalkerRedirection) { if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANESWALKERS)) { return true;