From f4db360265f5df6c9e1a7fc726a1f899234d5749 Mon Sep 17 00:00:00 2001 From: Agetian Date: Fri, 26 Oct 2018 17:16:12 +0300 Subject: [PATCH] - Prevent NPEs caused by the AI when testing for the new style AI hints from within AI methods on Card objects, which is dangerous because Card.getRules() is not guaranteed to be non-null, and may indeed be null for objects such as tokens (especially noticeable in Momir Basic and MoJhoSto). - Currently relegated 99% of AI calls to those getAIHints tests to a wrapper method which checks for a non-null getRules. - The card predicate has to test for non-null directly to avoid adding an unnecessary dependency on the AI module (the alternative would be to add the wrapper methods to the Card object, but that'll clutter it even more and the AI hints belong to the AI side of things, not the card itself). --- forge-ai/src/main/java/forge/ai/AiController.java | 2 +- forge-ai/src/main/java/forge/ai/ComputerUtilCard.java | 9 +++++++++ .../src/main/java/forge/ai/ability/ChangeZoneAi.java | 2 +- .../src/main/java/forge/ai/ability/ControlGainAi.java | 2 +- .../src/main/java/forge/ai/ability/PowerExchangeAi.java | 2 +- .../src/main/java/forge/game/card/CardPredicates.java | 5 +++-- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java index 2314c4867bb..64f14a4c955 100644 --- a/forge-ai/src/main/java/forge/ai/AiController.java +++ b/forge-ai/src/main/java/forge/ai/AiController.java @@ -1705,7 +1705,7 @@ public class AiController { if (!useSimulation) { for (Entry ds : myDeck) { for (Entry cp : ds.getValue()) { - if (cp.getKey().getRules().getAiHints().getRemAIDecks()) + if (cp.getKey().getRules().getAiHints().getRemAIDecks()) result.add(cp.getKey()); } } diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java index 115568e4ce4..008da7ec703 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtilCard.java @@ -1844,4 +1844,13 @@ public class ComputerUtilCard { return AiPlayDecision.WillPlay; } + + // Determine if the AI has an AI:RemoveDeck:All or an AI:RemoveDeck:Random hint specified. + // Includes a NPE guard on getRules() which might otherwise be tripped on some cards (e.g. tokens). + public static boolean isCardRemAIDeck(final Card card) { + return card.getRules() != null && card.getRules().getAiHints().getRemAIDecks(); + } + public static boolean isCardRemRandomDeck(final Card card) { + return card.getRules() != null && card.getRules().getAiHints().getRemRandomDecks(); + } } diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java index 47c408d8c79..7d7a60528fc 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -1463,7 +1463,7 @@ public class ChangeZoneAi extends SpellAbilityAi { fetchList = CardLists.filter(fetchList, new Predicate() { @Override public boolean apply(final Card c) { - if (c.getRules().getAiHints().getRemAIDecks() || c.getRules().getAiHints().getRemRandomDecks()) { + if (ComputerUtilCard.isCardRemAIDeck(c) || ComputerUtilCard.isCardRemRandomDeck(c)) { return false; } return true; diff --git a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java index a6631ad51bb..eb6e3ed5d9b 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java @@ -171,7 +171,7 @@ public class ControlGainAi extends SpellAbilityAi { } // do not take control on something it doesn't know how to use - return !c.getRules().getAiHints().getRemAIDecks(); + return !ComputerUtilCard.isCardRemAIDeck(c); } }); diff --git a/forge-ai/src/main/java/forge/ai/ability/PowerExchangeAi.java b/forge-ai/src/main/java/forge/ai/ability/PowerExchangeAi.java index 028e234f365..6dd2e5aa767 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PowerExchangeAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PowerExchangeAi.java @@ -37,7 +37,7 @@ public class PowerExchangeAi extends SpellAbilityAi { list = CardLists.filter(list, new Predicate() { @Override public boolean apply(final Card c) { - return !c.getRules().getAiHints().getRemAIDecks() && c.canBeTargetedBy(sa); + return !ComputerUtilCard.isCardRemAIDeck(c) && c.canBeTargetedBy(sa); } }); CardLists.sortByPowerAsc(list); 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 4809a42da8d..c6180228a3d 100644 --- a/forge-game/src/main/java/forge/game/card/CardPredicates.java +++ b/forge-game/src/main/java/forge/game/card/CardPredicates.java @@ -416,8 +416,9 @@ public final class CardPredicates { public static final Predicate isRemAIDeck() { return new Predicate() { @Override - public boolean apply(final Card c) { - return c.getRules().getAiHints().getRemAIDecks(); + public boolean apply(final Card c) + { + return c.getRules() != null && c.getRules().getAiHints().getRemAIDecks(); } }; }