mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-16 10:48:00 +00:00
Merge branch 'aifix' into 'master'
Fix missing triggers See merge request core-developers/forge!5876
This commit is contained in:
@@ -1871,7 +1871,7 @@ public class ComputerUtilCard {
|
|||||||
return oppCards;
|
return oppCards;
|
||||||
}
|
}
|
||||||
|
|
||||||
CardCollection aiCreats = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
CardCollection aiCreats = ai.getCreaturesInPlay();
|
||||||
if (temporary) {
|
if (temporary) {
|
||||||
// Pump effects that add "CARDNAME can't attack" and similar things. Only do it if something is untapped.
|
// Pump effects that add "CARDNAME can't attack" and similar things. Only do it if something is untapped.
|
||||||
oppCards = CardLists.filter(oppCards, CardPredicates.Presets.UNTAPPED);
|
oppCards = CardLists.filter(oppCards, CardPredicates.Presets.UNTAPPED);
|
||||||
|
|||||||
@@ -162,21 +162,21 @@ public abstract class SpellAbilityAi {
|
|||||||
*/
|
*/
|
||||||
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
protected boolean checkApiLogic(final Player ai, final SpellAbility sa) {
|
||||||
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
if (ComputerUtil.preventRunAwayActivations(sa)) {
|
||||||
return false; // prevent infinite loop
|
return false; // prevent infinite loop
|
||||||
}
|
}
|
||||||
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
return MyRandom.getRandom().nextFloat() < .8f; // random success
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
public final boolean doTriggerAI(final Player aiPlayer, final SpellAbility sa, final boolean mandatory) {
|
||||||
|
// this evaluation order is currently intentional as it does more stuff that helps avoiding some crashes
|
||||||
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
|
if (!ComputerUtilCost.canPayCost(sa, aiPlayer) && !mandatory) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a mandatory SpellAbility with targeting but without candidates,
|
// a mandatory SpellAbility with targeting but without candidates,
|
||||||
// does not need to go any deeper
|
// does not need to go any deeper
|
||||||
if (sa.usesTargeting() && mandatory && !sa.isTargetNumberValid()
|
if (sa.usesTargeting() && mandatory && !sa.getTargetRestrictions().hasCandidates(sa)) {
|
||||||
&& !sa.getTargetRestrictions().hasCandidates(sa)) {
|
return sa.isTargetNumberValid();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
|
return doTriggerNoCostWithSubs(aiPlayer, sa, mandatory);
|
||||||
|
|||||||
@@ -1699,19 +1699,21 @@ public class ChangeZoneAi extends SpellAbilityAi {
|
|||||||
if (card.isToken()) {
|
if (card.isToken()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (card.isCreature() && ComputerUtilCard.isUselessCreature(decider, card)) {
|
if (card.isCreature() && ComputerUtilCard.isUselessCreature(decider, card)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (card.isEquipped()) {
|
}
|
||||||
|
if (card.isEquipped()) {
|
||||||
return false;
|
return false;
|
||||||
} else if (card.isEnchanted()) {
|
}
|
||||||
|
if (card.isEnchanted()) {
|
||||||
for (Card enc : card.getEnchantedBy()) {
|
for (Card enc : card.getEnchantedBy()) {
|
||||||
if (enc.getOwner().isOpponentOf(decider)) {
|
if (enc.getOwner().isOpponentOf(decider)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} else if (card.hasCounters()) {
|
}
|
||||||
|
if (card.hasCounters()) {
|
||||||
if (card.isPlaneswalker()) {
|
if (card.isPlaneswalker()) {
|
||||||
int maxLoyaltyToConsider = 2;
|
int maxLoyaltyToConsider = 2;
|
||||||
int loyaltyDiff = 2;
|
int loyaltyDiff = 2;
|
||||||
|
|||||||
@@ -392,15 +392,19 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
PlayerPredicates.isTargetableBy(sa)));
|
PlayerPredicates.isTargetableBy(sa)));
|
||||||
|
|
||||||
if (oppList.isEmpty()) {
|
if (oppList.isEmpty()) {
|
||||||
|
if (mandatory && !sa.isTargetNumberValid() && sa.canTarget(ai)) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(ai);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the one with the most handsize
|
// get the one with the most handsize
|
||||||
Player oppTarget = Collections.max(oppList,
|
Player oppTarget = Collections.max(oppList, PlayerPredicates.compareByZoneSize(origin));
|
||||||
PlayerPredicates.compareByZoneSize(origin));
|
|
||||||
|
|
||||||
// set the target
|
// set the target
|
||||||
if (!oppTarget.getCardsIn(ZoneType.Hand).isEmpty()) {
|
if (!oppTarget.getCardsIn(ZoneType.Hand).isEmpty() || mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(oppTarget);
|
sa.getTargets().add(oppTarget);
|
||||||
} else {
|
} else {
|
||||||
@@ -434,7 +438,12 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
PlayerPredicates.isTargetableBy(sa)));
|
PlayerPredicates.isTargetableBy(sa)));
|
||||||
|
|
||||||
if (oppList.isEmpty()) {
|
if (oppList.isEmpty()) {
|
||||||
return false;
|
if (mandatory && !sa.isTargetNumberValid() && sa.canTarget(ai)) {
|
||||||
|
sa.resetTargets();
|
||||||
|
sa.getTargets().add(ai);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return sa.isTargetNumberValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the one with the most in graveyard
|
// get the one with the most in graveyard
|
||||||
@@ -443,7 +452,7 @@ public class ChangeZoneAllAi extends SpellAbilityAi {
|
|||||||
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
AiPlayerPredicates.compareByZoneValue(sa.getParam("ChangeType"), origin, sa));
|
||||||
|
|
||||||
// set the target
|
// set the target
|
||||||
if (!oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty()) {
|
if (!oppTarget.getCardsIn(ZoneType.Graveyard).isEmpty() || mandatory) {
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(oppTarget);
|
sa.getTargets().add(oppTarget);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public class ChooseTypeAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
protected boolean doTriggerAINoCost(Player ai, SpellAbility sa, boolean mandatory) {
|
||||||
boolean isCurse = sa.hasParam("IsCurse");
|
boolean isCurse = sa.isCurse();
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
final List<Player> oppList = Lists.newArrayList(Iterables.filter(
|
final List<Player> oppList = Lists.newArrayList(Iterables.filter(
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public class ClashAi extends SpellAbilityAi {
|
|||||||
if ("Creature".equals(valid)) {
|
if ("Creature".equals(valid)) {
|
||||||
// Springjack Knight
|
// Springjack Knight
|
||||||
// TODO: Whirlpool Whelm also uses creature targeting but it's trickier to support
|
// TODO: Whirlpool Whelm also uses creature targeting but it's trickier to support
|
||||||
CardCollectionView aiCreats = CardLists.filter(ai.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
CardCollectionView aiCreats = ai.getCreaturesInPlay();
|
||||||
CardCollectionView oppCreats = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
CardCollectionView oppCreats = CardLists.filter(ai.getOpponents().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
||||||
|
|
||||||
Card tgt = aiCreats.isEmpty() ? ComputerUtilCard.getWorstCreatureAI(oppCreats) : ComputerUtilCard.getBestCreatureAI(aiCreats);
|
Card tgt = aiCreats.isEmpty() ? ComputerUtilCard.getWorstCreatureAI(oppCreats) : ComputerUtilCard.getBestCreatureAI(aiCreats);
|
||||||
|
|||||||
@@ -76,8 +76,8 @@ public abstract class DamageAiBase extends SpellAbilityAi {
|
|||||||
Card hostcard = sa.getHostCard();
|
Card hostcard = sa.getHostCard();
|
||||||
for (Trigger trig : hostcard.getTriggers()) {
|
for (Trigger trig : hostcard.getTriggers()) {
|
||||||
if (trig.getMode() == TriggerType.DamageDone) {
|
if (trig.getMode() == TriggerType.DamageDone) {
|
||||||
if (("Opponent".equals(trig.getParam("ValidTarget")))
|
if ("Opponent".equals(trig.getParam("ValidTarget"))
|
||||||
&& (!"True".equals(trig.getParam("CombatDamage")))) {
|
&& !"True".equals(trig.getParam("CombatDamage"))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -448,7 +448,8 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
|
|
||||||
// We can hurt a planeswalker, so rank the one which is the best target
|
// We can hurt a planeswalker, so rank the one which is the best target
|
||||||
if (!hPlay.isEmpty() && pl.isOpponentOf(ai) && activator.equals(ai)) {
|
if (!hPlay.isEmpty() && pl.isOpponentOf(ai) && activator.equals(ai)) {
|
||||||
return ComputerUtilCard.getBestPlaneswalkerToDamage(hPlay);
|
Card pw = ComputerUtilCard.getBestPlaneswalkerToDamage(hPlay);
|
||||||
|
return pw == null && mandatory ? hPlay.get(0) : pw;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -713,7 +714,7 @@ public class DamageDealAi extends DamageAiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (freePing && sa.canTarget(enemy) && (!avoidTargetP(ai, sa))) {
|
if (freePing && sa.canTarget(enemy) && !avoidTargetP(ai, sa)) {
|
||||||
tcs.add(enemy);
|
tcs.add(enemy);
|
||||||
if (divided) {
|
if (divided) {
|
||||||
sa.addDividedAllocation(enemy, dmg);
|
sa.addDividedAllocation(enemy, dmg);
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Card c = ComputerUtilCard.getBestAI(preferred);
|
Card c = ComputerUtilCard.getBestAI(preferred);
|
||||||
@@ -380,7 +380,7 @@ public class DestroyAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (sa.canAddMoreTarget()) {
|
while (!sa.isMinTargetChosen()) {
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -130,8 +130,7 @@ public class DigUntilAi extends SpellAbilityAi {
|
|||||||
if ("OathOfDruids".equals(logic)) {
|
if ("OathOfDruids".equals(logic)) {
|
||||||
final List<Card> creaturesInLibrary =
|
final List<Card> creaturesInLibrary =
|
||||||
CardLists.filter(player.getCardsIn(ZoneType.Library), CardPredicates.Presets.CREATURES);
|
CardLists.filter(player.getCardsIn(ZoneType.Library), CardPredicates.Presets.CREATURES);
|
||||||
final List<Card> creaturesInBattlefield =
|
final List<Card> creaturesInBattlefield = player.getCreaturesInPlay();
|
||||||
CardLists.filter(player.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
|
||||||
// if there are at least 3 creatures in library,
|
// if there are at least 3 creatures in library,
|
||||||
// or none in play with one in library, oath
|
// or none in play with one in library, oath
|
||||||
return creaturesInLibrary.size() > 2
|
return creaturesInLibrary.size() > 2
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public class FightAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fighter1List.isEmpty()) {
|
if (fighter1List.isEmpty()) {
|
||||||
return true; // FIXME: shouldn't this return "false" if nothing found?
|
return false;
|
||||||
}
|
}
|
||||||
Card fighter1 = fighter1List.get(0);
|
Card fighter1 = fighter1List.get(0);
|
||||||
for (Card humanCreature : humCreatures) {
|
for (Card humanCreature : humCreatures) {
|
||||||
|
|||||||
@@ -122,9 +122,9 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// special cases when amount can't be calculated without targeting first
|
// special cases when amount can't be calculated without targeting first
|
||||||
if (amount == 0 && opponent != null && "TargetedPlayer$StartingLife/HalfDown".equals(source.getSVar(amountStr))) {
|
if (amount == 0 && "TargetedPlayer$StartingLife/HalfDown".equals(source.getSVar(amountStr))) {
|
||||||
// e.g. Torgaar, Famine Incarnate
|
// e.g. Torgaar, Famine Incarnate
|
||||||
return doHalfStartingLifeLogic(ai, opponent, sa);
|
return doHalfStartingLifeLogic(ai, opponent, sa) || mandatory;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceName.equals("Eternity Vessel")
|
if (sourceName.equals("Eternity Vessel")
|
||||||
@@ -160,9 +160,9 @@ public class LifeSetAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
private boolean doHalfStartingLifeLogic(Player ai, Player opponent, SpellAbility sa) {
|
private boolean doHalfStartingLifeLogic(Player ai, Player opponent, SpellAbility sa) {
|
||||||
int aiAmount = ai.getStartingLife() / 2;
|
int aiAmount = ai.getStartingLife() / 2;
|
||||||
int oppAmount = opponent.getStartingLife() / 2;
|
int oppAmount = opponent == null ? 0 : opponent.getStartingLife() / 2;
|
||||||
int aiLife = ai.getLife();
|
int aiLife = ai.getLife();
|
||||||
int oppLife = opponent.getLife();
|
int oppLife = opponent == null ? 0 : opponent.getLife();
|
||||||
|
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import forge.ai.SpellAbilityAi;
|
|||||||
import forge.card.ColorSet;
|
import forge.card.ColorSet;
|
||||||
import forge.card.MagicColor;
|
import forge.card.MagicColor;
|
||||||
import forge.card.mana.ManaCost;
|
import forge.card.mana.ManaCost;
|
||||||
|
import forge.game.GlobalRuleChange;
|
||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardCollection;
|
import forge.game.card.CardCollection;
|
||||||
@@ -80,7 +81,7 @@ public class ManaEffectAi extends SpellAbilityAi {
|
|||||||
if (logic.startsWith("ManaRitual")) {
|
if (logic.startsWith("ManaRitual")) {
|
||||||
return ph.is(PhaseType.MAIN2, ai) || ph.is(PhaseType.MAIN1, ai);
|
return ph.is(PhaseType.MAIN2, ai) || ph.is(PhaseType.MAIN1, ai);
|
||||||
} else if ("AtOppEOT".equals(logic)) {
|
} else if ("AtOppEOT".equals(logic)) {
|
||||||
return ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
|
return !ai.getGame().getStaticEffects().getGlobalRuleChange(GlobalRuleChange.manaBurn) && ph.is(PhaseType.END_OF_TURN) && ph.getNextTurn() == ai;
|
||||||
}
|
}
|
||||||
return super.checkPhaseRestrictions(ai, sa, ph, logic);
|
return super.checkPhaseRestrictions(ai, sa, ph, logic);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,13 +146,13 @@ public class MillAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
// can't target opponent?
|
// can't target opponent?
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
if (mandatory && sa.canTarget(ai)) {
|
if (mandatory && !sa.isTargetNumberValid() && sa.canTarget(ai)) {
|
||||||
sa.getTargets().add(ai);
|
sa.getTargets().add(ai);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// TODO Obscure case when you know what your top card is so you might?
|
// TODO Obscure case when you know what your top card is so you might?
|
||||||
// want to mill yourself here
|
// want to mill yourself here
|
||||||
return false;
|
return sa.isTargetNumberValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
// select Player which would cause the most damage
|
// select Player which would cause the most damage
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.util.Map;
|
|||||||
|
|
||||||
import com.google.common.base.Predicate;
|
import com.google.common.base.Predicate;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
|
|
||||||
import forge.ai.AiCardMemory;
|
import forge.ai.AiCardMemory;
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
@@ -15,14 +14,12 @@ import forge.game.Game;
|
|||||||
import forge.game.ability.AbilityUtils;
|
import forge.game.ability.AbilityUtils;
|
||||||
import forge.game.card.Card;
|
import forge.game.card.Card;
|
||||||
import forge.game.card.CardLists;
|
import forge.game.card.CardLists;
|
||||||
import forge.game.card.CardPredicates;
|
|
||||||
import forge.game.combat.Combat;
|
import forge.game.combat.Combat;
|
||||||
import forge.game.combat.CombatUtil;
|
import forge.game.combat.CombatUtil;
|
||||||
import forge.game.keyword.Keyword;
|
import forge.game.keyword.Keyword;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.game.spellability.SpellAbility;
|
import forge.game.spellability.SpellAbility;
|
||||||
import forge.game.zone.ZoneType;
|
|
||||||
|
|
||||||
public class MustBlockAi extends SpellAbilityAi {
|
public class MustBlockAi extends SpellAbilityAi {
|
||||||
|
|
||||||
@@ -76,7 +73,7 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Card attacker = null;
|
Card attacker = source;
|
||||||
if (sa.hasParam("DefinedAttacker")) {
|
if (sa.hasParam("DefinedAttacker")) {
|
||||||
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedAttacker"), sa);
|
final List<Card> cards = AbilityUtils.getDefinedCards(source, sa.getParam("DefinedAttacker"), sa);
|
||||||
if (cards.isEmpty()) {
|
if (cards.isEmpty()) {
|
||||||
@@ -86,18 +83,12 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
attacker = cards.get(0);
|
attacker = cards.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attacker == null) {
|
|
||||||
attacker = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Card definedAttacker = attacker;
|
|
||||||
|
|
||||||
boolean chance = false;
|
boolean chance = false;
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
final List<Card> list = determineGoodBlockers(definedAttacker, ai, ai.getWeakestOpponent(), sa, true,true);
|
final List<Card> list = determineGoodBlockers(attacker, ai, ai.getWeakestOpponent(), sa, true, true);
|
||||||
if (list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return false;
|
return sa.isTargetNumberValid();
|
||||||
}
|
}
|
||||||
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
final Card blocker = ComputerUtilCard.getBestCreatureAI(list);
|
||||||
if (blocker == null) {
|
if (blocker == null) {
|
||||||
@@ -160,8 +151,7 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
|
|
||||||
private List<Card> determineGoodBlockers(final Card attacker, final Player ai, Player defender, SpellAbility sa,
|
private List<Card> determineGoodBlockers(final Card attacker, final Player ai, Player defender, SpellAbility sa,
|
||||||
final boolean onlyLethal, final boolean testTapped) {
|
final boolean onlyLethal, final boolean testTapped) {
|
||||||
List<Card> list = Lists.newArrayList();
|
List<Card> list = defender.getCreaturesInPlay();
|
||||||
list = CardLists.filter(defender.getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
list = CardLists.getTargetableCards(list, sa);
|
list = CardLists.getTargetableCards(list, sa);
|
||||||
@@ -187,7 +177,7 @@ public class MustBlockAi extends SpellAbilityAi {
|
|||||||
List<Card> better = determineBlockerFromList(attacker, ai, options, sa, false, false);
|
List<Card> better = determineBlockerFromList(attacker, ai, options, sa, false, false);
|
||||||
|
|
||||||
if (!better.isEmpty()) {
|
if (!better.isEmpty()) {
|
||||||
return Iterables.getFirst(options, null);
|
return Iterables.getFirst(better, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Iterables.getFirst(options, null);
|
return Iterables.getFirst(options, null);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public class PhasesAi extends SpellAbilityAi {
|
|||||||
return true;
|
return true;
|
||||||
} else if (mandatory) {
|
} else if (mandatory) {
|
||||||
// not enough preferred targets, but mandatory so keep going:
|
// not enough preferred targets, but mandatory so keep going:
|
||||||
return phasesUnpreferredTargeting(aiPlayer.getGame(), sa, mandatory);
|
return sa.isTargetNumberValid() || phasesUnpreferredTargeting(aiPlayer.getGame(), sa, mandatory);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package forge.ai.ability;
|
package forge.ai.ability;
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import forge.ai.ComputerUtilCard;
|
import forge.ai.ComputerUtilCard;
|
||||||
@@ -57,13 +56,13 @@ public class SacrificeAi extends SpellAbilityAi {
|
|||||||
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa, boolean mandatory) {
|
private boolean sacrificeTgtAI(final Player ai, final SpellAbility sa, boolean mandatory) {
|
||||||
final Card source = sa.getHostCard();
|
final Card source = sa.getHostCard();
|
||||||
final boolean destroy = sa.hasParam("Destroy");
|
final boolean destroy = sa.hasParam("Destroy");
|
||||||
final PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
|
||||||
final Player opp = Collections.max(targetableOpps, PlayerPredicates.compareByLife());
|
|
||||||
|
|
||||||
if (sa.usesTargeting()) {
|
if (sa.usesTargeting()) {
|
||||||
if (opp == null) {
|
final PlayerCollection targetableOpps = ai.getOpponents().filter(PlayerPredicates.isTargetableBy(sa));
|
||||||
|
if (targetableOpps.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
final Player opp = targetableOpps.max(PlayerPredicates.compareByLife());
|
||||||
sa.resetTargets();
|
sa.resetTargets();
|
||||||
sa.getTargets().add(opp);
|
sa.getTargets().add(opp);
|
||||||
if (mandatory) {
|
if (mandatory) {
|
||||||
|
|||||||
@@ -451,9 +451,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
|||||||
sb.append(sa.getParam("AlternativeDestinationMessage"));
|
sb.append(sa.getParam("AlternativeDestinationMessage"));
|
||||||
Player alterDecider = player;
|
Player alterDecider = player;
|
||||||
if (sa.hasParam("AlternativeDecider")) {
|
if (sa.hasParam("AlternativeDecider")) {
|
||||||
alterDecider = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("AlternativeDecider"), sa).get(0);
|
PlayerCollection deciders = AbilityUtils.getDefinedPlayers(hostCard, sa.getParam("AlternativeDecider"), sa);
|
||||||
|
alterDecider = deciders.isEmpty() ? null : deciders.get(0);
|
||||||
}
|
}
|
||||||
if (!alterDecider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneToAltDestination, sb.toString())) {
|
if (alterDecider != null && !alterDecider.getController().confirmAction(sa, PlayerActionConfirmMode.ChangeZoneToAltDestination, sb.toString())) {
|
||||||
destination = ZoneType.smartValueOf(sa.getParam("DestinationAlternative"));
|
destination = ZoneType.smartValueOf(sa.getParam("DestinationAlternative"));
|
||||||
altDest = true;
|
altDest = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ public class AttackConstraints {
|
|||||||
|
|
||||||
public AttackConstraints(final Combat combat) {
|
public AttackConstraints(final Combat combat) {
|
||||||
final Game game = combat.getAttackingPlayer().getGame();
|
final Game game = combat.getAttackingPlayer().getGame();
|
||||||
possibleAttackers = CardLists.filter(combat.getAttackingPlayer().getCardsIn(ZoneType.Battlefield), CardPredicates.Presets.CREATURES);
|
possibleAttackers = combat.getAttackingPlayer().getCreaturesInPlay();
|
||||||
possibleDefenders = combat.getDefenders();
|
possibleDefenders = combat.getDefenders();
|
||||||
globalRestrictions = GlobalAttackRestrictions.getGlobalRestrictions(combat.getAttackingPlayer(), possibleDefenders);
|
globalRestrictions = GlobalAttackRestrictions.getGlobalRestrictions(combat.getAttackingPlayer(), possibleDefenders);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ ManaCost:2
|
|||||||
Types:Artifact
|
Types:Artifact
|
||||||
A:AB$ Pump | Cost$ T Discard<1/Card> | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN Unblockable | SpellDescription$ Up to one target creature can't be blocked this turn.
|
A:AB$ Pump | Cost$ T Discard<1/Card> | TargetMin$ 0 | TargetMax$ 1 | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN Unblockable | SpellDescription$ Up to one target creature can't be blocked this turn.
|
||||||
T:Mode$ Untaps | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever CARDNAME becomes untapped, you may pay {2}. If you do, draw a card.
|
T:Mode$ Untaps | ValidCard$ Card.Self | TriggerZones$ Battlefield | Execute$ TrigDraw | TriggerDescription$ Whenever CARDNAME becomes untapped, you may pay {2}. If you do, draw a card.
|
||||||
SVar:TrigDraw:AB$Draw | Cost$ 2 | Defined$ You | NumCards$ 1
|
SVar:TrigDraw:AB$ Draw | Cost$ 2 | Defined$ You | NumCards$ 1
|
||||||
SVar:Picture:http://www.wizards.com/global/images/magic/general/key_to_the_city.jpg
|
SVar:Picture:http://www.wizards.com/global/images/magic/general/key_to_the_city.jpg
|
||||||
Oracle:{T}, Discard a card: Up to one target creature can't be blocked this turn.\nWhenever Key to the City becomes untapped, you may pay {2}. If you do, draw a card.
|
Oracle:{T}, Discard a card: Up to one target creature can't be blocked this turn.\nWhenever Key to the City becomes untapped, you may pay {2}. If you do, draw a card.
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ PT:3/3
|
|||||||
K:Flash
|
K:Flash
|
||||||
K:Flying
|
K:Flying
|
||||||
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigTuck | TriggerDescription$ When CARDNAME enters the battlefield, choose up to one target creature spell or planeswalker spell. Its owner puts it on the top or bottom of their library.
|
T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigTuck | TriggerDescription$ When CARDNAME enters the battlefield, choose up to one target creature spell or planeswalker spell. Its owner puts it on the top or bottom of their library.
|
||||||
SVar:TrigTuck:DB$ ChangeZone | ValidTgts$ Card.inZoneStack+Creature,Card.inZoneStack+Planeswalker | TgtZone$ Stack | TgtPrompt$ Select up to one target creature spell or planeswalker spell | AlternativeDecider$ TargetedController | Origin$ Stack | Fizzle$ True | Destination$ Library | LibraryPosition$ 0 | DestinationAlternative$ Library | LibraryPositionAlternative$ -1 | AlternativeDestinationMessage$ Would you like to put the card on the top of your library (and not on the bottom)? | SpellDescription$ Choose up to one target creature spell or planeswalker spell. Its owner puts it on the top or bottom of their library.
|
SVar:TrigTuck:DB$ ChangeZone | ValidTgts$ Card.inZoneStack+Creature,Card.inZoneStack+Planeswalker | TgtZone$ Stack | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select up to one target creature spell or planeswalker spell | AlternativeDecider$ TargetedController | Origin$ Stack | Fizzle$ True | Destination$ Library | LibraryPosition$ 0 | DestinationAlternative$ Library | LibraryPositionAlternative$ -1 | AlternativeDestinationMessage$ Would you like to put the card on the top of your library (and not on the bottom)? | SpellDescription$ Choose up to one target creature spell or planeswalker spell. Its owner puts it on the top or bottom of their library.
|
||||||
K:Evoke:ExileFromHand<1/Card.Blue+Other/blue card>
|
K:Evoke:ExileFromHand<1/Card.Blue+Other/blue card>
|
||||||
Oracle:Flash\nFlying\nWhen Subtlety enters the battlefield, choose up to one target creature spell or planeswalker spell. Its owner puts it on the top or bottom of their library.\nEvoke—Exile a blue card from your hand.
|
Oracle:Flash\nFlying\nWhen Subtlety enters the battlefield, choose up to one target creature spell or planeswalker spell. Its owner puts it on the top or bottom of their library.\nEvoke—Exile a blue card from your hand.
|
||||||
|
|||||||
Reference in New Issue
Block a user