From c2d44efbc50fca25cb5c4425276ef81b16709514 Mon Sep 17 00:00:00 2001 From: TRT <> Date: Tue, 19 Oct 2021 11:20:44 +0200 Subject: [PATCH 1/2] Fix Muse Vessel + Hexproof --- .../src/main/java/forge/ai/ability/ActivateAbilityAi.java | 7 +++++-- forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java | 3 +++ forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java | 3 +-- forge-gui/res/cardsfolder/k/kyoki_sanitys_eclipse.txt | 3 +-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java b/forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java index f3aad3445a5..b87c5b00db1 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ActivateAbilityAi.java @@ -35,7 +35,11 @@ public class ActivateAbilityAi extends SpellAbilityAi { } } else { sa.resetTargets(); - sa.getTargets().add(opp); + if (sa.canTarget(opp)) { + sa.getTargets().add(opp); + } else { + return false; + } } boolean randomReturn = MyRandom.getRandom().nextFloat() <= Math.pow(.6667, sa.getActivationsThisTurn()); @@ -57,7 +61,6 @@ public class ActivateAbilityAi extends SpellAbilityAi { return defined.contains(opp); } - } else { sa.resetTargets(); sa.getTargets().add(opp); 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 2acb2426457..9db0a1417b0 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java @@ -344,6 +344,9 @@ public class ChangeZoneAi extends SpellAbilityAi { sa.resetTargets(); sa.getTargets().add(ai); } + if (!sa.isTargetNumberValid()) { + return false; + } pDefined = sa.getTargets().getTargetPlayers(); } else { if (sa.hasParam("DefinedPlayer")) { diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java index 23d7c5263da..7f40c4f40fd 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -107,8 +107,7 @@ public class CountersPutAi extends CountersAi { final Card source = sa.getHostCard(); if (sa.isOutlast()) { - if (ph.is(PhaseType.MAIN2, ai)) { // applicable to non-attackers - // only + if (ph.is(PhaseType.MAIN2, ai)) { // applicable to non-attackers only float chance = 0.8f; if (ComputerUtilCard.doesSpecifiedCreatureBlock(ai, source)) { return false; diff --git a/forge-gui/res/cardsfolder/k/kyoki_sanitys_eclipse.txt b/forge-gui/res/cardsfolder/k/kyoki_sanitys_eclipse.txt index 8d372445edc..674aa3c1588 100644 --- a/forge-gui/res/cardsfolder/k/kyoki_sanitys_eclipse.txt +++ b/forge-gui/res/cardsfolder/k/kyoki_sanitys_eclipse.txt @@ -3,8 +3,7 @@ ManaCost:4 B B Types:Legendary Creature Demon Spirit PT:6/4 T:Mode$ SpellCast | ValidCard$ Spirit,Arcane | ValidActivatingPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigExile | TriggerDescription$ Whenever you cast a Spirit or Arcane spell, target opponent exiles a card from their hand. -#This needs Defined$ Opponent because ValidTgts$ Opponent lets Kyoki's controller select the card to be exiled -SVar:TrigExile:DB$ChangeZone | Origin$ Hand | Destination$ Exile | ChangeType$ Card | ValidTgts$ Opponent | Chooser$ Targeted | TgtPrompt$ Select target opponent | ChangeNum$ 1 +SVar:TrigExile:DB$ChangeZone | Origin$ Hand | Destination$ Exile | ChangeType$ Card | ValidTgts$ Opponent | Chooser$ Targeted | TgtPrompt$ Select target opponent | ChangeNum$ 1 | Mandatory$ True AI:RemoveDeck:Random DeckHints:Type$Spirit|Arcane SVar:Picture:http://www.wizards.com/global/images/magic/general/kyoki_sanitys_eclipse.jpg From 386c30a8aed12e07008c72a82be4978de620e7db Mon Sep 17 00:00:00 2001 From: TRT <> Date: Tue, 19 Oct 2021 12:38:36 +0200 Subject: [PATCH 2/2] Fix NPE --- .../forge/ai/ability/ChangeZoneAllAi.java | 55 ++++++++++++------- .../java/forge/ai/ability/CountersPutAi.java | 2 +- .../main/java/forge/ai/ability/MillAi.java | 5 +- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java index be6d2cf4280..11e96df2fbd 100644 --- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAllAi.java @@ -1,6 +1,7 @@ package forge.ai.ability; import java.util.Collections; +import java.util.List; import com.google.common.base.Predicates; import com.google.common.collect.Iterables; @@ -138,13 +139,17 @@ public class ChangeZoneAllAi extends SpellAbilityAi { return true; } else { // search targetable Opponents - final Iterable oppList = Iterables.filter(ai.getOpponents(), PlayerPredicates.isTargetableBy(sa)); + final List oppList = Lists.newArrayList(Iterables.filter(ai.getOpponents(), PlayerPredicates.isTargetableBy(sa))); + + if (oppList.isEmpty()) { + return false; + } // get the one with the most handsize - Player oppTarget = Collections.max(Lists.newArrayList(oppList), PlayerPredicates.compareByZoneSize(origin)); + Player oppTarget = Collections.max(oppList, PlayerPredicates.compareByZoneSize(origin)); // set the target - if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) { + if (!oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) { sa.resetTargets(); sa.getTargets().add(oppTarget); } else { @@ -152,24 +157,26 @@ public class ChangeZoneAllAi extends SpellAbilityAi { } } } else if (origin.equals(ZoneType.Battlefield)) { - // this statement is assuming the AI is trying to use this spell - // offensively - // if the AI is using it defensively, then something else needs to - // occur + // this statement is assuming the AI is trying to use this spell offensively + // if the AI is using it defensively, then something else needs to occur // if only creatures are affected evaluate both lists and pass only // if human creatures are more valuable if (sa.usesTargeting()) { // search targetable Opponents - final Iterable oppList = Iterables.filter(ai.getOpponents(), - PlayerPredicates.isTargetableBy(sa)); + final List oppList = Lists.newArrayList(Iterables.filter(ai.getOpponents(), + PlayerPredicates.isTargetableBy(sa))); + + if (oppList.isEmpty()) { + return false; + } // get the one with the most in graveyard // zone is visible so evaluate which would be hurt the most - Player oppTarget = Collections.max(Lists.newArrayList(oppList), + Player oppTarget = Collections.max(oppList, PlayerPredicates.compareByZoneSize(origin)); // set the target - if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) { + if (oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) { sa.resetTargets(); sa.getTargets().add(oppTarget); } else { @@ -234,7 +241,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi { AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa)); // set the target - if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) { + if (!oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) { sa.resetTargets(); sa.getTargets().add(oppTarget); } else { @@ -380,15 +387,19 @@ public class ChangeZoneAllAi extends SpellAbilityAi { if (origin.equals(ZoneType.Hand) || origin.equals(ZoneType.Library)) { if (sa.usesTargeting()) { // search targetable Opponents - final Iterable oppList = Iterables.filter(ai.getOpponents(), - PlayerPredicates.isTargetableBy(sa)); + final List oppList = Lists.newArrayList(Iterables.filter(ai.getOpponents(), + PlayerPredicates.isTargetableBy(sa))); + + if (oppList.isEmpty()) { + return false; + } // get the one with the most handsize - Player oppTarget = Collections.max(Lists.newArrayList(oppList), + Player oppTarget = Collections.max(oppList, PlayerPredicates.compareByZoneSize(origin)); // set the target - if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) { + if (!oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) { sa.resetTargets(); sa.getTargets().add(oppTarget); } else { @@ -418,16 +429,20 @@ public class ChangeZoneAllAi extends SpellAbilityAi { } else if (origin.equals(ZoneType.Graveyard)) { if (sa.usesTargeting()) { // search targetable Opponents - final Iterable oppList = Iterables.filter(ai.getOpponents(), - PlayerPredicates.isTargetableBy(sa)); + final List oppList = Lists.newArrayList(Iterables.filter(ai.getOpponents(), + PlayerPredicates.isTargetableBy(sa))); + + if (oppList.isEmpty()) { + return false; + } // get the one with the most in graveyard // zone is visible so evaluate which would be hurt the most - Player oppTarget = Collections.max(Lists.newArrayList(oppList), + Player oppTarget = Collections.max(oppList, AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa)); // set the target - if (oppTarget != null && !oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) { + if (!oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) { sa.resetTargets(); sa.getTargets().add(oppTarget); } else { diff --git a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java index 7f40c4f40fd..9c8570e9cef 100644 --- a/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/CountersPutAi.java @@ -787,7 +787,7 @@ public class CountersPutAi extends CountersAi { List playerList = Lists.newArrayList(Iterables.filter( sa.getTargetRestrictions().getAllCandidates(sa, true, true), Player.class)); - if (playerList.isEmpty() && mandatory) { + if (playerList.isEmpty()) { return false; } diff --git a/forge-ai/src/main/java/forge/ai/ability/MillAi.java b/forge-ai/src/main/java/forge/ai/ability/MillAi.java index 08d784c303c..f6802577a72 100644 --- a/forge-ai/src/main/java/forge/ai/ability/MillAi.java +++ b/forge-ai/src/main/java/forge/ai/ability/MillAi.java @@ -117,8 +117,7 @@ public class MillAi extends SpellAbilityAi { // if it would mill none, try other one if (numCards <= 0) { - if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z"))) - { + if ((sa.getParam("NumCards").equals("X") || sa.getParam("NumCards").equals("Z"))) { if (source.getSVar("X").startsWith("Count$xPaid")) { // Spell is PayX based } else if (source.getSVar("X").startsWith("Remembered$ChromaSource")) { @@ -136,7 +135,7 @@ public class MillAi extends SpellAbilityAi { continue; } - // if that player can be miled, select this one. + // if that player can be milled, select this one. if (numCards >= pLibrary.size()) { sa.getTargets().add(o); return true;