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 ccd53683d25..99b42a343f7 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -96,14 +96,28 @@ public class ComputerUtilCard { * @return best Planeswalker */ public static Card getBestPlaneswalkerAI(final List list) { - List all = CardLists.filter(list, CardPredicates.Presets.PLANEWALKERS); - if (all.size() == 0) { + List all = CardLists.filter(list, CardPredicates.Presets.PLANESWALKERS); + if (all.isEmpty()) { return null; } // no AI logic, just return most expensive 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.PLANESWALKERS); + if (all.isEmpty()) { + 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/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..b252db592b1 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,7 +83,9 @@ public abstract class DamageAiBase extends SpellAbilityAi { } // burn Planeswalkers - if (Iterables.any(enemy.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.PLANEWALKERS)) { + // TODO: Must be removed completely when the "planeswalker redirection" rule is removed. + if (!noPlaneswalkerRedirection + && 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 9aaf676ebb6..04210b7d832 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 = CardLists.filter(getTargetableCards(ai, sa, pl, tgt, activator, source, game), CardPredicates.Presets.PLANESWALKERS); List killables = CardLists.filter(hPlay, new Predicate() { @Override @@ -338,6 +320,77 @@ 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 = CardLists.filter(getTargetableCards(ai, sa, pl, tgt, activator, source, game), CardPredicates.Presets.PLANESWALKERS); + + 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); + + if (pl.isOpponentOf(ai) && activator.equals(ai) && !killables.isEmpty()) { + return ComputerUtilCard.getBestPlaneswalkerAI(killables); + } + + if (!hPlay.isEmpty() && pl.isOpponentOf(ai) && activator.equals(ai)) { + return ComputerUtilCard.getBestPlaneswalkerAI(hPlay); + } + + 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 +528,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 && !this.shouldTgtP(ai, sa, dmg, noPrevention, true)) { + 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 +562,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) { @@ -687,7 +761,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) { 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; diff --git a/forge-gui/src/main/java/forge/limited/CardThemedDeckBuilder.java b/forge-gui/src/main/java/forge/limited/CardThemedDeckBuilder.java index e40c65f378c..6dbf7bc116c 100644 --- a/forge-gui/src/main/java/forge/limited/CardThemedDeckBuilder.java +++ b/forge-gui/src/main/java/forge/limited/CardThemedDeckBuilder.java @@ -506,8 +506,8 @@ public class CardThemedDeckBuilder extends DeckGeneratorBase { &&!card.getRules().getManaCost().isPureGeneric() && colors.containsAllColorsFrom(card.getRules().getColorIdentity().getColor()) && !deckList.contains(card) - && (keyCard!=null && !keyCard.equals(card)) - && (secondKeyCard!=null && !secondKeyCard.equals(card)) + && (keyCard == null || !keyCard.equals(card)) + && (secondKeyCard==null || !secondKeyCard.equals(card)) &&!card.getRules().getAiHints().getRemAIDecks() &&!card.getRules().getAiHints().getRemRandomDecks() &&!card.getRules().getMainPart().getType().isLand();