it = memorySet.iterator();
+
+ while (it.hasNext()) {
+ Card c = it.next();
+ if (c.getName().equals(cardName) && c.getOwner().equals(owner)) {
+ return forgetCard(c, set);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines if the memory set is empty.
+ * @param set the memory set to inspect.
+ * @return true, if the given memory set contains no remembered cards.
+ */
+ public boolean isMemorySetEmpty(MemorySet set) {
+ return getMemorySet(set).isEmpty();
+ }
+
+ /**
+ * Clears the "remembered attackers" memory set stored in this card memory for the given player.
+ */
+ public void clearRememberedAttackers() {
+ getMemorySet(MemorySet.MANDATORY_ATTACKERS).clear();
+ }
+
+ /**
+ * Clears the "remembered mana sources" memory set stored in this card memory for the given player.
+ */
+ public void clearRememberedManaSources() {
+ getMemorySet(MemorySet.HELD_MANA_SOURCES).clear();
+ }
+
+ /**
+ * Clears the "remembered revealed cards" memory set stored in this card memory for the given player.
+ */
+ //public void clearRememberedRevealedCards() {
+ // getMemorySet(MemorySet.REVEALED_CARDS).clear();
+ //}
+
+ /**
+ * Clears all memory sets stored in this card memory for the given player.
+ */
+ public void clearAllRemembered() {
+ clearRememberedAttackers();
+ clearRememberedManaSources();
+ //clearRememberedRevealedCards();
+ }
+}
\ No newline at end of file
diff --git a/forge-ai/src/main/java/forge/ai/AiController.java b/forge-ai/src/main/java/forge/ai/AiController.java
index cafb8ba1704..91d9d17fd65 100644
--- a/forge-ai/src/main/java/forge/ai/AiController.java
+++ b/forge-ai/src/main/java/forge/ai/AiController.java
@@ -60,6 +60,7 @@ import forge.game.combat.Combat;
import forge.game.cost.Cost;
import forge.game.cost.CostDiscard;
import forge.game.cost.CostPart;
+import forge.game.mana.ManaCostBeingPaid;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
@@ -94,6 +95,7 @@ public class AiController {
private final Player player;
private final Game game;
+ private final AiCardMemory memory;
public boolean bCheatShuffle;
public boolean canCheatShuffle() {
@@ -114,6 +116,10 @@ public class AiController {
return player;
}
+ public AiCardMemory getCardMemory() {
+ return memory;
+ }
+
/**
*
* Constructor for ComputerAI_General.
@@ -122,6 +128,7 @@ public class AiController {
public AiController(final Player computerPlayer, final Game game0) {
player = computerPlayer;
game = game0;
+ memory = new AiCardMemory();
}
/**
@@ -628,6 +635,33 @@ public class AiController {
return bestSA;
} // playCounterSpell()
+ public SpellAbility predictSpellToCastInMain2(ApiType exceptSA) {
+ final List cards = getAvailableCards();
+
+ ArrayList all = getSpellAbilities(cards);
+ Collections.sort(all, saComparator); // put best spells first
+
+ for (final SpellAbility sa : getOriginalAndAltCostAbilities(all)) {
+ if (sa.getApi() == ApiType.Counter || sa.getApi() == exceptSA) {
+ continue;
+ }
+ sa.setActivatingPlayer(player);
+ if (sa.canPlay() && !ComputerUtil.castPermanentInMain1(player, sa) && sa.getHostCard() != null && !sa.getHostCard().getType().contains("Land") && ComputerUtilCost.canPayCost(sa, player)) {
+ return sa;
+ }
+ }
+
+ return null;
+ }
+
+ public void reserveManaSourcesForMain2(SpellAbility sa) {
+ ManaCostBeingPaid cost = ComputerUtilMana.calculateManaCost(sa, true, 0);
+ ArrayList manaSources = ComputerUtilMana.getManaSourcesToPayCost(cost, sa, player);
+ for (Card c : manaSources) {
+ ((PlayerControllerAi)player.getController()).getAi().getCardMemory().rememberCard(c, AiCardMemory.MemorySet.HELD_MANA_SOURCES);
+ }
+ }
+
// This is for playing spells regularly (no Cascade/Ripple etc.)
private AiPlayDecision canPlayAndPayFor(final SpellAbility sa) {
if (!sa.canPlay()) {
@@ -1146,18 +1180,18 @@ public class AiController {
SpellAbility counter = chooseCounterSpell(getPlayableCounters(cards));
if( counter != null ) return counter;
- SpellAbility counterETB = chooseSpellAbilyToPlay(this.getPossibleETBCounters(), false);
+ SpellAbility counterETB = chooseSpellAbilityToPlay(this.getPossibleETBCounters(), false);
if( counterETB != null )
return counterETB;
}
- SpellAbility result = chooseSpellAbilyToPlay(getSpellAbilities(cards), true);
+ SpellAbility result = chooseSpellAbilityToPlay(getSpellAbilities(cards), true);
if( null == result)
return null;
return result;
}
- private SpellAbility chooseSpellAbilyToPlay(final ArrayList all, boolean skipCounter) {
+ private SpellAbility chooseSpellAbilityToPlay(final ArrayList all, boolean skipCounter) {
if ( all == null || all.isEmpty() )
return null;
diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
index b7534ab9923..9a60ae3eed3 100644
--- a/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
+++ b/forge-ai/src/main/java/forge/ai/ComputerUtilMana.java
@@ -21,6 +21,7 @@ import forge.game.mana.Mana;
import forge.game.mana.ManaCostAdjustment;
import forge.game.mana.ManaCostBeingPaid;
import forge.game.mana.ManaPool;
+import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.replacement.ReplacementEffect;
import forge.game.spellability.AbilityManaPart;
@@ -230,6 +231,113 @@ public class ComputerUtilMana {
}
+ public static ArrayList getManaSourcesToPayCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai) {
+ ArrayList manaSources = new ArrayList();
+
+ adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
+ List manaSpentToPay = new ArrayList();
+
+ List unpaidShards = cost.getUnpaidShards();
+ Collections.sort(unpaidShards); // most difficult shards must come first
+ for (ManaCostShard part : unpaidShards) {
+ if (part != ManaCostShard.X) {
+ if (cost.isPaid()) {
+ continue;
+ }
+
+ // get a mana of this type from floating, bail if none available
+ final Mana mana = getMana(ai, part, sa, cost.getSourceRestriction());
+ if (mana != null) {
+ if (ai.getManaPool().tryPayCostWithMana(sa, cost, mana)) {
+ manaSpentToPay.add(0, mana);
+ }
+ }
+ }
+ }
+
+ if (cost.isPaid()) {
+ // refund any mana taken from mana pool when test
+ refundMana(manaSpentToPay, ai, sa);
+
+ handleOfferingsAI(sa, true, cost.isPaid());
+ return manaSources;
+ }
+
+ // arrange all mana abilities by color produced.
+ final ArrayListMultimap manaAbilityMap = ComputerUtilMana.groupSourcesByManaColor(ai, true);
+ if (manaAbilityMap.isEmpty()) {
+ refundMana(manaSpentToPay, ai, sa);
+
+ handleOfferingsAI(sa, true, cost.isPaid());
+ return manaSources;
+ }
+
+ // select which abilities may be used for each shard
+ ArrayListMultimap sourcesForShards = ComputerUtilMana.groupAndOrderToPayShards(ai, manaAbilityMap, cost);
+
+ sortManaAbilities(sourcesForShards);
+
+ ManaCostShard toPay = null;
+ // Loop over mana needed
+ while (!cost.isPaid()) {
+ toPay = getNextShardToPay(cost);
+
+ Collection saList = sourcesForShards.get(toPay);
+ SpellAbility saPayment = null;
+ if (saList != null) {
+ for (final SpellAbility ma : saList) {
+ if (ma.getHostCard() == sa.getHostCard()) {
+ continue;
+ }
+
+ final String typeRes = cost.getSourceRestriction();
+ if (StringUtils.isNotBlank(typeRes) && !ma.getHostCard().isType(typeRes)) {
+ continue;
+ }
+
+ if (canPayShardWithSpellAbility(toPay, ai, ma, sa, true)) {
+ saPayment = ma;
+ manaSources.add(saPayment.getHostCard());
+ break;
+ }
+ }
+ } else {
+ break;
+ }
+
+ if (saPayment == null) {
+ if (!toPay.isPhyrexian() || !ai.canPayLife(2)) {
+ break; // cannot pay
+ }
+
+ cost.payPhyrexian();
+ continue;
+ }
+
+ setExpressColorChoice(sa, ai, cost, toPay, saPayment);
+
+ String manaProduced = toPay.isSnow() ? "S" : GameActionUtil.generatedMana(saPayment);
+ manaProduced = AbilityManaPart.applyManaReplacement(saPayment, manaProduced);
+ //System.out.println(manaProduced);
+ payMultipleMana(cost, manaProduced, ai);
+
+ // remove from available lists
+ Iterator itSa = sourcesForShards.values().iterator();
+ while (itSa.hasNext()) {
+ SpellAbility srcSa = itSa.next();
+ if (srcSa.getHostCard().equals(saPayment.getHostCard())) {
+ itSa.remove();
+ }
+ }
+ }
+
+ handleOfferingsAI(sa, true, cost.isPaid());
+
+ refundMana(manaSpentToPay, ai, sa);
+
+ return manaSources;
+ } // getManaSourcesToPayCost()
+
private static boolean payManaCost(final ManaCostBeingPaid cost, final SpellAbility sa, final Player ai, final boolean test, boolean checkPlayable) {
adjustManaCostToAvoidNegEffects(cost, sa.getHostCard(), ai);
List manaSpentToPay = test ? new ArrayList() : sa.getPayingMana();
@@ -255,9 +363,9 @@ public class ComputerUtilMana {
if (cost.isPaid()) {
// refund any mana taken from mana pool when test
- if(test)
+ if (test) {
refundMana(manaSpentToPay, ai, sa);
-
+ }
handleOfferingsAI(sa, test, cost.isPaid());
return true;
}
@@ -307,7 +415,8 @@ public class ComputerUtilMana {
break;
}
}
- } else {
+ }
+ else {
break;
}
@@ -381,9 +490,9 @@ public class ComputerUtilMana {
}
}
- if (test)
+ if (test) {
refundMana(manaSpentToPay, ai, sa);
-
+ }
sa.getHostCard().setColorsPaid(cost.getColorsPaid());
// if (sa instanceof Spell_Permanent) // should probably add this
sa.getHostCard().setSunburstValue(cost.getSunburst());
@@ -485,8 +594,9 @@ public class ComputerUtilMana {
ManaCostShard toPay, SpellAbility saPayment) {
AbilityManaPart m = saPayment.getManaPart();
- if (m.isComboMana())
+ if (m.isComboMana()) {
getComboManaChoice(ai, saPayment, sa, cost);
+ }
else if (saPayment.getApi() == ApiType.ManaReflected) {
System.out.println("Evaluate reflected mana of: " + saPayment.getHostCard());
Set reflected = CardUtil.getReflectableManaColors(saPayment);
@@ -497,7 +607,8 @@ public class ComputerUtilMana {
return;
}
}
- } else if (m.isAnyMana()) {
+ }
+ else if (m.isAnyMana()) {
byte colorChoice = 0;
if (toPay.isOr2Colorless())
colorChoice = toPay.getColorMask();
@@ -516,7 +627,13 @@ public class ComputerUtilMana {
private static boolean canPayShardWithSpellAbility(ManaCostShard toPay, Player ai, SpellAbility ma, SpellAbility sa, boolean checkCosts) {
final Card sourceCard = ma.getHostCard();
- if (toPay.isSnow() && !sourceCard.isSnow()) { return false; }
+ if (isManaSourceReserved(ai, sourceCard, sa)) {
+ return false;
+ }
+
+ if (toPay.isSnow() && !sourceCard.isSnow()) {
+ return false;
+ }
AbilityManaPart m = ma.getManaPart();
if (!m.meetsManaRestrictions(sa)) {
@@ -530,7 +647,8 @@ public class ComputerUtilMana {
if (!CostPayment.canPayAdditionalCosts(ma.getPayCosts(), ma)) {
return false;
}
- } else if (sourceCard.isTapped()) {
+ }
+ else if (sourceCard.isTapped()) {
return false;
}
}
@@ -541,8 +659,9 @@ public class ComputerUtilMana {
return true;
}
return false;
+ }
- } else if (ma.getApi() == ApiType.ManaReflected) {
+ if (ma.getApi() == ApiType.ManaReflected) {
Set reflected = CardUtil.getReflectableManaColors(ma);
for (byte c : MagicColor.WUBRG) {
@@ -556,10 +675,41 @@ public class ComputerUtilMana {
return true;
}
+ // isManaSourceReserved returns true if sourceCard is reserved as a mana source for payment
+ // for the future spell to be cast in Mana 2. However, if "sa" (the spell ability that is
+ // being considered for casting) is high priority, then mana source reservation will be
+ // ignored.
+ private static boolean isManaSourceReserved(Player ai, Card sourceCard, SpellAbility sa) {
+ if (sa == null) {
+ return false;
+ }
+ if (!(ai.getController() instanceof PlayerControllerAi)) {
+ return false;
+ }
+
+ // If the spell ability being considered is high priority, ignore mana source reservations.
+ // TODO: currently the only "low priority" spell abilities are +X/+X pumps.
+ if (!(sa.getApi() == ApiType.Pump && (sa.hasParam("NumAtk") || (sa.hasParam("NumDef"))))) {
+ return false;
+ }
+
+ PhaseType curPhase = ai.getGame().getPhaseHandler().getPhase();
+ if (curPhase == PhaseType.MAIN2 || curPhase == PhaseType.CLEANUP) {
+ ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().clearRememberedManaSources();
+ }
+ else {
+ if (((PlayerControllerAi)ai.getController()).getAi().getCardMemory().isRememberedCard(sourceCard, AiCardMemory.MemorySet.HELD_MANA_SOURCES)) {
+ // This mana source is held elsewhere for a Main Phase 2 spell.
+ return true;
+ }
+ }
+ return false;
+ }
+
private static ManaCostShard getNextShardToPay(ManaCostBeingPaid cost) {
// mind the priorities
- // * Pay mono-colored first,
+ // * Pay mono-colored first,curPhase == PhaseType.CLEANUP
// * Pay 2/C with matching colors
// * pay hybrids
// * pay phyrexian, keep mana for colorless
diff --git a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
index 9fa5c4a62a2..25853e79bac 100644
--- a/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/AttachAi.java
@@ -1085,6 +1085,11 @@ public class AttachAi extends SpellAbilityAi {
c = attachAIHighestEvaluationPreference(prefList);
}
+ // Consider exceptional cases which break the normal evaluation rules
+ if (!isUsefulAttachAction(c, sa)) {
+ return null;
+ }
+
return c;
}
@@ -1266,6 +1271,38 @@ public class AttachAi extends SpellAbilityAi {
return true;
}
+ /**
+ * Checks if it is useful to execute the attach action given the current context.
+ *
+ * @param card
+ * the card
+ * @param sa SpellAbility
+ * @return true, if the action is useful (beneficial) in the current minimal context (Card vs. Attach SpellAbility)
+ */
+ private static boolean isUsefulAttachAction(Card c, SpellAbility sa) {
+ if (c == null) {
+ return false;
+ }
+ if (sa.getHostCard() == null) {
+ // FIXME: Not sure what should the resolution be if a SpellAbility has no host card. This should
+ // not happen normally. Possibly remove this block altogether? (if it's an impossible condition).
+ System.out.println("AttachAi: isUsefulAttachAction unexpectedly called with SpellAbility with no host card. Assuming it's a determined useful action.");
+ return true;
+ }
+
+ ArrayList cardTypes = sa.getHostCard().getType();
+
+ if (cardTypes.contains("Equipment") && c.hasKeyword("CARDNAME can't attack or block.")) {
+ // useless to equip a creature that can't attack or block.
+ return false;
+ } else if (cardTypes.contains("Equipment") && c.isTapped() && c.hasKeyword("CARDNAME doesn't untap during your untap step.")) {
+ // useless to equip a creature that won't untap and is tapped.
+ return false;
+ }
+
+ return true;
+ }
+
@Override
public boolean confirmAction(Player player, SpellAbility sa, PlayerActionConfirmMode mode, String message) {
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 44e762941d5..dbbcc1fb173 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ControlGainAi.java
@@ -19,8 +19,10 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
+import forge.ai.AiCardMemory;
import forge.ai.ComputerUtilCard;
+import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
@@ -168,6 +170,10 @@ public class ControlGainAi extends SpellAbilityAi {
if (hasCreature) {
t = ComputerUtilCard.getBestCreatureAI(list);
+ if (lose != null && lose.contains("EOT")) {
+ // Remember to always attack with this creature since it'll bounce back to its owner at end of turn anyway
+ ((PlayerControllerAi)ai.getController()).getAi().getCardMemory().rememberCard(t, AiCardMemory.MemorySet.MANDATORY_ATTACKERS);
+ }
} else if (hasArtifact) {
t = ComputerUtilCard.getBestArtifactAI(list);
} else if (hasLand) {
diff --git a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java
index 738c10e3c77..53ec1c39442 100644
--- a/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java
+++ b/forge-ai/src/main/java/forge/ai/ability/PumpAiBase.java
@@ -3,13 +3,17 @@ package forge.ai.ability;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
+import forge.ai.AiCardMemory;
+import forge.ai.AiController;
import forge.ai.ComputerUtil;
import forge.ai.ComputerUtilCard;
import forge.ai.ComputerUtilCombat;
+import forge.ai.PlayerControllerAi;
import forge.ai.SpellAbilityAi;
import forge.card.MagicColor;
import forge.game.Game;
+import forge.game.ability.ApiType;
import forge.game.card.Card;
import forge.game.card.CardFactory;
import forge.game.card.CardLists;
@@ -439,6 +443,16 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return false;
}
+ // determine if some mana sources need to be held for the future spell to cast in Main 2 before determining whether to pump.
+ AiController aic = ((PlayerControllerAi)ai.getController()).getAi();
+ if (aic.getCardMemory().isMemorySetEmpty(AiCardMemory.MemorySet.HELD_MANA_SOURCES)) {
+ // only hold mana sources once
+ SpellAbility futureSpell = aic.predictSpellToCastInMain2(ApiType.Pump);
+ if (futureSpell != null && futureSpell.getHostCard() != null) {
+ aic.reserveManaSourcesForMain2(futureSpell);
+ }
+ }
+
// will the creature attack (only relevant for sorcery speed)?
if (phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)
&& phase.isPlayerTurn(ai)
@@ -448,7 +462,7 @@ public abstract class PumpAiBase extends SpellAbilityAi {
return true;
}
- // buff attacker/blocker using using triggered pump
+ // buff attacker/blocker using triggered pump
if (sa.isTrigger() && phase.getPhase().isBefore(PhaseType.COMBAT_DECLARE_ATTACKERS)) {
if (phase.isPlayerTurn(ai)) {
if (CombatUtil.canAttack(c)) {
diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java
index 6fa8e94143e..d1260795620 100644
--- a/forge-game/src/main/java/forge/game/GameAction.java
+++ b/forge-game/src/main/java/forge/game/GameAction.java
@@ -176,6 +176,7 @@ public class GameAction {
HashMap repParams = new HashMap();
repParams.put("Event", "Moved");
repParams.put("Affected", copied);
+ repParams.put("CardLKI", lastKnownInfo);
repParams.put("Origin", zoneFrom != null ? zoneFrom.getZoneType() : null);
repParams.put("Destination", zoneTo.getZoneType());
diff --git a/forge-game/src/main/java/forge/game/GameType.java b/forge-game/src/main/java/forge/game/GameType.java
index 307fea5d6e8..735f2f7629b 100644
--- a/forge-game/src/main/java/forge/game/GameType.java
+++ b/forge-game/src/main/java/forge/game/GameType.java
@@ -34,7 +34,7 @@ public enum GameType {
mainDeck.add("Island", 12);
mainDeck.add("Swamp", 12);
mainDeck.add("Mountain", 12);
- mainDeck.add("Swamp", 12);
+ mainDeck.add("Forest", 12);
deck.getOrCreate(DeckSection.Avatar).add(StaticData.instance().getVariantCards()
.getCard("Momir Vig, Simic Visionary Avatar"), 1);
return deck;
diff --git a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java
index d5951faed84..391c9dff89c 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/CopyPermanentEffect.java
@@ -152,7 +152,7 @@ public class CopyPermanentEffect extends SpellAbilityEffect {
for (int i = 0; i < multiplier; i++) {
final Card copy = CardFactory.copyCopiableCharacteristics(c, sa.getActivatingPlayer());
copy.setToken(true);
- copy.setCopiedToken(true);
+ copy.setCopiedPermanent(c);
CardFactory.copyCopiableAbilities(c, copy);
// add keywords from sa
for (final String kw : keywords) {
diff --git a/forge-game/src/main/java/forge/game/card/Card.java b/forge-game/src/main/java/forge/game/card/Card.java
index c4e2a80eae6..c181a9e8884 100644
--- a/forge-game/src/main/java/forge/game/card/Card.java
+++ b/forge-game/src/main/java/forge/game/card/Card.java
@@ -147,7 +147,7 @@ public class Card extends GameEntity implements Comparable {
private boolean tapped = false;
private boolean sickness = true; // summoning sickness
private boolean token = false;
- private boolean copiedToken = false;
+ private Card copiedPermanent = null;
private boolean copiedSpell = false;
private ArrayList mustBlockCards = null;
@@ -2906,25 +2906,25 @@ public class Card extends GameEntity implements Comparable {
/**
*
- * Setter for the field copiedToken.
+ * Getter for the field copiedPermanent.
*
*
- * @param b
- * a boolean.
+ * @return a Card.
*/
- public final void setCopiedToken(final boolean b) {
- this.copiedToken = b;
+ public final Card getCopiedPermanent() {
+ return this.copiedPermanent;
}
/**
*
- * isCopiedToken.
+ * Setter for the field copiedPermanent.
*
*
- * @return a boolean.
+ * @param c
+ * a Card.
*/
- public final boolean isCopiedToken() {
- return this.copiedToken;
+ public final void setCopiedPermanent(final Card c) {
+ this.copiedPermanent = c;
}
/**
@@ -6438,6 +6438,11 @@ public class Card extends GameEntity implements Comparable {
return false;
}
}
+ } else if (property.startsWith("hasKeyword")) {
+ // "withFlash" would find Flashback cards, add this to fix Mystical Teachings
+ if (!this.hasKeyword(property.substring(10))) {
+ return false;
+ }
} else if (property.startsWith("withFlashback")) {
boolean fb = false;
if (this.hasStartOfUnHiddenKeyword("Flashback")) {
@@ -8848,6 +8853,8 @@ public class Card extends GameEntity implements Comparable {
}
public boolean canBeShownTo(final Player viewer) {
+ if (viewer == null) { return false; }
+
Zone zone = this.getZone();
if (zone == null) { return true; } //cards outside any zone are visible to all
@@ -8922,7 +8929,7 @@ public class Card extends GameEntity implements Comparable {
}
public int getCMC(SplitCMCMode mode) {
- if (isToken() && !isCopiedToken()) {
+ if (isToken() && getCopiedPermanent() == null) {
return 0;
}
@@ -9140,4 +9147,12 @@ public class Card extends GameEntity implements Comparable {
public Card getCardForUi() {
return this;
}
+
+ public String getOracleText() {
+ CardRules rules = cardRules;
+ if (copiedPermanent != null) { //return oracle text of copied permanent if applicable
+ rules = copiedPermanent.getRules();
+ }
+ return rules != null ? rules.getOracleText() : "";
+ }
} // end Card class
diff --git a/forge-game/src/main/java/forge/game/card/CardFactory.java b/forge-game/src/main/java/forge/game/card/CardFactory.java
index 4bd7cf8d557..41eef621ae1 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactory.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactory.java
@@ -73,7 +73,7 @@ public class CardFactory {
*/
public final static Card copyCard(final Card in, boolean assignNewId) {
Card out;
- if (!(in.isToken() || in.isCopiedToken())) {
+ if (!(in.isToken() || in.getCopiedPermanent() != null)) {
out = assignNewId ? getCard(in.getPaperCard(), in.getOwner())
: getCard(in.getPaperCard(), in.getOwner(), in.getUniqueNumber());
} else { // token
diff --git a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
index 4f6dc78b432..3d2b0738595 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
@@ -38,6 +38,7 @@ import forge.game.ability.AbilityUtils;
import forge.game.ability.ApiType;
import forge.game.card.CardPredicates.Presets;
import forge.game.cost.Cost;
+import forge.game.cost.CostPayment;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.phase.PhaseHandler;
import forge.game.player.Player;
@@ -137,7 +138,7 @@ public class CardFactoryUtil {
@Override
public boolean canPlay() {
return sourceCard.getController().equals(this.getActivatingPlayer()) && sourceCard.isFaceDown()
- && sourceCard.isInPlay();
+ && sourceCard.isInPlay() && CostPayment.canPayAdditionalCosts(cost, this);
}
}; // morph_up
diff --git a/forge-game/src/main/java/forge/game/combat/Combat.java b/forge-game/src/main/java/forge/game/combat/Combat.java
index 7308a8137ec..34ba2b70a82 100644
--- a/forge-game/src/main/java/forge/game/combat/Combat.java
+++ b/forge-game/src/main/java/forge/game/combat/Combat.java
@@ -172,7 +172,7 @@ public class Combat {
return ab;
}
CombatLki lki = lkiCache.get(c);
- return lki == null ? null : lki.getFirstBand();
+ return lki == null || lki.isAttacker ? null : lki.getFirstBand();
}
public final List getAttackingBands() {
diff --git a/forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java b/forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java
index c2dc9c5f836..11abba707ca 100644
--- a/forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java
+++ b/forge-game/src/main/java/forge/game/replacement/ReplaceMoved.java
@@ -72,6 +72,7 @@ public class ReplaceMoved extends ReplacementEffect {
@Override
public void setReplacingObjects(Map runParams, SpellAbility sa) {
sa.setReplacingObject("Card", runParams.get("Affected"));
+ sa.setReplacingObject("CardLKI", runParams.get("CardLKI"));
}
}
diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDock.java b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDock.java
index 804bf0e1bdc..6d36f273587 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDock.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CDock.java
@@ -61,10 +61,7 @@ public enum CDock implements ICDoc {
* End turn.
*/
public void endTurn() {
- game.autoPassUntilEndOfTurn();
- if (!CPrompt.SINGLETON_INSTANCE.passPriority()) {
- game.autoPassCancel();
- }
+ CPrompt.SINGLETON_INSTANCE.passPriorityUntilEndOfTurn();
}
public void revertLayout() {
diff --git a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java
index 57b75710b6a..0f5f40c60cd 100644
--- a/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java
+++ b/forge-gui-desktop/src/main/java/forge/screens/match/controllers/CPrompt.java
@@ -101,6 +101,10 @@ public enum CPrompt implements ICDoc {
return Singletons.getControl().getGameView().passPriority();
}
+ public boolean passPriorityUntilEndOfTurn() {
+ return Singletons.getControl().getGameView().passPriorityUntilEndOfTurn();
+ }
+
public void selectPlayer(final PlayerView player, final ITriggerEvent triggerEvent) {
Singletons.getControl().getGameView().selectPlayer(player, triggerEvent);
}
diff --git a/forge-gui/CHANGES.txt b/forge-gui/CHANGES.txt
index 857aad69048..2ed11b7c7da 100644
--- a/forge-gui/CHANGES.txt
+++ b/forge-gui/CHANGES.txt
@@ -1,7 +1,7 @@
-Forge Beta: 08-29-2014 ver 1.5.25
+Forge Beta: 09-##-2014 ver 1.5.26
-14049 cards in total.
+# cards in total.
-------------
@@ -9,65 +9,25 @@ Release Notes
-------------
- Improved Commander support -
-There will no longer be a black rectangle for "Commander effect" in the command zone
-The details previously available by hovering over that rectangle will now appear when hovering over the commander itself
-The dialog for the commander replacement effect will now display the commander's name
+There will no longer be a black rectangle for "Commander effect" in the command zone.
+The details previously available by hovering over that rectangle will now appear when hovering over the commander itself.
+The dialog for the commander replacement effect will now display the commander's name.
+
- Auto-targeting support -
When playing spells and abilities with the text "target opponent", if you only have one opponent, you will no longer be asked to choose the opponent to target.
-When triggered abilities have only one valid target, that target will now be auto-selected
+When triggered abilities have only one valid target, that target will now be auto-selected.
-- New Commander 2014 and KTK cards -
+- New Commander 2014 and Khans of Tarkir cards -
We have added a branch to our SVN for the new cards that are currently being scripted. These cards are not yet available in this build of forge. Please be patient and they will soon become available.
-- Improved auto-yield support -
-Pressing "End Turn" now properly skips your attack phase and doesn't get cancelled automatically if a spell or ability is put on the stack. You will still be given a chance to declare blockers if your opponent attacks, but after that the rest of your opponent's turn will now be skipped properly.
-To alleviate pressing this accidentally, as long as you're auto-yielding this way, you'll be able to press Escape or the Cancel button to cancel the auto-yield and be given the chance to act again. Phases with stops and spells/abilities resolving will be given a slight delay to allow you to see what's going on and give you the chance to regain control by cancelling the yield.
-Added support for auto-yielding abilities on the stack so you don't have to press OK each time
-
-
-- Improved Gauntlet Support -
-Add column to Load Gauntlet screen to display your deck for the gauntlet.
-Fix so your deck is saved with a gauntlet when starting a quick gauntlet.
-Instead of crashing, prompt user to select a deck if attempting to load a gauntlet that doesn't have one saved.
-Make it so Last Activity column isn't cut off and is formatted properly.
-Make it so double-clicking a gauntlet on the Load Gauntlet screen will launch it.
-Support renaming gauntlets.
-Support sorting gauntlets (and quests) case insensitive.
-
-
-- Fixed loading overlay to show logo properly and have some transparency -
-
-
-- Vastly Improved Boolean Expressions -
-Boolean expressions no longer require the use of quotation marks around your search terms, making it a far more useful
-feature. Boolean expressions been rewritten from the ground up to be faster and better at parsing your search terms.
-With the new parsing also comes NOT operators (!). Place these before things to negate them. For example:
-
-!r
-
-will find all cards that don't have red in their mana costs (when searching by cost). These can be a bit finicky at
-times since there's no order of operations yet. This will possibly be implemented in a future update. For now, be sure
-to put all possibly ambiguous NOT operators within parenthesis:
-
-!(r || g) instead of !r && !g
-
-Unfortunately I did not have time to implement an order of operations for this release, but rest assured it's on its
-way... eventually.
-
-
-- Quest Draft Preferences -
-You can now set how long you want quest draft tournaments to be available and how often you want them to be generated.
-Existing draft tournaments will not be affected.
-
-
---------
New Cards
---------
-Wishmonger
+
-------------------------------
@@ -79,13 +39,22 @@ Stitcher Geralf
Teferi, Temporal Archmage
---------------------
-New KTK branch Cards
---------------------
+--------------------------------
+New Khans of Tarkir branch Cards
+--------------------------------
Jeskai Elder
Thousand Winds
Zurgo Helmsmasher
+Dragon-Style Twins
+Herald of Anafenza
+Rattleclaw Mystic
+Temur Ascendancy
+Ainok Bond-Kin
+Mardu Skullhunter
+Heir of the Wilds
+Ivorytusk Fortress
+Sagu Mauler
------------
diff --git a/forge-gui/README.txt b/forge-gui/README.txt
index db4f9d3cfac..495b74b9aa1 100644
--- a/forge-gui/README.txt
+++ b/forge-gui/README.txt
@@ -1170,6 +1170,47 @@ will find all humans or cats that are also warriors, clerics, or soldiers.
More improvements are on their way in the next version of Forge, including removing the need for quotation marks and adding NOT operators.
+- Improved auto-yield support -
+Pressing "End Turn" now properly skips your attack phase and doesn't get cancelled automatically if a spell or ability is put on the stack. You will still be given a chance to declare blockers if your opponent attacks, but after that the rest of your opponent's turn will now be skipped properly.
+To alleviate pressing this accidentally, as long as you're auto-yielding this way, you'll be able to press Escape or the Cancel button to cancel the auto-yield and be given the chance to act again. Phases with stops and spells/abilities resolving will be given a slight delay to allow you to see what's going on and give you the chance to regain control by cancelling the yield.
+Added support for auto-yielding abilities on the stack so you don't have to press OK each time.
+
+
+- Improved Gauntlet Support -
+Add column to Load Gauntlet screen to display your deck for the gauntlet.
+Fix so your deck is saved with a gauntlet when starting a quick gauntlet.
+Instead of crashing, prompt user to select a deck if attempting to load a gauntlet that doesn't have one saved.
+Make it so Last Activity column isn't cut off and is formatted properly.
+Make it so double-clicking a gauntlet on the Load Gauntlet screen will launch it.
+Support renaming gauntlets.
+Support sorting gauntlets (and quests) case insensitive.
+
+
+- Fixed loading overlay to show logo properly and have some transparency -
+
+
+- Vastly Improved Boolean Expressions -
+Boolean expressions no longer require the use of quotation marks around your search terms, making it a far more useful
+feature. Boolean expressions been rewritten from the ground up to be faster and better at parsing your search terms.
+With the new parsing also comes NOT operators (!). Place these before things to negate them. For example:
+
+!r
+
+will find all cards that don't have red in their mana costs (when searching by cost). These can be a bit finicky at
+times since there's no order of operations yet. This will possibly be implemented in a future update. For now, be sure
+to put all possibly ambiguous NOT operators within parenthesis:
+
+!(r || g) instead of !r && !g
+
+Unfortunately I did not have time to implement an order of operations for this release, but rest assured it's on its
+way... eventually.
+
+
+- Quest Draft Preferences -
+You can now set how long you want quest draft tournaments to be available and how often you want them to be generated.
+Existing draft tournaments will not be affected.
+
+
Our Lawyers Made Us Do This:
----------------------------
diff --git a/forge-gui/res/cardsfolder/b/bola_warrior.txt b/forge-gui/res/cardsfolder/b/bola_warrior.txt
index 454087699cf..c553b36c5bf 100644
--- a/forge-gui/res/cardsfolder/b/bola_warrior.txt
+++ b/forge-gui/res/cardsfolder/b/bola_warrior.txt
@@ -2,7 +2,7 @@ Name:Bola Warrior
ManaCost:1 R
Types:Creature Human Spellshaper Warrior
PT:1/1
-A:AB$ Pump | Cost$ R T Discard<1/Card> | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn.
+A:AB$ Pump | Cost$ R T Discard<1/Card> | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn.
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/bola_warrior.jpg
Oracle:{R}, {T}, Discard a card: Target creature can't block this turn.
diff --git a/forge-gui/res/cardsfolder/g/grixis_battlemage.txt b/forge-gui/res/cardsfolder/g/grixis_battlemage.txt
index e842a8df714..2194064aadf 100644
--- a/forge-gui/res/cardsfolder/g/grixis_battlemage.txt
+++ b/forge-gui/res/cardsfolder/g/grixis_battlemage.txt
@@ -4,6 +4,6 @@ Types:Creature Human Wizard
PT:2/2
A:AB$ Draw | Cost$ U T | NumCards$ 1 | SpellDescription$ Draw a card, then discard a card. | SubAbility$ DBDiscard
SVar:DBDiscard:DB$Discard | Defined$ You | NumCards$ 1 | Mode$ TgtChoose
-A:AB$ Pump | Cost$ R T | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn.
+A:AB$ Pump | Cost$ R T | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn.
SVar:Picture:http://www.wizards.com/global/images/magic/general/grixis_battlemage.jpg
Oracle:{U}, {T}: Draw a card, then discard a card.\n{R}, {T}: Target creature can't block this turn.
diff --git a/forge-gui/res/cardsfolder/j/jamuraan_lion.txt b/forge-gui/res/cardsfolder/j/jamuraan_lion.txt
index 9f433efee25..50fcf8e7ad3 100644
--- a/forge-gui/res/cardsfolder/j/jamuraan_lion.txt
+++ b/forge-gui/res/cardsfolder/j/jamuraan_lion.txt
@@ -2,7 +2,7 @@ Name:Jamuraan Lion
ManaCost:2 W
Types:Creature Cat
PT:3/1
-A:AB$ Pump | Cost$ W T | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn.
+A:AB$ Pump | Cost$ W T | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn.
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/jamuraan_lion.jpg
Oracle:{W}, {T}: Target creature can't block this turn.
diff --git a/forge-gui/res/cardsfolder/k/krark_clan_ogre.txt b/forge-gui/res/cardsfolder/k/krark_clan_ogre.txt
index 05fd99b97c1..cae09465d8d 100644
--- a/forge-gui/res/cardsfolder/k/krark_clan_ogre.txt
+++ b/forge-gui/res/cardsfolder/k/krark_clan_ogre.txt
@@ -2,7 +2,7 @@ Name:Krark-Clan Ogre
ManaCost:3 R R
Types:Creature Ogre
PT:3/3
-A:AB$ Pump | Cost$ R Sac<1/Artifact> | ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True | KW$ CARDNAME can't block. | SpellDescription$ Target creature can't block this turn.
+A:AB$ Pump | Cost$ R Sac<1/Artifact> | ValidTgts$ Creature | TgtPrompt$ Select target creature | IsCurse$ True | KW$ HIDDEN CARDNAME can't block. | SpellDescription$ Target creature can't block this turn.
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/krark_clan_ogre.jpg
Oracle:{R}, Sacrifice an artifact: Target creature can't block this turn.
diff --git a/forge-gui/res/cardsfolder/m/mystical_teachings.txt b/forge-gui/res/cardsfolder/m/mystical_teachings.txt
index 1953b68000b..919c6df3986 100644
--- a/forge-gui/res/cardsfolder/m/mystical_teachings.txt
+++ b/forge-gui/res/cardsfolder/m/mystical_teachings.txt
@@ -2,7 +2,7 @@ Name:Mystical Teachings
ManaCost:3 U
Types:Instant
K:Flashback 5 B
-A:SP$ ChangeZone | Cost$ 3 U | ChangeType$ Instant,Card.withFlash | ChangeNum$ 1 | Origin$ Library | Destination$ Hand | Shuffle$ True | SpellDescription$ Search your library for an instant card or a card with flash, reveal it, and put it into your hand. Then shuffle your library.
+A:SP$ ChangeZone | Cost$ 3 U | ChangeType$ Instant,Card.hasKeywordFlash | ChangeNum$ 1 | Origin$ Library | Destination$ Hand | Shuffle$ True | SpellDescription$ Search your library for an instant card or a card with flash, reveal it, and put it into your hand. Then shuffle your library.
SVar:RemAIDeck:True
SVar:Picture:http://www.wizards.com/global/images/magic/general/mystical_teachings.jpg
Oracle:Search your library for an instant card or a card with flash, reveal it, and put it into your hand. Then shuffle your library.\nFlashback {5}{B} (You may cast this card from your graveyard for its flashback cost. Then exile it.)
diff --git a/forge-gui/res/cardsfolder/s/stun.txt b/forge-gui/res/cardsfolder/s/stun.txt
index 2a0565bd90f..9292b702606 100644
--- a/forge-gui/res/cardsfolder/s/stun.txt
+++ b/forge-gui/res/cardsfolder/s/stun.txt
@@ -1,7 +1,7 @@
Name:Stun
ManaCost:1 R
Types:Instant
-A:SP$ Pump | Cost$ 1 R | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn. | SubAbility$ DBDraw
+A:SP$ Pump | Cost$ 1 R | ValidTgts$ Creature | TgtPrompt$ Select target creature | KW$ HIDDEN CARDNAME can't block. | IsCurse$ True | SpellDescription$ Target creature can't block this turn. | SubAbility$ DBDraw
SVar:DBDraw:DB$Draw | NumCards$ 1 | SpellDescription$ Draw a card.
SVar:Picture:http://www.wizards.com/global/images/magic/general/stun.jpg
Oracle:Target creature can't block this turn.\nDraw a card.
diff --git a/forge-gui/res/editions/Duel Decks Speed vs. Cunning.txt b/forge-gui/res/editions/Duel Decks Speed vs. Cunning.txt
new file mode 100644
index 00000000000..f26b5ab4254
--- /dev/null
+++ b/forge-gui/res/editions/Duel Decks Speed vs. Cunning.txt
@@ -0,0 +1,88 @@
+[metadata]
+Code=DDN
+Date=2014-09-05
+Name=Duel Decks: Speed vs. Cunning
+Type=Duel_Decks
+
+[cards]
+M Zurgo Helmsmasher
+U Frenzied Goblin
+C Infantry Veteran
+C Leonin Snarecaster
+C Dregscape Zombie
+C Goblin Deathraiders
+U Hellraiser Goblin
+U Fleshbag Marauder
+U Goblin Warchief
+R Hell's Thunder
+C Kathari Bomber
+U Shambling Remains
+U Mardu Heart-Piercer
+U Beetleback Chief
+R Krenko, Mob Boss
+R Ogre Battledriver
+U Flame-Kin Zealot
+U Scourge Devil
+U Oni of Wild Places
+C Reckless Abandon
+C Shock
+C Bone Splinters
+U Arc Trail
+U Goblin Bombardment
+C Krenko's Command
+C Act of Treason
+U Dauntless Onslaught
+C Orcish Cannonade
+C Fiery Fall
+R Fury of the Horde
+R Banefire
+C Evolving Wilds
+U Ghitu Encampment
+U Nomad Outpost
+L Mountain
+L Mountain
+L Mountain
+L Mountain
+L Plains
+L Plains
+L Plains
+L Plains
+L Swamp
+L Swamp
+L Swamp
+M Arcanis the Omnipotent
+U Faerie Impostor
+C Coral Trickster
+C Fathom Seer
+U Jeskai Elder
+U Willbender
+C Sparkmage Apprentice
+C Lone Missionary
+C Master Decoy
+C Echo Tracer
+C Kor Hookmaster
+U Stonecloaker
+C Aquamorph Entity
+C Hussar Patrol
+R Lightning Angel
+C Faerie Invaders
+R Thousand Winds
+R Sphinx of Uthuun
+C Fleeting Distraction
+C Stave Off
+C Swift Justice
+C Impulse
+C Mana Leak
+U Lightning Helix
+R Hold the Line
+U Inferno Trap
+R Steam Augury
+C Traumatic Visions
+C Whiplash Trap
+U Arrow Volley Trap
+C Repeal
+U Mystic Monastery
+C Terramorphic Expanse
+L Island
+L Island
+L Island
\ No newline at end of file
diff --git a/forge-gui/src/main/java/forge/match/input/InputBase.java b/forge-gui/src/main/java/forge/match/input/InputBase.java
index 145d167a72b..b78f29eb3ab 100644
--- a/forge-gui/src/main/java/forge/match/input/InputBase.java
+++ b/forge-gui/src/main/java/forge/match/input/InputBase.java
@@ -64,7 +64,7 @@ public abstract class InputBase implements java.io.Serializable, Input {
}
protected boolean allowAwaitNextInput() {
- return true;
+ return false;
}
private static final Timer awaitNextInputTimer = new Timer();
diff --git a/forge-gui/src/main/java/forge/match/input/InputBlock.java b/forge-gui/src/main/java/forge/match/input/InputBlock.java
index 91556fb5cbd..ac6bbb7c22d 100644
--- a/forge-gui/src/main/java/forge/match/input/InputBlock.java
+++ b/forge-gui/src/main/java/forge/match/input/InputBlock.java
@@ -28,6 +28,7 @@ import forge.game.player.Player;
import forge.game.zone.ZoneType;
import forge.player.PlayerControllerHuman;
import forge.util.ITriggerEvent;
+import forge.util.ThreadUtil;
import forge.util.gui.SGuiDialog;
import forge.view.CardView;
@@ -90,14 +91,20 @@ public class InputBlock extends InputSyncronizedBase {
/** {@inheritDoc} */
@Override
public final void onOk() {
- String blockErrors = CombatUtil.validateBlocks(combat, defender);
+ final String blockErrors = CombatUtil.validateBlocks(combat, defender);
if (blockErrors == null) {
// Done blocking
setCurrentAttacker(null);
stop();
}
- else {
- SGuiDialog.message(getGui(), blockErrors);
+ else {
+ //must run in game thread to prevent problems for mobile game
+ ThreadUtil.invokeInGameThread(new Runnable() {
+ @Override
+ public void run() {
+ SGuiDialog.message(getGui(), blockErrors);
+ }
+ });
}
}
diff --git a/forge-gui/src/main/java/forge/match/input/InputPassPriority.java b/forge-gui/src/main/java/forge/match/input/InputPassPriority.java
index c7bdc785e3f..354888457bd 100644
--- a/forge-gui/src/main/java/forge/match/input/InputPassPriority.java
+++ b/forge-gui/src/main/java/forge/match/input/InputPassPriority.java
@@ -91,7 +91,7 @@ public class InputPassPriority extends InputSyncronizedBase {
@Override
protected boolean allowAwaitNextInput() {
- return !player.getController().mayAutoPass(); //don't allow awaiting next input if player chose to end the turn
+ return chosenSa == null && !player.getController().mayAutoPass(); //don't allow awaiting next input if player chose to end the turn or if a spell/ability is chosen
}
private void passPriority(final Runnable runnable) {
diff --git a/forge-gui/src/main/java/forge/match/input/InputProxy.java b/forge-gui/src/main/java/forge/match/input/InputProxy.java
index ddaa045ea9a..c1a3af61bc2 100644
--- a/forge-gui/src/main/java/forge/match/input/InputProxy.java
+++ b/forge-gui/src/main/java/forge/match/input/InputProxy.java
@@ -57,9 +57,18 @@ public class InputProxy implements Observer {
return this.controller.getGui();
}
- public boolean passPriority() {
+ public boolean passPriority() {
+ return passPriority(false);
+ }
+ public boolean passPriorityUntilEndOfTurn() {
+ return passPriority(true);
+ }
+ private boolean passPriority(final boolean passUntilEndOfTurn) {
final Input inp = getInput();
if (inp instanceof InputPassPriority) {
+ if (passUntilEndOfTurn) {
+ controller.autoPassUntilEndOfTurn();
+ }
inp.selectButtonOK();
return true;
}
diff --git a/forge-gui/src/main/java/forge/player/PlayerControllerLocal.java b/forge-gui/src/main/java/forge/player/PlayerControllerLocal.java
index 2cca70081f7..a58b6abb4ea 100644
--- a/forge-gui/src/main/java/forge/player/PlayerControllerLocal.java
+++ b/forge-gui/src/main/java/forge/player/PlayerControllerLocal.java
@@ -1421,6 +1421,11 @@ public class PlayerControllerLocal extends PlayerControllerHuman implements IGam
return getInputProxy().passPriority();
}
+ @Override
+ public boolean passPriorityUntilEndOfTurn() {
+ return getInputProxy().passPriorityUntilEndOfTurn();
+ }
+
@Override
public void selectPlayer(final PlayerView player, final ITriggerEvent triggerEvent) {
getInputProxy().selectPlayer(player, triggerEvent);
diff --git a/forge-gui/src/main/java/forge/view/IGameView.java b/forge-gui/src/main/java/forge/view/IGameView.java
index 1de59faa6e2..e06873f6cfd 100644
--- a/forge-gui/src/main/java/forge/view/IGameView.java
+++ b/forge-gui/src/main/java/forge/view/IGameView.java
@@ -55,6 +55,7 @@ public interface IGameView {
public abstract void selectButtonOk();
public abstract void selectButtonCancel();
public abstract boolean passPriority();
+ public abstract boolean passPriorityUntilEndOfTurn();
public abstract void selectPlayer(PlayerView player, ITriggerEvent triggerEvent);
public abstract void selectCard(CardView card, ITriggerEvent triggerEvent);
public abstract void selectAbility(SpellAbilityView sa);