From 57eeb5e0bfe86ef08fec441a3ecb4f15d473333e Mon Sep 17 00:00:00 2001 From: tool4ever Date: Wed, 11 May 2022 18:45:36 +0200 Subject: [PATCH] Fix NPE with Crystalline Giant (#321) * Fix Crystalline Giant NPE * Clean up Co-authored-by: tool4EvEr --- .../java/forge/ai/AiAttackController.java | 8 +- .../ai/ability/CountersPutOrRemoveAi.java | 76 +++++++++---------- .../forge/ai/ability/CountersRemoveAi.java | 3 - .../main/java/forge/ai/ability/DrawAi.java | 2 +- .../java/forge/ai/ability/PermanentAi.java | 4 +- .../src/main/java/forge/game/GameAction.java | 1 - .../ability/effects/CountersPutEffect.java | 1 + .../res/cardsfolder/o/ogre_head_helm.txt | 4 +- 8 files changed, 49 insertions(+), 50 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java index 85ebfab6223..64424620d11 100644 --- a/forge-ai/src/main/java/forge/ai/AiAttackController.java +++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java @@ -168,10 +168,14 @@ public class AiAttackController { public static Player choosePreferredDefenderPlayer(Player ai) { Player defender = ai.getWeakestOpponent(); //Concentrate on opponent within easy kill range - // TODO connect with evaluateBoardPosition and only fall back to random when no player is the biggest threat by a fair margin + // TODO for multiplayer combat avoid players with cantLose or (if not playing infect) cantLoseForZeroOrLessLife and !canLoseLife + + if (defender.getLife() > 8) { + // TODO connect with evaluateBoardPosition and only fall back to random when no player is the biggest threat by a fair margin - if (defender.getLife() > 8) { //Otherwise choose a random opponent to ensure no ganging up on players // TODO should we cache the random for each turn? some functions like shouldPumpCard base their decisions on the assumption who will be attacked + + //Otherwise choose a random opponent to ensure no ganging up on players return Aggregates.random(ai.getOpponents()); } return defender; 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 f9fa3764eae..79f59f18334 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutOrRemoveAi.java @@ -197,53 +197,51 @@ public class CountersPutOrRemoveAi extends SpellAbilityAi { */ @Override public CounterType chooseCounterType(List options, SpellAbility sa, Map params) { - if (options.size() > 1) { - final Player ai = sa.getActivatingPlayer(); - final Game game = ai.getGame(); + final Player ai = sa.getActivatingPlayer(); + final Game game = ai.getGame(); - boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule); + boolean noLegendary = game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule); - Card tgt = (Card) params.get("Target"); + Card tgt = (Card) params.get("Target"); - // planeswalker has high priority for loyalty counters - if (tgt.isPlaneswalker() && options.contains(CounterType.get(CounterEnumType.LOYALTY))) { - return CounterType.get(CounterEnumType.LOYALTY); - } + // planeswalker has high priority for loyalty counters + if (tgt.isPlaneswalker() && options.contains(CounterType.get(CounterEnumType.LOYALTY))) { + return CounterType.get(CounterEnumType.LOYALTY); + } - if (tgt.getController().isOpponentOf(ai)) { - // creatures with BaseToughness below or equal zero might be - // killed if their counters are removed - if (tgt.isCreature() && tgt.getBaseToughness() <= 0) { - if (options.contains(CounterType.get(CounterEnumType.P1P1))) { - return CounterType.get(CounterEnumType.P1P1); - } else if (options.contains(CounterType.get(CounterEnumType.M1M1))) { - return CounterType.get(CounterEnumType.M1M1); - } - } - - // fallback logic, select positive counter to remove it - for (final CounterType type : options) { - if (!ComputerUtil.isNegativeCounter(type, tgt)) { - return type; - } - } - } else { - // this counters are treat first to be removed - if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) { - if (!ai.isCardInPlay("Marit Lage") || noLegendary) { - return CounterType.get(CounterEnumType.ICE); - } - } else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) { + if (tgt.getController().isOpponentOf(ai)) { + // creatures with BaseToughness below or equal zero might be + // killed if their counters are removed + if (tgt.isCreature() && tgt.getBaseToughness() <= 0) { + if (options.contains(CounterType.get(CounterEnumType.P1P1))) { return CounterType.get(CounterEnumType.P1P1); - } else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.get(CounterEnumType.M1M1))) { + } else if (options.contains(CounterType.get(CounterEnumType.M1M1))) { return CounterType.get(CounterEnumType.M1M1); } + } - // fallback logic, select positive counter to add more - for (final CounterType type : options) { - if (!ComputerUtil.isNegativeCounter(type, tgt)) { - return type; - } + // fallback logic, select positive counter to remove it + for (final CounterType type : options) { + if (!ComputerUtil.isNegativeCounter(type, tgt)) { + return type; + } + } + } else { + // this counters are treat first to be removed + if ("Dark Depths".equals(tgt.getName()) && options.contains(CounterType.get(CounterEnumType.ICE))) { + if (!ai.isCardInPlay("Marit Lage") || noLegendary) { + return CounterType.get(CounterEnumType.ICE); + } + } else if (tgt.hasKeyword(Keyword.UNDYING) && options.contains(CounterType.get(CounterEnumType.P1P1))) { + return CounterType.get(CounterEnumType.P1P1); + } else if (tgt.hasKeyword(Keyword.PERSIST) && options.contains(CounterType.get(CounterEnumType.M1M1))) { + return CounterType.get(CounterEnumType.M1M1); + } + + // fallback logic, select positive counter to add more + for (final CounterType type : options) { + if (!ComputerUtil.isNegativeCounter(type, tgt)) { + return type; } } } 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 1421489b691..92f636973d3 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersRemoveAi.java @@ -407,9 +407,6 @@ public class CountersRemoveAi extends SpellAbilityAi { */ @Override public CounterType chooseCounterType(List options, SpellAbility sa, Map params) { - if (options.size() <= 1) { - return super.chooseCounterType(options, sa, params); - } Player ai = sa.getActivatingPlayer(); GameEntity target = (GameEntity) params.get("Target"); 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 eec736c93e6..630c6f71c64 100644 --- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java @@ -508,7 +508,7 @@ public class DrawAi extends SpellAbilityAi { } if (numCards >= computerLibrarySize - 3) { - if (ai.isCardInPlay("Laboratory Maniac")) { + if (ai.isCardInPlay("Laboratory Maniac") && !ai.cantWin()) { return true; } // Don't deck yourself 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 bbcfac625d2..56b41a80289 100644 --- a/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/PermanentAi.java @@ -56,8 +56,8 @@ public class PermanentAi extends SpellAbilityAi { // check on legendary if (card.getType().isLegendary() - && !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) { - if (ai.isCardInPlay(card.getName())) { + && !game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLegendRule)) { // TODO check the risk we'd lose the effect with bad timing + if (ai.isCardInPlay(card.getName())) { // TODO check for keyword if (!card.hasSVar("AILegendaryException")) { // AiPlayDecision.WouldDestroyLegend return false; diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 591cd5f4082..897fbae53a4 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -1706,7 +1706,6 @@ public class GameAction { List yamazaki = CardLists.getKeyword(a, "Legend rule doesn't apply to CARDNAME."); a.removeAll(yamazaki); - Multimap uniqueLegends = ArrayListMultimap.create(); for (Card c : a) { if (!c.isFaceDown()) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java index f48f09ebee4..ddece52248d 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/CountersPutEffect.java @@ -432,6 +432,7 @@ public class CountersPutEffect extends SpellAbilityEffect { } if (sa.hasParam("CounterTypePerDefined") || sa.hasParam("UniqueType")) { counterType = chooseTypeFromList(sa, sa.getParam("CounterType"), obj, pc); + if (counterType == null) continue; } if (obj instanceof Card) { diff --git a/forge-gui/res/cardsfolder/o/ogre_head_helm.txt b/forge-gui/res/cardsfolder/o/ogre_head_helm.txt index 0e18242971f..6229ea553fa 100644 --- a/forge-gui/res/cardsfolder/o/ogre_head_helm.txt +++ b/forge-gui/res/cardsfolder/o/ogre_head_helm.txt @@ -4,8 +4,8 @@ Types:Artifact Creature Equipment Ogre PT:2/2 K:Reconfigure:3 S:Mode$ Continuous | Affected$ Creature.EquippedBy | AddPower$ 2 | AddToughness$ 2 | Description$ Equipped creature gets +2/+2. -T:Mode$ DamageDone | ValidSource$ Card.Self | CombatDamage$ True | Execute$ TrigDiscard1 | TriggerDescription$ Whenever CARDNAME or equipped creature deals combat damage to a player, you may sacrifice it. If you do, discard your hand, then draw three cards. -T:Mode$ DamageDone | ValidSource$ Creature.EquippedBy | CombatDamage$ True | Execute$ TrigDiscard2 | Secondary$ True | TriggerDescription$ Whenever CARDNAME or equipped creature deals combat damage to a player, you may sacrifice it. If you do, discard your hand, then draw three cards. +T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDiscard1 | TriggerDescription$ Whenever CARDNAME or equipped creature deals combat damage to a player, you may sacrifice it. If you do, discard your hand, then draw three cards. +T:Mode$ DamageDone | ValidSource$ Creature.EquippedBy | ValidTarget$ Player | CombatDamage$ True | Execute$ TrigDiscard2 | Secondary$ True | TriggerDescription$ Whenever CARDNAME or equipped creature deals combat damage to a player, you may sacrifice it. If you do, discard your hand, then draw three cards. SVar:TrigDiscard1:AB$ Discard | Cost$ Sac<1/CARDNAME> | Mode$ Hand | SubAbility$ DBDraw SVar:TrigDiscard2:AB$ Discard | Cost$ Sac<1/Creature.EquippedBy/equipped creature> | Mode$ Hand | SubAbility$ DBDraw SVar:DBDraw:DB$ Draw | NumCards$ 3