AbilityUtils: don't crash when CardTrait is null

This commit is contained in:
Hans Mackowiak
2021-03-18 13:37:05 +01:00
parent fc5557a137
commit 0d81961325
19 changed files with 57 additions and 133 deletions

View File

@@ -214,7 +214,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
return null;
}
else {
CardCollectionView chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c);
CardCollectionView chosen = ComputerUtil.chooseExileFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c, ability);
return null == chosen ? null : PaymentDecision.card(chosen);
}
}
@@ -421,7 +421,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
chosen = chosen.subList(0, c);
}
else {
chosen = ComputerUtil.choosePutToLibraryFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c);
chosen = ComputerUtil.choosePutToLibraryFrom(player, cost.getFrom(), cost.getType(), source, ability.getTargetCard(), c, ability);
}
return chosen.isEmpty() ? null : PaymentDecision.card(chosen);
}
@@ -493,7 +493,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
type = TextUtil.fastReplace(type, "+withTotalPowerGE", "");
totap = ComputerUtil.chooseTapTypeAccumulatePower(player, type, ability, !cost.canTapSource, Integer.parseInt(totalP), exclude);
} else {
totap = ComputerUtil.chooseTapType(player, type, source, !cost.canTapSource, c, exclude);
totap = ComputerUtil.chooseTapType(player, type, source, !cost.canTapSource, c, exclude, ability);
}
if (totap == null) {
@@ -536,7 +536,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
c = AbilityUtils.calculateAmount(source, cost.getAmount(), ability);
}
CardCollectionView res = ComputerUtil.chooseReturnType(player, cost.getType(), source, ability.getTargetCard(), c);
CardCollectionView res = ComputerUtil.chooseReturnType(player, cost.getType(), source, ability.getTargetCard(), c, ability);
return res.isEmpty() ? null : PaymentDecision.card(res);
}
@@ -854,7 +854,7 @@ public class AiCostDecision extends CostDecisionMakerBase {
c = AbilityUtils.calculateAmount(source, amount, ability);
}
CardCollectionView list = ComputerUtil.chooseUntapType(player, cost.getType(), source, cost.canUntapSource, c);
CardCollectionView list = ComputerUtil.chooseUntapType(player, cost.getType(), source, cost.canUntapSource, c, ability);
if (list == null) {
System.out.println("Couldn't find a valid card to untap for: " + source.getName());

View File

@@ -539,7 +539,7 @@ public class ComputerUtil {
public static CardCollection chooseSacrificeType(final Player ai, final String type, final SpellAbility ability, final Card target, final int amount) {
final Card source = ability.getHostCard();
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, null);
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, ability);
typeList = CardLists.filter(typeList, CardPredicates.canBeSacrificedBy(ability));
@@ -570,8 +570,8 @@ public class ComputerUtil {
}
public static CardCollection chooseExileFrom(final Player ai, final ZoneType zone, final String type, final Card activate,
final Card target, final int amount) {
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, null);
final Card target, final int amount, SpellAbility sa) {
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
if ((target != null) && target.getController() == ai) {
typeList.remove(target); // don't exile the card we're pumping
@@ -591,8 +591,8 @@ public class ComputerUtil {
}
public static CardCollection choosePutToLibraryFrom(final Player ai, final ZoneType zone, final String type, final Card activate,
final Card target, final int amount) {
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, null);
final Card target, final int amount, SpellAbility sa) {
CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(zone), type.split(";"), activate.getController(), activate, sa);
if ((target != null) && target.getController() == ai) {
typeList.remove(target); // don't move the card we're pumping
@@ -615,14 +615,11 @@ public class ComputerUtil {
return list;
}
public static CardCollection chooseTapType(final Player ai, final String type, final Card activate, final boolean tap, final int amount) {
return chooseTapType(ai, type, activate, tap, amount, CardCollection.EMPTY);
}
public static CardCollection chooseTapType(final Player ai, final String type, final Card activate, final boolean tap, final int amount, final CardCollectionView exclude) {
public static CardCollection chooseTapType(final Player ai, final String type, final Card activate, final boolean tap, final int amount, final CardCollectionView exclude, SpellAbility sa) {
CardCollection all = new CardCollection(ai.getCardsIn(ZoneType.Battlefield));
all.removeAll(exclude);
CardCollection typeList =
CardLists.getValidCards(all, type.split(";"), activate.getController(), activate, null);
CardLists.getValidCards(all, type.split(";"), activate.getController(), activate, sa);
// is this needed?
typeList = CardLists.filter(typeList, Presets.UNTAPPED);
@@ -695,9 +692,9 @@ public class ComputerUtil {
return tapList;
}
public static CardCollection chooseUntapType(final Player ai, final String type, final Card activate, final boolean untap, final int amount) {
public static CardCollection chooseUntapType(final Player ai, final String type, final Card activate, final boolean untap, final int amount, SpellAbility sa) {
CardCollection typeList =
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, null);
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
// is this needed?
typeList = CardLists.filter(typeList, Presets.TAPPED);
@@ -720,9 +717,9 @@ public class ComputerUtil {
return untapList;
}
public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount) {
public static CardCollection chooseReturnType(final Player ai, final String type, final Card activate, final Card target, final int amount, SpellAbility sa) {
final CardCollection typeList =
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, null);
CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), activate.getController(), activate, sa);
if ((target != null) && target.getController() == ai) {
// don't bounce the card we're pumping
typeList.remove(target);
@@ -970,7 +967,7 @@ public class ComputerUtil {
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
if (tgt != null) {
if (CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), controller, sa.getHostCard(), null).contains(card)) {
if (CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), tgt.getValidTgts(), controller, sa.getHostCard(), sa).contains(card)) {
prevented += AbilityUtils.calculateAmount(sa.getHostCard(), sa.getParam("Amount"), sa);
}
@@ -1586,9 +1583,8 @@ public class ComputerUtil {
if (threatApi == null) {
return threatened;
}
final TargetRestrictions tgt = topStack.getTargetRestrictions();
if (tgt == null) {
if (!topStack.usesTargeting()) {
if (topStack.hasParam("Defined")) {
objects = AbilityUtils.getDefinedObjects(source, topStack.getParam("Defined"), topStack);
} else if (topStack.hasParam("ValidCards")) {
@@ -1685,7 +1681,7 @@ public class ComputerUtil {
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) {
boolean canSave = ComputerUtilCombat.predictDamageTo(c, dmg - toughness, source, false) < ComputerUtilCombat.getDamageToKill(c);
if ((tgt == null && !grantIndestructible && !canSave)
if ((!topStack.usesTargeting() && !grantIndestructible && !canSave)
|| (!grantIndestructible && !grantShroud && !canSave)) {
continue;
}
@@ -1745,7 +1741,7 @@ public class ComputerUtil {
final boolean cantSave = c.getNetToughness() + toughness <= dmg
|| (!c.hasKeyword(Keyword.INDESTRUCTIBLE) && c.getShieldCount() == 0 && !grantIndestructible
&& (dmg >= toughness + ComputerUtilCombat.getDamageToKill(c)));
if (cantSave && (tgt == null || !grantShroud)) {
if (cantSave && (!topStack.usesTargeting() || !grantShroud)) {
continue;
}
}
@@ -1758,7 +1754,7 @@ public class ComputerUtil {
}
if (saviourApi == ApiType.Protection) {
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
if (!topStack.usesTargeting() || (ProtectAi.toProtectFrom(source, saviour) == null)) {
continue;
}
}
@@ -1795,13 +1791,13 @@ public class ComputerUtil {
if (saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll
|| saviorWithSubsApi == ApiType.Pump
|| saviorWithSubsApi == ApiType.PumpAll) {
if ((tgt == null && !grantIndestructible)
if ((!topStack.usesTargeting() && !grantIndestructible)
|| (!grantShroud && !grantIndestructible)) {
continue;
}
}
if (saviourApi == ApiType.Protection) {
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
if (!topStack.usesTargeting() || (ProtectAi.toProtectFrom(source, saviour) == null)) {
continue;
}
}
@@ -1830,11 +1826,11 @@ public class ComputerUtil {
if (o instanceof Card) {
final Card c = (Card) o;
// give Shroud to targeted creatures
if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) && (tgt == null || !grantShroud)) {
if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll) && (!topStack.usesTargeting() || !grantShroud)) {
continue;
}
if (saviourApi == ApiType.Protection) {
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
if (!topStack.usesTargeting() || (ProtectAi.toProtectFrom(source, saviour) == null)) {
continue;
}
}
@@ -1858,11 +1854,11 @@ public class ComputerUtil {
if (o instanceof Card) {
final Card c = (Card) o;
// give Shroud to targeted creatures
if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll && tgt == null) && !grantShroud) {
if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll && !topStack.usesTargeting()) && !grantShroud) {
continue;
}
if (saviourApi == ApiType.Protection) {
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
if (!topStack.usesTargeting() || (ProtectAi.toProtectFrom(source, saviour) == null)) {
continue;
}
}
@@ -1879,11 +1875,11 @@ public class ComputerUtil {
if (o instanceof Card) {
final Card c = (Card) o;
// give Shroud to targeted creatures
if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll && tgt == null) && !grantShroud) {
if ((saviourApi == ApiType.Pump || saviourApi == ApiType.PumpAll && !topStack.usesTargeting()) && !grantShroud) {
continue;
}
if (saviourApi == ApiType.Protection) {
if (tgt == null || (ProtectAi.toProtectFrom(source, saviour) == null)) {
if (!topStack.usesTargeting() || (ProtectAi.toProtectFrom(source, saviour) == null)) {
continue;
}
}

View File

@@ -1913,7 +1913,7 @@ public class ComputerUtilCard {
CardCollectionView list = game.getCardsIn(ZoneType.Battlefield);
list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card, null);
list = CardLists.getValidCards(list, needsToPlay.split(","), card.getController(), card, sa);
if (list.isEmpty()) {
return AiPlayDecision.MissingNeededCards;
}

View File

@@ -137,7 +137,7 @@ public class ComputerUtilCost {
* the source
* @return true, if successful
*/
public static boolean checkDiscardCost(final Player ai, final Cost cost, final Card source) {
public static boolean checkDiscardCost(final Player ai, final Cost cost, final Card source, SpellAbility sa) {
if (cost == null) {
return true;
}
@@ -152,7 +152,7 @@ public class ComputerUtilCost {
if (type.equals("CARDNAME") && source.getAbilityText().contains("Bloodrush")) {
continue;
}
final CardCollection typeList = CardLists.getValidCards(hand, type.split(","), source.getController(), source, null);
final CardCollection typeList = CardLists.getValidCards(hand, type.split(","), source.getController(), source, sa);
if (typeList.size() > ai.getMaxHandSize()) {
continue;
}
@@ -270,7 +270,7 @@ public class ComputerUtilCost {
}
final CardCollection sacList = new CardCollection();
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, null);
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
int count = 0;
while (count < amount) {
@@ -320,7 +320,7 @@ public class ComputerUtilCost {
}
final CardCollection sacList = new CardCollection();
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, null);
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), type.split(";"), source.getController(), source, sourceAbility);
int count = 0;
while (count < amount) {
@@ -641,7 +641,7 @@ public class ComputerUtilCost {
return checkLifeCost(payer, cost, source, 4, sa)
&& checkDamageCost(payer, cost, source, 4)
&& (isMine || checkSacrificeCost(payer, cost, source, sa))
&& (isMine || checkDiscardCost(payer, cost, source))
&& (isMine || checkDiscardCost(payer, cost, source, sa))
&& (!source.getName().equals("Tyrannize") || payer.getCardsIn(ZoneType.Hand).size() > 2)
&& (!source.getName().equals("Perplex") || payer.getCardsIn(ZoneType.Hand).size() < 2)
&& (!source.getName().equals("Breaking Point") || payer.getCreaturesInPlay().size() > 1)
@@ -722,7 +722,7 @@ public class ComputerUtilCost {
continue;
}
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), source.getController(), source, null);
final CardCollection typeList = CardLists.getValidCards(ai.getCardsIn(ZoneType.Battlefield), part.getType().split(";"), source.getController(), source, sa);
int count = 0;
while (count < val) {

View File

@@ -127,7 +127,7 @@ public abstract class SpellAbilityAi {
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa)) {

View File

@@ -288,7 +288,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
for (final CostPart part : abCost.getCostParts()) {
if (part instanceof CostDiscard) {
CostDiscard cd = (CostDiscard) part;

View File

@@ -51,7 +51,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
boolean aiLogicAllowsDiscard = sa.hasParam("AILogic") && sa.getParam("AILogic").startsWith("DiscardAll");
if (!aiLogicAllowsDiscard) {

View File

@@ -10,7 +10,6 @@ import com.google.common.collect.Lists;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameObject;
@@ -47,20 +46,7 @@ public class ChooseSourceAi extends SpellAbilityAi {
final Card source = sa.getHostCard();
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) {
if (!willPayCosts(ai, sa, abCost, source)) {
return false;
}
}

View File

@@ -52,7 +52,7 @@ public class CountersPutAllAi extends SpellAbilityAi {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
return false;
}

View File

@@ -245,7 +245,7 @@ public class DamageDealAi extends DamageAiBase {
return false;
}
if ("DiscardLands".equals(sa.getParam("AILogic")) && !ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
if ("DiscardLands".equals(sa.getParam("AILogic")) && !ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
return false;
}

View File

@@ -6,7 +6,6 @@ import java.util.List;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.GameObject;
@@ -37,20 +36,7 @@ public class DamagePreventAi extends SpellAbilityAi {
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) {
if (!willPayCosts(ai, sa, cost, hostCard)) {
return false;
}

View File

@@ -1,7 +1,5 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.cost.Cost;
@@ -22,19 +20,7 @@ public class DamagePreventAllAi extends SpellAbilityAi {
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) {
if (!willPayCosts(ai, sa, cost, hostCard)) {
return false;
}

View File

@@ -32,24 +32,9 @@ public class DiscardAi extends SpellAbilityAi {
final Cost abCost = sa.getPayCosts();
final String aiLogic = sa.getParamOrDefault("AILogic", "");
if (abCost != null) {
// AI currently disabled for these costs
if (!ComputerUtilCost.checkSacrificeCost(ai, abCost, source, sa)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, abCost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(abCost, source, sa)) {
return false;
}
// temporarily disabled until better AI
if (!willPayCosts(ai, sa, abCost, source)) {
return false;
}
if ("Chandra, Flamecaller".equals(sourceName)) {

View File

@@ -100,7 +100,7 @@ public class DrawAi extends SpellAbilityAi {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source,sa)) {
AiCostDecision aiDecisions = new AiCostDecision(ai, sa);
for (final CostPart part : cost.getCostParts()) {
if (part instanceof CostDiscard) {

View File

@@ -43,14 +43,14 @@ public class LifeGainAi extends SpellAbilityAi {
if (!lifeCritical) {
// return super.willPayCosts(ai, sa, cost, source);
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa,false)) {
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, source, sa, false)) {
return false;
}
if (!ComputerUtilCost.checkLifeCost(ai, cost, source, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source)) {
if (!ComputerUtilCost.checkDiscardCost(ai, cost, source, sa)) {
return false;
}

View File

@@ -1,7 +1,5 @@
package forge.ai.ability;
import forge.ai.ComputerUtilCost;
import forge.ai.SpellAbilityAi;
import forge.game.card.Card;
import forge.game.cost.Cost;
@@ -21,19 +19,7 @@ public class ProtectAllAi extends SpellAbilityAi {
final Cost cost = sa.getPayCosts();
// temporarily disabled until better AI
if (!ComputerUtilCost.checkLifeCost(ai, cost, hostCard, 4, sa)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, cost, hostCard)) {
return false;
}
if (!ComputerUtilCost.checkSacrificeCost(ai, cost, hostCard, sa)) {
return false;
}
if (!ComputerUtilCost.checkRemoveCounterCost(cost, hostCard, sa)) {
if (!willPayCosts(ai, sa, cost, hostCard)) {
return false;
}

View File

@@ -48,10 +48,9 @@ public class TapAi extends TapAiBase {
final Card source = sa.getHostCard();
final Cost abCost = sa.getPayCosts();
if (abCost != null) {
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source)) {
return false;
}
if (!ComputerUtilCost.checkDiscardCost(ai, abCost, source, sa)) {
return false;
}
if (!sa.usesTargeting()) {

View File

@@ -52,7 +52,7 @@ public class UntapAi extends SpellAbilityAi {
return false;
}
return ComputerUtilCost.checkDiscardCost(ai, cost, sa.getHostCard());
return ComputerUtilCost.checkDiscardCost(ai, cost, sa.getHostCard(), sa);
}
@Override

View File

@@ -1606,7 +1606,7 @@ public class AbilityUtils {
final String s2 = AbilityUtils.applyAbilityTextChangeEffects(s, ctb);
final String[] l = s2.split("/");
final String expr = CardFactoryUtil.extractOperators(s2);
final Player player = ctb instanceof SpellAbility ? ((SpellAbility)ctb).getActivatingPlayer() : ctb.getHostCard().getController();
final Player player = ctb == null ? null : ctb instanceof SpellAbility ? ((SpellAbility)ctb).getActivatingPlayer() : ctb.getHostCard().getController();
final String[] sq;
sq = l[0].split("\\.");