diff --git a/forge-ai/pom.xml b/forge-ai/pom.xml
index 89c157c81ae..ec14ef4f065 100644
--- a/forge-ai/pom.xml
+++ b/forge-ai/pom.xml
@@ -6,7 +6,7 @@
forge
forge
- 1.6.25-SNAPSHOT
+ 1.6.26-SNAPSHOT
forge-ai
diff --git a/forge-ai/src/main/java/forge/ai/AiAttackController.java b/forge-ai/src/main/java/forge/ai/AiAttackController.java
index e98fd7b6a7e..f4d65fa5980 100644
--- a/forge-ai/src/main/java/forge/ai/AiAttackController.java
+++ b/forge-ai/src/main/java/forge/ai/AiAttackController.java
@@ -190,7 +190,28 @@ public class AiAttackController {
if ((attacker.getNetToughness() + ComputerUtilCombat.predictToughnessBonusOfAttacker(attacker, null, combat, true)) <= 0) {
return false;
}
-
+
+ // the attacker will die to a triggered ability (e.g. Sarkhan the Masterless)
+ for (Card c : ai.getOpponents().getCardsIn(ZoneType.Battlefield)) {
+ for (Trigger t : c.getTriggers()) {
+ if (t.getMode() == TriggerType.Attacks) {
+ SpellAbility sa = t.getOverridingAbility();
+ if (sa == null && t.hasParam("Execute")) {
+ sa = AbilityFactory.getAbility(c, t.getParam("Execute"));
+ }
+
+ if (sa != null && sa.getApi() == ApiType.EachDamage && "TriggeredAttacker".equals(sa.getParam("DefinedPlayers"))) {
+ List valid = CardLists.getValidCards(c.getController().getCreaturesInPlay(), sa.getParam("ValidCards"), c.getController(), c, sa);
+ // TODO: this assumes that 1 damage is dealt per creature. Improve this to check the parameter/X to determine
+ // how much damage is dealt by each of the creatures in the valid list.
+ if (attacker.getNetToughness() <= valid.size()) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+
if ("TRUE".equals(attacker.getSVar("HasAttackEffect"))) {
return true;
}
diff --git a/forge-ai/src/main/java/forge/ai/AiProps.java b/forge-ai/src/main/java/forge/ai/AiProps.java
index 290744383e4..413e1c45d1f 100644
--- a/forge-ai/src/main/java/forge/ai/AiProps.java
+++ b/forge-ai/src/main/java/forge/ai/AiProps.java
@@ -129,7 +129,10 @@ public enum AiProps { /** */
FLASH_USE_BUFF_AURAS_AS_COMBAT_TRICKS("true"),
FLASH_BUFF_AURA_CHANCE_TO_CAST_EARLY("1"),
FLASH_BUFF_AURA_CHANCE_CAST_AT_EOT("5"),
- FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("100"); /** */
+ FLASH_BUFF_AURA_CHANCE_TO_RESPOND_TO_STACK("100"),
+ BLINK_RELOAD_PLANESWALKER_CHANCE("30"), /** */
+ BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY("2"), /** */
+ BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF("2"); /** */
// Experimental features, must be promoted or removed after extensive testing and, ideally, defaulting
// <-- There are no experimental options here -->
diff --git a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
index 69d55bb9281..8f8e2240660 100644
--- a/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/ChangeZoneAi.java
@@ -26,6 +26,7 @@ import forge.game.spellability.AbilitySub;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.TargetRestrictions;
import forge.game.zone.ZoneType;
+import forge.util.MyRandom;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
@@ -676,7 +677,8 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
// only use blink or bounce effects
- if (!(destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone))
+ if (!(destination.equals(ZoneType.Exile)
+ && (subApi == ApiType.DelayedTrigger || subApi == ApiType.ChangeZone || "DelayedBlink".equals(sa.getParam("AILogic"))))
&& !destination.equals(ZoneType.Hand)) {
return false;
}
@@ -936,7 +938,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// if it's blink or bounce, try to save my about to die stuff
final boolean blink = (destination.equals(ZoneType.Exile) && (subApi == ApiType.DelayedTrigger
- || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered"))));
+ || "DelayedBlink".equals(sa.getParam("AILogic")) || (subApi == ApiType.ChangeZone && subAffected.equals("Remembered"))));
if ((destination.equals(ZoneType.Hand) || blink) && (tgt.getMinTargets(sa.getHostCard(), sa) <= 1)) {
// save my about to die stuff
Card tobounce = canBouncePermanent(ai, sa, list);
@@ -1230,6 +1232,7 @@ public class ChangeZoneAi extends SpellAbilityAi {
// filter out untargetables
CardCollectionView aiPermanents = CardLists
.filterControlledBy(list, ai);
+ CardCollection aiPlaneswalkers = CardLists.filter(aiPermanents, Presets.PLANESWALKERS);
// Felidar Guardian + Saheeli Rai combo support
if (sa.getHostCard().getName().equals("Felidar Guardian")) {
@@ -1273,6 +1276,33 @@ public class ChangeZoneAi extends SpellAbilityAi {
}
}
}
+ // Reload planeswalkers
+ else if (!aiPlaneswalkers.isEmpty() && (sa.getHostCard().isSorcery() || !game.getPhaseHandler().isPlayerTurn(ai))) {
+ int maxLoyaltyToConsider = 2;
+ int loyaltyDiff = 2;
+ int chance = 30;
+ if (ai.getController().isAI()) {
+ AiController aic = ((PlayerControllerAi) ai.getController()).getAi();
+ maxLoyaltyToConsider = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY);
+ loyaltyDiff = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF);
+ chance = aic.getIntProperty(AiProps.BLINK_RELOAD_PLANESWALKER_CHANCE);
+ }
+ if (MyRandom.percentTrue(chance)) {
+ Collections.sort(aiPlaneswalkers, new Comparator() {
+ @Override
+ public int compare(final Card a, final Card b) {
+ return a.getCounters(CounterType.LOYALTY) - b.getCounters(CounterType.LOYALTY);
+ }
+ });
+ for (Card pw : aiPlaneswalkers) {
+ int curLoyalty = pw.getCounters(CounterType.LOYALTY);
+ int freshLoyalty = pw.getCurrentState().getBaseLoyalty();
+ if (freshLoyalty - curLoyalty >= loyaltyDiff && curLoyalty <= maxLoyaltyToConsider) {
+ return pw;
+ }
+ }
+ }
+ }
return null;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
index 531ba01988a..32a604679d2 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DamageDealAi.java
@@ -75,7 +75,7 @@ public class DamageDealAi extends DamageAiBase {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
- source.setSVar("PayX", Integer.toString(dmg));
+ sa.setSVar("PayX", Integer.toString(dmg));
} else if (sa.getSVar(damage).equals("Count$CardsInYourHand") && source.getZone().is(ZoneType.Hand)) {
dmg--; // the card will be spent casting the spell, so actual damage is 1 less
}
@@ -115,7 +115,7 @@ public class DamageDealAi extends DamageAiBase {
}
// Set PayX here to maximum value. It will be adjusted later depending on the target.
- source.setSVar("PayX", Integer.toString(dmg));
+ sa.setSVar("PayX", Integer.toString(dmg));
} else if (sa.getSVar(damage).contains("InYourHand") && source.getZone().is(ZoneType.Hand)) {
dmg = CardFactoryUtil.xCount(source, sa.getSVar(damage)) - 1; // the card will be spent casting the spell, so actual damage is 1 less
} else if (sa.getSVar(damage).equals("TargetedPlayer$CardsInHand")) {
@@ -283,7 +283,7 @@ public class DamageDealAi extends DamageAiBase {
if (sourceName.equals("Crater's Claws") && ai.hasFerocious()) {
actualPay = actualPay > 2 ? actualPay - 2 : 0;
}
- source.setSVar("PayX", Integer.toString(actualPay));
+ sa.setSVar("PayX", Integer.toString(actualPay));
}
}
@@ -291,7 +291,7 @@ public class DamageDealAi extends DamageAiBase {
// Check to ensure that we have enough counters to remove per the defined PayX
for (CostPart part : sa.getPayCosts().getCostParts()) {
if (part instanceof CostRemoveCounter) {
- if (source.getCounters(((CostRemoveCounter) part).counter) < Integer.valueOf(source.getSVar("PayX"))) {
+ if (source.getCounters(((CostRemoveCounter) part).counter) < Integer.valueOf(sa.getSVar("PayX"))) {
return false;
}
break;
@@ -941,7 +941,7 @@ public class DamageDealAi extends DamageAiBase {
if (damage.equals("X") && sa.getSVar(damage).equals("Count$xPaid")) {
// Set PayX here to maximum value.
dmg = ComputerUtilMana.determineLeftoverMana(sa, ai);
- source.setSVar("PayX", Integer.toString(dmg));
+ sa.setSVar("PayX", Integer.toString(dmg));
}
final TargetRestrictions tgt = sa.getTargetRestrictions();
@@ -971,7 +971,7 @@ public class DamageDealAi extends DamageAiBase {
}
}
- source.setSVar("PayX", Integer.toString(actualPay));
+ sa.setSVar("PayX", Integer.toString(actualPay));
}
}
@@ -1031,7 +1031,7 @@ public class DamageDealAi extends DamageAiBase {
saTgt.resetTargets();
saTgt.getTargets().add(tgtCreature != null && dmg < opponent.getLife() ? tgtCreature : opponent);
- source.setSVar("PayX", Integer.toString(dmg));
+ sa.setSVar("PayX", Integer.toString(dmg));
return true;
}
diff --git a/forge-ai/src/main/java/forge/ai/ability/DigAi.java b/forge-ai/src/main/java/forge/ai/ability/DigAi.java
index b575c91a709..00d8ce5566d 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DigAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DigAi.java
@@ -1,9 +1,13 @@
package forge.ai.ability;
+import com.google.common.base.Predicate;
import forge.ai.*;
import forge.game.Game;
import forge.game.ability.AbilityUtils;
import forge.game.card.Card;
+import forge.game.card.CardCollection;
+import forge.game.card.CardLists;
+import forge.game.keyword.Keyword;
import forge.game.phase.PhaseType;
import forge.game.player.Player;
import forge.game.player.PlayerActionConfirmMode;
@@ -126,6 +130,25 @@ public class DigAi extends SpellAbilityAi {
@Override
public Card chooseSingleCard(Player ai, SpellAbility sa, Iterable valid, boolean isOptional, Player relatedPlayer) {
+ if ("DigForCreature".equals(sa.getParam("AILogic"))) {
+ Card bestChoice = ComputerUtilCard.getBestCreatureAI(valid);
+ if (bestChoice == null) {
+ // no creatures, but maybe there's a morphable card that can be played as a creature?
+ CardCollection morphs = CardLists.filter(valid, new Predicate() {
+ @Override
+ public boolean apply(Card card) {
+ return card.hasKeyword(Keyword.MORPH);
+ }
+ });
+ if (!morphs.isEmpty()) {
+ bestChoice = ComputerUtilCard.getBestAI(morphs);
+ }
+ }
+
+ // still nothing, so return the worst card since it'll be unplayable from exile (e.g. Vivien, Champion of the Wilds)
+ return bestChoice != null ? bestChoice : ComputerUtilCard.getWorstAI(valid);
+ }
+
if (sa.getActivatingPlayer().isOpponentOf(ai) && relatedPlayer.isOpponentOf(ai)) {
return ComputerUtilCard.getWorstPermanentAI(valid, false, true, false, false);
} else {
diff --git a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java
index 3fd77edf9fc..2d286dbddd8 100644
--- a/forge-ai/src/main/java/forge/ai/ability/DrawAi.java
+++ b/forge-ai/src/main/java/forge/ai/ability/DrawAi.java
@@ -211,9 +211,11 @@ public class DrawAi extends SpellAbilityAi {
private boolean targetAI(final Player ai, final SpellAbility sa, final boolean mandatory) {
final Card source = sa.getHostCard();
- final boolean drawback = sa.getParent() != null;
final Game game = ai.getGame();
final String logic = sa.getParamOrDefault("AILogic", "");
+ final boolean considerPrimary = logic.equals("ConsiderPrimary");
+ final boolean drawback = (sa.getParent() != null) && !considerPrimary;
+ boolean assumeSafeX = false; // if true, the AI will assume that the X value has been set to a value that is safe to draw
int computerHandSize = ai.getCardsIn(ZoneType.Hand).size();
final int computerLibrarySize = ai.getCardsIn(ZoneType.Library).size();
@@ -241,7 +243,12 @@ public class DrawAi extends SpellAbilityAi {
numCards = Integer.parseInt(source.getSVar("PayX"));
} else {
numCards = ComputerUtilMana.determineLeftoverMana(sa, ai);
+ // try not to overdraw
+ int safeDraw = Math.min(computerMaxHandSize - computerHandSize, computerLibrarySize - 3);
+ if (sa.getHostCard().isInstant() || sa.getHostCard().isSorcery()) { safeDraw++; } // card will be spent
+ numCards = Math.min(numCards, safeDraw);
source.setSVar("PayX", Integer.toString(numCards));
+ assumeSafeX = true;
}
xPaid = true;
}
@@ -492,7 +499,8 @@ public class DrawAi extends SpellAbilityAi {
if ((computerHandSize + numCards > computerMaxHandSize)
&& game.getPhaseHandler().isPlayerTurn(ai)
- && !sa.isTrigger()) {
+ && !sa.isTrigger()
+ && !assumeSafeX) {
// Don't draw too many cards and then risk discarding cards at
// EOT
if (!drawback) {
diff --git a/forge-core/pom.xml b/forge-core/pom.xml
index afb89ae67bc..8a79508056a 100644
--- a/forge-core/pom.xml
+++ b/forge-core/pom.xml
@@ -6,7 +6,7 @@
forge
forge
- 1.6.25-SNAPSHOT
+ 1.6.26-SNAPSHOT
forge-core
diff --git a/forge-game/pom.xml b/forge-game/pom.xml
index f5bd19fa4fe..1133ec9e0f9 100644
--- a/forge-game/pom.xml
+++ b/forge-game/pom.xml
@@ -6,7 +6,7 @@
forge
forge
- 1.6.25-SNAPSHOT
+ 1.6.26-SNAPSHOT
forge-game
diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java
index f9043497e96..5f9b82723ad 100644
--- a/forge-game/src/main/java/forge/game/GameAction.java
+++ b/forge-game/src/main/java/forge/game/GameAction.java
@@ -185,8 +185,7 @@ public class GameAction {
// all sort of funky shenanigans may happen later (e.g. their ETB replacement effects are set
// up on the wrong card state etc.).
if (wasFacedown && (fromBattlefield || (toHand && zoneFrom.is(ZoneType.Exile)))) {
- c.setState(CardStateName.Original, true);
- c.runFaceupCommands();
+ c.turnFaceUp();
}
if (!c.isToken()) {
@@ -306,8 +305,8 @@ public class GameAction {
}
if (game.getStack().isResolving(c) && !zoneTo.is(ZoneType.Graveyard) && repres == ReplacementResult.Prevented) {
- copied.getOwner().removeInboundToken(copied);
- return moveToGraveyard(c, cause, params);
+ copied.getOwner().removeInboundToken(copied);
+ return moveToGraveyard(c, cause, params);
}
copied.getOwner().removeInboundToken(copied);
return c;
@@ -445,13 +444,9 @@ public class GameAction {
// rule 504.6: reveal a face-down card leaving the stack
if (zoneFrom != null && zoneTo != null && zoneFrom.is(ZoneType.Stack) && !zoneTo.is(ZoneType.Battlefield) && wasFacedown) {
- // FIXME: tracker freeze-unfreeze is needed here to avoid a bug with the card staying face down in the View for the reveal
- boolean trackerFrozen = game.getTracker().isFrozen();
- game.getTracker().unfreeze();
- c.setState(CardStateName.Original, true);
- reveal(new CardCollection(c), c.getOwner(), true, "Face-down card moves from the stack: ");
- c.setState(CardStateName.FaceDown, true);
- if (trackerFrozen) { game.getTracker().freeze(); }
+ Card revealLKI = CardUtil.getLKICopy(c);
+ revealLKI.turnFaceUp(true, false);
+ reveal(new CardCollection(revealLKI), revealLKI.getOwner(), true, "Face-down card moves from the stack: ");
}
if (fromBattlefield) {
@@ -480,14 +475,12 @@ public class GameAction {
}
// Reveal if face-down
if (wasFacedown) {
- // FIXME: tracker freeze-unfreeze is needed here to avoid a bug with the card staying face down in the View for the reveal
- boolean trackerFrozen = game.getTracker().isFrozen();
- game.getTracker().unfreeze();
- c.setState(CardStateName.Original, true);
- reveal(new CardCollection(c), c.getOwner(), true, "Face-down card leaves the battlefield: ");
- c.setState(CardStateName.FaceDown, true);
- if (trackerFrozen) { game.getTracker().freeze(); }
- copied.setState(CardStateName.Original, true);
+ Card revealLKI = CardUtil.getLKICopy(c);
+ revealLKI.turnFaceUp(true, false);
+
+ reveal(new CardCollection(revealLKI), revealLKI.getOwner(), true, "Face-down card leaves the battlefield: ");
+
+ copied.setState(CardStateName.Original, true);
}
unattachCardLeavingBattlefield(copied);
// Remove all changed keywords
@@ -506,9 +499,9 @@ public class GameAction {
game.getStack().fizzleTriggersOnStackTargeting(copied, TriggerType.DamageDoneOnce);
}
} else if (zoneTo.is(ZoneType.Graveyard)
- || zoneTo.is(ZoneType.Hand)
- || zoneTo.is(ZoneType.Library)
- || zoneTo.is(ZoneType.Exile)) {
+ || zoneTo.is(ZoneType.Hand)
+ || zoneTo.is(ZoneType.Library)
+ || zoneTo.is(ZoneType.Exile)) {
copied.setTimestamp(game.getNextTimestamp());
copied.clearOptionalCostsPaid();
if (copied.isFaceDown()) {
@@ -1238,14 +1231,14 @@ public class GameAction {
}
if (reason == null) {
- List notLost = Lists.newArrayList();
- Set teams = Sets.newHashSet();
- for (Player p : allPlayers) {
+ List notLost = Lists.newArrayList();
+ Set teams = Sets.newHashSet();
+ for (Player p : allPlayers) {
if (p.getOutcome() == null || p.getOutcome().hasWon()) {
- notLost.add(p);
- teams.add(p.getTeam());
+ notLost.add(p);
+ teams.add(p.getTeam());
}
- }
+ }
int cntNotLost = notLost.size();
if (cntNotLost == 1) {
reason = GameEndReason.AllOpponentsLost;
@@ -1733,7 +1726,7 @@ public class GameAction {
} while (!allKept);
//Vancouver Mulligan as a scry with the decisions inside
- List scryers = Lists.newArrayList();
+ List scryers = Lists.newArrayList();
for(Player p : whoCanMulligan) {
if (p.getStartingHandSize() > p.getZone(ZoneType.Hand).size()) {
scryers.add(p);
diff --git a/forge-game/src/main/java/forge/game/GameActionUtil.java b/forge-game/src/main/java/forge/game/GameActionUtil.java
index dc3f6079cc7..c655377090c 100644
--- a/forge-game/src/main/java/forge/game/GameActionUtil.java
+++ b/forge-game/src/main/java/forge/game/GameActionUtil.java
@@ -72,6 +72,17 @@ public final class GameActionUtil {
if (sa.isSpell()) {
boolean lkicheck = false;
+
+ // need to be done before so it works with Vivien and Zoetic Cavern
+ if (source.isFaceDown() && source.isInZone(ZoneType.Exile)) {
+ if (!source.isLKI()) {
+ source = CardUtil.getLKICopy(source);
+ }
+
+ source.turnFaceUp(false, false);
+ lkicheck = true;
+ }
+
if (sa.hasParam("Bestow") && !source.isBestowed() && !source.isInZone(ZoneType.Battlefield)) {
if (!source.isLKI()) {
source = CardUtil.getLKICopy(source);
@@ -106,23 +117,16 @@ public final class GameActionUtil {
}
final Card host = o.getHost();
- final SpellAbility newSA = sa.copy(activator);
- final SpellAbilityRestriction sar = newSA.getRestrictions();
- if (o.isWithFlash()) {
- sar.setInstantSpeed(true);
- }
- sar.setZone(null);
- newSA.setMayPlay(o.getAbility());
- newSA.setMayPlayOriginal(sa);
+ SpellAbility newSA = null;
boolean changedManaCost = false;
if (o.getPayManaCost() == PayManaCost.NO) {
+ newSA = sa.copyWithNoManaCost(activator);
newSA.setBasicSpell(false);
- newSA.setPayCosts(newSA.getPayCosts().copyWithNoMana());
changedManaCost = true;
} else if (o.getAltManaCost() != null) {
+ newSA = sa.copyWithManaCostReplaced(activator, o.getAltManaCost());
newSA.setBasicSpell(false);
- newSA.setPayCosts(newSA.getPayCosts().copyWithDefinedMana(o.getAltManaCost()));
changedManaCost = true;
if (host.hasSVar("AsForetoldSplitCMCHack")) {
// TODO: This is a temporary workaround for As Foretold interaction with split cards, better solution needed.
@@ -138,7 +142,17 @@ public final class GameActionUtil {
}
}
}
+ } else {
+ newSA = sa.copy(activator);
}
+ final SpellAbilityRestriction sar = newSA.getRestrictions();
+ if (o.isWithFlash()) {
+ sar.setInstantSpeed(true);
+ }
+ sar.setZone(null);
+ newSA.setMayPlay(o.getAbility());
+ newSA.setMayPlayOriginal(sa);
+
if (changedManaCost) {
if ("0".equals(sa.getParam("ActivationLimit")) && sa.getHostCard().getManaCost().isNoCost()) {
sar.setLimitToCheck(null);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/ManifestEffect.java b/forge-game/src/main/java/forge/game/ability/effects/ManifestEffect.java
index 2e587b0c173..85d39d51eb5 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/ManifestEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/ManifestEffect.java
@@ -2,7 +2,6 @@ package forge.game.ability.effects;
import com.google.common.collect.Sets;
-import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GlobalRuleChange;
import forge.game.ability.AbilityUtils;
@@ -43,7 +42,7 @@ public class ManifestEffect extends SpellAbilityEffect {
//check if lki would be a land entering the battlefield
if (game.getStaticEffects().getGlobalRuleChange(GlobalRuleChange.noLandBattlefield)) {
Card lki = CardUtil.getLKICopy(c);
- lki.setState(CardStateName.FaceDown, false);
+ lki.turnFaceDownNoUpdate();
lki.setManifested(true);
lki.setLastKnownZone(p.getZone(ZoneType.Battlefield));
CardCollection preList = new CardCollection(lki);
diff --git a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java
index 51de7cf354b..1bbe40ac2a3 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/MillEffect.java
@@ -1,6 +1,5 @@
package forge.game.ability.effects;
-import forge.card.CardStateName;
import forge.game.Game;
import forge.game.GameLogEntryType;
import forge.game.ability.AbilityUtils;
@@ -63,8 +62,8 @@ public class MillEffect extends SpellAbilityEffect {
}
for (final Card c : milled) {
c.setExiledWith(host);
- if (facedown) {
- c.setState(CardStateName.FaceDown, true);
+ if (facedown) {
+ c.turnFaceDown(true);
}
}
}
diff --git a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java
index f4c70fd4f85..3b458163b09 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/PlayEffect.java
@@ -14,7 +14,6 @@ import com.google.common.collect.Lists;
import forge.GameCommand;
import forge.StaticData;
import forge.card.CardRulesPredicates;
-import forge.card.CardStateName;
import forge.game.Game;
import forge.game.ability.AbilityFactory;
import forge.game.ability.AbilityUtils;
@@ -153,7 +152,7 @@ public class PlayEffect extends SpellAbilityEffect {
final boolean wasFaceDown;
if (tgtCard.isFaceDown()) {
- tgtCard.setState(CardStateName.Original, false);
+ tgtCard.turnFaceUp(false, false);
wasFaceDown = true;
} else {
wasFaceDown = false;
@@ -165,7 +164,7 @@ public class PlayEffect extends SpellAbilityEffect {
if (optional && !controller.getController().confirmAction(sa, null, TextUtil.concatWithSpace("Do you want to play", TextUtil.addSuffix(tgtCard.toString(),"?")))) {
if (wasFaceDown) {
- tgtCard.setState(CardStateName.FaceDown, false);
+ tgtCard.turnFaceDownNoUpdate();
}
saidNoTo.add(tgtCard);
continue;
diff --git a/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java b/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java
index 7e810439403..bd366ab9e24 100644
--- a/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java
+++ b/forge-game/src/main/java/forge/game/ability/effects/SetStateEffect.java
@@ -7,6 +7,7 @@ import forge.game.GameLogEntryType;
import forge.game.ability.SpellAbilityEffect;
import forge.game.card.Card;
import forge.game.card.CardCollection;
+import forge.game.card.CardUtil;
import forge.game.card.CounterType;
import forge.game.event.GameEventCardStatsChanged;
import forge.game.player.Player;
@@ -73,13 +74,9 @@ public class SetStateEffect extends SpellAbilityEffect {
// facedown cards that are not Permanent, can't turn faceup there
if ("TurnFace".equals(mode) && tgt.isFaceDown() && tgt.isInZone(ZoneType.Battlefield)
&& !tgt.getState(CardStateName.Original).getType().isPermanent()) {
- // need to cache manifest status
- boolean manifested = tgt.isManifested();
- // FIXME setState has to many other Consequences, use LKI?
- tgt.setState(CardStateName.Original, true);
- game.getAction().reveal(new CardCollection(tgt), tgt.getOwner(), true, "Face-down card can't turn face up");
- tgt.setState(CardStateName.FaceDown, true);
- tgt.setManifested(manifested);
+ Card lki = CardUtil.getLKICopy(tgt);
+ lki.turnFaceUp(true, false);
+ game.getAction().reveal(new CardCollection(lki), lki.getOwner(), true, "Face-down card can't turn face up");
continue;
}
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 4d599db2b1e..2bf560661a4 100644
--- a/forge-game/src/main/java/forge/game/card/Card.java
+++ b/forge-game/src/main/java/forge/game/card/Card.java
@@ -2743,7 +2743,7 @@ public class Card extends GameEntity implements Comparable {
}
return result;
}
- public final void setMayPlay(final Player player, final boolean withoutManaCost, final String altManaCost, final boolean withFlash, final boolean grantZonePermissions, final StaticAbility sta) {
+ public final void setMayPlay(final Player player, final boolean withoutManaCost, final Cost altManaCost, final boolean withFlash, final boolean grantZonePermissions, final StaticAbility sta) {
this.mayPlay.put(sta, new CardPlayOption(player, sta, withoutManaCost, altManaCost, withFlash, grantZonePermissions));
}
public final void removeMayPlay(final StaticAbility sta) {
@@ -5803,7 +5803,7 @@ public class Card extends GameEntity implements Comparable {
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
}
- if (isFaceDown() && isInZone(ZoneType.Exile) && !mayPlay(player).isEmpty()) {
+ if (isFaceDown() && isInZone(ZoneType.Exile)) {
for (final SpellAbility sa : getState(CardStateName.Original).getSpellAbilities()) {
abilities.addAll(GameActionUtil.getAlternativeCosts(sa, player));
}
diff --git a/forge-game/src/main/java/forge/game/card/CardPlayOption.java b/forge-game/src/main/java/forge/game/card/CardPlayOption.java
index 52cd569d72b..3c53c4d0e35 100644
--- a/forge-game/src/main/java/forge/game/card/CardPlayOption.java
+++ b/forge-game/src/main/java/forge/game/card/CardPlayOption.java
@@ -1,9 +1,8 @@
package forge.game.card;
-import forge.card.mana.ManaCost;
-import forge.card.mana.ManaCostParser;
import org.apache.commons.lang3.StringUtils;
+import forge.game.cost.Cost;
import forge.game.player.Player;
import forge.game.staticability.StaticAbility;
@@ -20,12 +19,12 @@ public final class CardPlayOption {
private final PayManaCost payManaCost;
private final boolean withFlash;
private final boolean grantsZonePermissions;
- private final String altManaCost;
+ private final Cost altManaCost;
- public CardPlayOption(final Player player, final StaticAbility sta, final boolean withoutManaCost, final String altManaCost, final boolean withFlash, final boolean grantZonePermissions) {
+ public CardPlayOption(final Player player, final StaticAbility sta, final boolean withoutManaCost, final Cost altManaCost, final boolean withFlash, final boolean grantZonePermissions) {
this(player, sta, withoutManaCost ? PayManaCost.NO : PayManaCost.YES, altManaCost, withFlash, grantZonePermissions);
}
- private CardPlayOption(final Player player, final StaticAbility sta, final PayManaCost payManaCost, final String altManaCost, final boolean withFlash,
+ private CardPlayOption(final Player player, final StaticAbility sta, final PayManaCost payManaCost, final Cost altManaCost, final boolean withFlash,
final boolean grantZonePermissions) {
this.player = player;
this.sta = sta;
@@ -66,10 +65,10 @@ public final class CardPlayOption {
public boolean grantsZonePermissions() { return grantsZonePermissions; }
- public String getAltManaCost() { return altManaCost; }
+ public Cost getAltManaCost() { return altManaCost; }
private String getFormattedAltManaCost() {
- return new ManaCost(new ManaCostParser(altManaCost)).getSimpleString();
+ return altManaCost.toSimpleString();
}
@Override
diff --git a/forge-game/src/main/java/forge/game/card/CardView.java b/forge-game/src/main/java/forge/game/card/CardView.java
index fd263817f20..e884a316500 100644
--- a/forge-game/src/main/java/forge/game/card/CardView.java
+++ b/forge-game/src/main/java/forge/game/card/CardView.java
@@ -371,13 +371,18 @@ public class CardView extends GameEntityView {
switch (zone) {
case Ante:
case Command:
- case Exile:
case Battlefield:
case Graveyard:
case Flashback:
case Stack:
//cards in these zones are visible to all
return true;
+ case Exile:
+ //in exile, only face up cards and face down cards you can look at should be shown (since "exile face down" is a thing)
+ if (!isFaceDown()) {
+ return true;
+ }
+ break;
case Hand:
if (controller.hasKeyword("Play with your hand revealed.")) {
return true;
@@ -628,6 +633,7 @@ public class CardView extends GameEntityView {
set(TrackableProperty.Cloned, c.isCloned());
set(TrackableProperty.SplitCard, isSplitCard);
set(TrackableProperty.FlipCard, c.isFlipCard());
+ set(TrackableProperty.Facedown, c.isFaceDown());
final Card cloner = c.getCloner();
diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
index 6a790b4f343..13c6965e12f 100644
--- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
+++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
@@ -933,6 +933,18 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return copyWithDefinedCost(new Cost(abCost, isAbility()));
}
+ public SpellAbility copyWithManaCostReplaced(Player active, Cost abCost) {
+
+ final SpellAbility newSA = copy(active);
+ if (newSA == null) {
+ return null; // the ability was not copyable, e.g. a Suspend SA may get here
+ }
+ final Cost newCost = newSA.getPayCosts().copyWithNoMana();
+ newCost.add(abCost);
+ newSA.setPayCosts(newCost);
+ return newSA;
+ }
+
public boolean isTrigger() {
return trigger;
}
diff --git a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java
index 6097d7c0669..c73f494b7ae 100644
--- a/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java
+++ b/forge-game/src/main/java/forge/game/staticability/StaticAbilityContinuous.java
@@ -6,12 +6,12 @@
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
@@ -639,7 +639,7 @@ public final class StaticAbilityContinuous {
affectedCard.addReplacementEffect(actualRep).setTemporary(true);;
}
}
-
+
// add Types
if ((addTypes != null) || (removeTypes != null)) {
affectedCard.addChangedCardTypes(addTypes, removeTypes, removeSuperTypes, removeCardTypes,
@@ -667,7 +667,7 @@ public final class StaticAbilityContinuous {
// set OriginalHost to get the owner of this static ability
sa.setOriginalHost(hostCard);
// set overriding ability to the trigger
- actualTrigger.setOverridingAbility(sa);
+ actualTrigger.setOverridingAbility(sa);
}
actualTrigger.setIntrinsic(false);
affectedCard.addTrigger(actualTrigger).setTemporary(true);
@@ -735,10 +735,19 @@ public final class StaticAbilityContinuous {
if (withFlash != null) {
affectedCard.addWithFlash(se.getTimestamp(), withFlash);
}
-
+
if (controllerMayPlay && (mayPlayLimit == null || stAb.getMayPlayTurn() < mayPlayLimit)) {
+ String mayPlayAltCost = mayPlayAltManaCost;
+
+ if (mayPlayAltCost != null && mayPlayAltCost.contains("ConvertedManaCost")) {
+ final String costcmc = Integer.toString(affectedCard.getCMC());
+ mayPlayAltCost = mayPlayAltCost.replace("ConvertedManaCost", costcmc);
+ }
+
Player mayPlayController = params.containsKey("MayPlayCardOwner") ? affectedCard.getOwner() : controller;
- affectedCard.setMayPlay(mayPlayController, mayPlayWithoutManaCost, mayPlayAltManaCost, mayPlayWithFlash, mayPlayGrantZonePermissions, stAb);
+ affectedCard.setMayPlay(mayPlayController, mayPlayWithoutManaCost,
+ mayPlayAltCost != null ? new Cost(mayPlayAltCost, false) : null,
+ mayPlayWithFlash, mayPlayGrantZonePermissions, stAb);
}
}
diff --git a/forge-gui-android/pom.xml b/forge-gui-android/pom.xml
index a2736015655..5633bacc26c 100644
--- a/forge-gui-android/pom.xml
+++ b/forge-gui-android/pom.xml
@@ -6,7 +6,7 @@
jar
-Xms1024m
-Xmx1536m
- 1.6.24.001
+ 1.6.25.001
keystore
alias
storepass
@@ -19,7 +19,7 @@
forge
forge
- 1.6.25-SNAPSHOT
+ 1.6.26-SNAPSHOT
forge-gui-android
diff --git a/forge-gui-desktop/pom.xml b/forge-gui-desktop/pom.xml
index f15c0200d06..13d08231132 100644
--- a/forge-gui-desktop/pom.xml
+++ b/forge-gui-desktop/pom.xml
@@ -4,7 +4,7 @@
forge
forge
- 1.6.25-SNAPSHOT
+ 1.6.26-SNAPSHOT
forge-gui-desktop
diff --git a/forge-gui-ios/pom.xml b/forge-gui-ios/pom.xml
index 1fb892305c9..bdd558aacb8 100644
--- a/forge-gui-ios/pom.xml
+++ b/forge-gui-ios/pom.xml
@@ -6,13 +6,13 @@
jar
-Xms128m
-Xmx2048m
- 1.6.24.001
+ 1.6.25.001
forge
forge
- 1.6.25-SNAPSHOT
+ 1.6.26-SNAPSHOT
forge-gui-ios
diff --git a/forge-gui-mobile-dev/pom.xml b/forge-gui-mobile-dev/pom.xml
index 351ccbfe15a..6aaa483d474 100644
--- a/forge-gui-mobile-dev/pom.xml
+++ b/forge-gui-mobile-dev/pom.xml
@@ -4,7 +4,7 @@
forge
forge
- 1.6.25-SNAPSHOT
+ 1.6.26-SNAPSHOT
forge-gui-mobile-dev
diff --git a/forge-gui-mobile/pom.xml b/forge-gui-mobile/pom.xml
index f0f247a1f8c..d0e1c628b54 100644
--- a/forge-gui-mobile/pom.xml
+++ b/forge-gui-mobile/pom.xml
@@ -4,7 +4,7 @@
forge
forge
- 1.6.25-SNAPSHOT
+ 1.6.26-SNAPSHOT
forge-gui-mobile
diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java
index 044f3cf9bd5..8594963adcb 100644
--- a/forge-gui-mobile/src/forge/Forge.java
+++ b/forge-gui-mobile/src/forge/Forge.java
@@ -34,7 +34,7 @@ import java.util.List;
import java.util.Stack;
public class Forge implements ApplicationListener {
- public static final String CURRENT_VERSION = "1.6.24.001";
+ public static final String CURRENT_VERSION = "1.6.25.001";
private static final ApplicationListener app = new Forge();
private static Clipboard clipboard;
diff --git a/forge-gui/pom.xml b/forge-gui/pom.xml
index 8a781f93577..e2602b9e7d3 100644
--- a/forge-gui/pom.xml
+++ b/forge-gui/pom.xml
@@ -4,7 +4,7 @@
forge
forge
- 1.6.25-SNAPSHOT
+ 1.6.26-SNAPSHOT
forge-gui
diff --git a/forge-gui/release-files/ANNOUNCEMENTS.txt b/forge-gui/release-files/ANNOUNCEMENTS.txt
index 3c4f31a98f1..6029ab366c0 100644
--- a/forge-gui/release-files/ANNOUNCEMENTS.txt
+++ b/forge-gui/release-files/ANNOUNCEMENTS.txt
@@ -1,7 +1,5 @@
#Add one announcement per line
-War for the Spark is here! We're still missing a few cards, but hopefully we'll get those added soon.
-A very drastic change has happened for tokens and token images. Check out the information here: https://www.slightlymagic.net/forum/viewtopic.php?f=26&t=23317
+War for the Spark is all added. I'm sure we didn't get all of the bugs fully worked out, so please let us know if you see anything weird.
A significant improvement was made to how we cache images which should improve memory usage
[b]Forge now requires Java 8 (or newer). You will not be able to start the game if you are not yet running Java 8.[/b]
-We have a Discord server for hanging out with Forge devs and other Forge fans. Feel free to [url=https://discord.gg/3v9JCVr]jump on in and say hi[/url]!
-Online Multiplayer is once again functional!
\ No newline at end of file
+We have a Discord server for hanging out with Forge devs and other Forge fans. Feel free to [url=https://discord.gg/3v9JCVr]jump on in and say hi[/url]!
\ No newline at end of file
diff --git a/forge-gui/release-files/CONTRIBUTORS.txt b/forge-gui/release-files/CONTRIBUTORS.txt
index f6b81a3eff8..c0ee8d0b2d3 100644
--- a/forge-gui/release-files/CONTRIBUTORS.txt
+++ b/forge-gui/release-files/CONTRIBUTORS.txt
@@ -3,6 +3,7 @@ Austinio7116
Churrufli
DrDev
excessum
+Flair
Gos
Hanmac
Indigo Dragon
diff --git a/forge-gui/release-files/INSTALLATION.txt b/forge-gui/release-files/INSTALLATION.txt
index 75aecb66140..1697fce20b3 100644
--- a/forge-gui/release-files/INSTALLATION.txt
+++ b/forge-gui/release-files/INSTALLATION.txt
@@ -7,7 +7,7 @@ Some people use the Windows application 7zip. This utility can be found at http:
Once the Forge archive has been decompressed you should then be able to launch Forge by using the included launcher. Launching Forge by double clicking on the forge jar file in the past caused a java heap space error. Forge's memory requirements have increased over time and the launchers increase the java heap space available to Forge. Currently you can launch Forge by double clicking on the forge jar file without a java heap space error but this is likely to change as we add in more sounds, icons, etc.
- The Mac OS application version -
-We haven't been able to distribute the OS X Application version of Forge in sometime. We've recently automated our release tools, and will continue to look in the viability of creating this file now that things are autoamted.
+We provide separate macOS builds of desktop and mobile (backported) Forge packaged as native Mac applications. Please check the relevant thread for details and download links.
- Online Multiplayer -
diff --git a/forge-gui/res/ai/Cautious.ai b/forge-gui/res/ai/Cautious.ai
index 2cc850ed726..f276aa1c9c9 100644
--- a/forge-gui/res/ai/Cautious.ai
+++ b/forge-gui/res/ai/Cautious.ai
@@ -252,6 +252,14 @@ BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF=200
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF=3
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=3
+# Use a blink spell to reload a planeswalker's loyalty (e.g. Teferi's Time Twist)
+# A chance (per check) to activate this ability
+BLINK_RELOAD_PLANESWALKER_CHANCE=15
+# Maximum loyalty at which the planeswalker needs to be in order to activate the blink-reload
+BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY=2
+# The difference between maximum loyalty and base loyalty of the planeswalker in order to consider blink-reloading it
+BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF=2
+
# If enabled, the AI will try to pair up cards to present to the opponent so that a specific card may be picked,
# it'll also try to grab Accumulated Knowledge and Take Inventory more actively, as well as interact with the Trix
# combo deck more appropriately. In Reanimator decks, this logic will make the AI pick the fattest threats in the
diff --git a/forge-gui/res/ai/Default.ai b/forge-gui/res/ai/Default.ai
index 9588dcf87fa..3e273e52d5b 100644
--- a/forge-gui/res/ai/Default.ai
+++ b/forge-gui/res/ai/Default.ai
@@ -253,6 +253,14 @@ BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF=200
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF=3
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=3
+# Use a blink spell to reload a planeswalker's loyalty (e.g. Teferi's Time Twist)
+# A chance (per check) to activate this ability
+BLINK_RELOAD_PLANESWALKER_CHANCE=30
+# Maximum loyalty at which the planeswalker needs to be in order to activate the blink-reload
+BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY=2
+# The difference between maximum loyalty and base loyalty of the planeswalker in order to consider blink-reloading it
+BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF=2
+
# If enabled, the AI will try to pair up cards to present to the opponent so that a specific card may be picked,
# it'll also try to grab Accumulated Knowledge and Take Inventory more actively, as well as interact with the Trix
# combo deck more appropriately. In Reanimator decks, this logic will make the AI pick the fattest threats in the
diff --git a/forge-gui/res/ai/Experimental.ai b/forge-gui/res/ai/Experimental.ai
index 8d3cf6fdeeb..7a5a6fb88fa 100644
--- a/forge-gui/res/ai/Experimental.ai
+++ b/forge-gui/res/ai/Experimental.ai
@@ -253,6 +253,14 @@ BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF=400
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF=5
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=5
+# Use a blink spell to reload a planeswalker's loyalty (e.g. Teferi's Time Twist)
+# A chance (per check) to activate this ability
+BLINK_RELOAD_PLANESWALKER_CHANCE=70
+# Maximum loyalty at which the planeswalker needs to be in order to activate the blink-reload
+BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY=2
+# The difference between maximum loyalty and base loyalty of the planeswalker in order to consider blink-reloading it
+BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF=1
+
# If enabled, the AI will try to pair up cards to present to the opponent so that a specific card may be picked,
# it'll also try to grab Accumulated Knowledge and Take Inventory more actively, as well as interact with the Trix
# combo deck more appropriately. In Reanimator decks, this logic will make the AI pick the fattest threats in the
diff --git a/forge-gui/res/ai/Reckless.ai b/forge-gui/res/ai/Reckless.ai
index d6c1c08e5c5..6ce96c9e468 100644
--- a/forge-gui/res/ai/Reckless.ai
+++ b/forge-gui/res/ai/Reckless.ai
@@ -253,6 +253,14 @@ BOUNCE_ALL_ELSEWHERE_CREAT_EVAL_DIFF=200
BOUNCE_ALL_TO_HAND_NONCREAT_EVAL_DIFF=3
BOUNCE_ALL_ELSEWHERE_NONCREAT_EVAL_DIFF=3
+# Use a blink spell to reload a planeswalker's loyalty (e.g. Teferi's Time Twist)
+# A chance (per check) to activate this ability
+BLINK_RELOAD_PLANESWALKER_CHANCE=60
+# Maximum loyalty at which the planeswalker needs to be in order to activate the blink-reload
+BLINK_RELOAD_PLANESWALKER_MAX_LOYALTY=2
+# The difference between maximum loyalty and base loyalty of the planeswalker in order to consider blink-reloading it
+BLINK_RELOAD_PLANESWALKER_LOYALTY_DIFF=1
+
# If enabled, the AI will try to pair up cards to present to the opponent so that a specific card may be picked,
# it'll also try to grab Accumulated Knowledge and Take Inventory more actively, as well as interact with the Trix
# combo deck more appropriately. In Reanimator decks, this logic will make the AI pick the fattest threats in the
diff --git a/forge-gui/res/cardsfolder/b/bant_sojourners.txt b/forge-gui/res/cardsfolder/b/bant_sojourners.txt
index 2c53d45b060..1c4b986fe4d 100644
--- a/forge-gui/res/cardsfolder/b/bant_sojourners.txt
+++ b/forge-gui/res/cardsfolder/b/bant_sojourners.txt
@@ -4,7 +4,7 @@ Types:Creature Human Soldier
PT:2/4
K:Cycling:2 W
T:Mode$ Cycled | ValidCard$ Card.Self | Execute$ TrigToken | OptionalDecider$ You | TriggerDescription$ When you cycle CARDNAME or it dies, you may create a 1/1 white Soldier creature token.
-T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigToken | OptionalDecider$ You | Secondary$ True | TriggerDescription$ When you cycle CARDNAME or it dies, you may create a 1/1 white Soldier creature token.
+T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | Execute$ TrigToken | OptionalDecider$ You | TriggerController$ TriggeredCardController | Secondary$ True | TriggerDescription$ When you cycle CARDNAME or it dies, you may create a 1/1 white Soldier creature token.
SVar:TrigToken:DB$ Token | TokenAmount$ 1 | TokenScript$ w_1_1_soldier | TokenOwner$ You | LegacyImage$ w 1 1 soldier arb
SVar:Picture:http://www.wizards.com/global/images/magic/general/bant_sojourners.jpg
Oracle:When you cycle Bant Sojourners or it dies, you may create a 1/1 white Soldier creature token.\nCycling {2}{W} ({2}{W}, Discard this card: Draw a card.)
diff --git a/forge-gui/res/cardsfolder/b/bolass_citadel.txt b/forge-gui/res/cardsfolder/b/bolass_citadel.txt
new file mode 100644
index 00000000000..c5dd4fc7dd4
--- /dev/null
+++ b/forge-gui/res/cardsfolder/b/bolass_citadel.txt
@@ -0,0 +1,7 @@
+Name:Bolas's Citadel
+ManaCost:3 B B B
+Types:Legendary Artifact
+S:Mode$ Continuous | Affected$ Card.TopLibrary+YouCtrl | AffectedZone$ Library | MayLookAt$ You | Description$ You may look at the top card of your library any time.
+S:Mode$ Continuous | Affected$ Card.TopLibrary+YouCtrl | AffectedZone$ Library | MayPlay$ True | MayPlayAltManaCost$ PayLife | Description$ You may play the top card of your library. If you cast a spell this way, pay life equal to its converted mana cost rather than pay its mana cost.
+A:AB$ LoseLife | Cost$ T Sac<10/Permanent.nonLand/nonland permanent> | Defined$ Player.Opponent | LifeAmount$ 10 | SpellDescription$ Each opponent loses 10 life.
+Oracle:You may look at the top card of your library any time.\nYou may play the top card of your library. If you cast a spell this way, pay life equal to its converted mana cost rather than pay its mana cost.\n{T}, Sacrifice ten nonland permanents: Each opponent loses 10 life.
diff --git a/forge-gui/res/cardsfolder/f/finale_of_revelation.txt b/forge-gui/res/cardsfolder/f/finale_of_revelation.txt
index 1587abbf755..7f886e2e154 100644
--- a/forge-gui/res/cardsfolder/f/finale_of_revelation.txt
+++ b/forge-gui/res/cardsfolder/f/finale_of_revelation.txt
@@ -2,7 +2,7 @@ Name:Finale of Revelation
ManaCost:X U U
Types:Sorcery
A:SP$ ChangeZoneAll | Cost$ X U U | ChangeType$ Card | Origin$ Graveyard | Destination$ Library | Shuffle$ True | Random$ True | SubAbility$ DBDraw | UseAllOriginZones$ True | References$ X | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10 | SpellDescription$ Draw X cards. If X is 10 or more, instead shuffle your graveyard into your library, draw X cards, untap up to five lands, and you have no maximum hand size for the rest of the game.
-SVar:DBDraw:DB$ Draw | NumCards$ X | References$ X | SubAbility$ DBUntap
+SVar:DBDraw:DB$ Draw | NumCards$ X | References$ X | SubAbility$ DBUntap | AILogic$ ConsiderPrimary
SVar:DBUntap:DB$ Untap | UntapUpTo$ True | UntapType$ Land | Amount$ 5 | SubAbility$ DBEffect | References$ X | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10
SVar:DBEffect:DB$ Effect | Name$ Finale of Revelation Effect | StaticAbilities$ STHandSize | Duration$ Permanent | SubAbility$ DBChange | References$ X | ConditionCheckSVar$ X | ConditionSVarCompare$ GE10
SVar:STHandSize:Mode$ Continuous | EffectZone$ Command | Affected$ You | SetMaxHandSize$ Unlimited | Description$ You have no maximum hand size.
diff --git a/forge-gui/res/cardsfolder/g/god_eternal_rhonas.txt b/forge-gui/res/cardsfolder/g/god_eternal_rhonas.txt
index 652f941ca2d..3b123d9c670 100644
--- a/forge-gui/res/cardsfolder/g/god_eternal_rhonas.txt
+++ b/forge-gui/res/cardsfolder/g/god_eternal_rhonas.txt
@@ -12,4 +12,5 @@ T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$
SVar:TrigFromGraveyard:DB$ChangeZone | Defined$ TriggeredCard | Origin$ Graveyard | Destination$ Library | LibraryPosition$ 2
T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Exile | ValidCard$ Card.Self | Execute$ TrigFromExile | OptionalDecider$ You | TriggerController$ TriggeredCardController | Secondary$ True | TriggerDescription$ When CARDNAME dies or is put into exile from the battlefield, you may put it into its owner's library third from the top.
SVar:TrigFromExile:DB$ChangeZone | Defined$ TriggeredCard | Origin$ Exile | Destination$ Library | LibraryPosition$ 2
+SVar:PlayMain1:TRUE
Oracle:Deathtouch\nWhen God-Eternal Rhonas enters the battlefield, double the power of each other creature you control until end of turn. Those creatures gain vigilance until end of turn.\nWhen God-Eternal Rhonas dies or is put into exile from the battlefield, you may put it into its owner's library third from the top.
diff --git a/forge-gui/res/cardsfolder/k/kasmina_enigmatic_mentor.txt b/forge-gui/res/cardsfolder/k/kasmina_enigmatic_mentor.txt
index 0fdf8142a31..5c9d0e12c6d 100644
--- a/forge-gui/res/cardsfolder/k/kasmina_enigmatic_mentor.txt
+++ b/forge-gui/res/cardsfolder/k/kasmina_enigmatic_mentor.txt
@@ -3,7 +3,7 @@ ManaCost:3 U
Types:Legendary Planeswalker Kasmina
Loyalty:5
S:Mode$ RaiseCost | ValidTarget$ Creature.YouOwn+inZoneBattlefield | Activator$ Player.Opponent | Type$ Spell | Amount$ 2 | Description$ Spells your opponents cast that target a creature or planeswalker you control cost {2} more to cast.
-A:AB$ Token | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ u_2_2_Wizard | TokenOwner$ You | LegacyImage$ u 2 2 Wizard war | SubAbility$ DBDraw | SpellDescription$ Create a 2/2 blue Wizard creature token. Draw a card, then discard a card.
+A:AB$ Token | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | TokenAmount$ 1 | TokenScript$ u_2_2_wizard | TokenOwner$ You | LegacyImage$ u 2 2 Wizard war | SubAbility$ DBDraw | SpellDescription$ Create a 2/2 blue Wizard creature token. Draw a card, then discard a card.
SVar:DBDraw:DB$Draw | NumCards$ 1 | SubAbility$ DBDiscard
SVar:DBDiscard:DB$Discard | Defined$ You | Mode$ TgtChoose | NumCards$ 1
AI:RemoveDeck:Random
diff --git a/forge-gui/res/cardsfolder/s/sorin_vengeful_bloodlord.txt b/forge-gui/res/cardsfolder/s/sorin_vengeful_bloodlord.txt
index f93754c8ea8..11291bb3ee1 100644
--- a/forge-gui/res/cardsfolder/s/sorin_vengeful_bloodlord.txt
+++ b/forge-gui/res/cardsfolder/s/sorin_vengeful_bloodlord.txt
@@ -6,7 +6,7 @@ S:Mode$ Continuous | Affected$ Creature.YouCtrl,Planeswalker.YouCtrl | AddKeywor
SVar:NonStackingEffect:True
A:AB$ DealDamage | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | ValidTgts$ Player,Planeswalker | TgtPrompt$ Select target player or planeswalker | NumDmg$ 1 | SpellDescription$ CARDNAME deals 1 damage to target player or planeswalker.
A:AB$ ChangeZone | Cost$ SubCounter | Planeswalker$ True | Origin$ Graveyard | Destination$ Battlefield | Announce$ X | References$ X | ValidTgts$ Creature.YouOwn | TgtPrompt$ Select target creature with converted mana cost X from your graveyard | SubAbility$ Animate | SpellDescription$ Return target creature card with converted mana cost X from your graveyard to the battlefield. That creature is a Vampire in addition to its other types.
-SVar:Animate:DB$Animate | Defined$ Targeted | Types$ Vampire | Keywords$ Animate | Permanent$ True
+SVar:Animate:DB$ Animate | Defined$ Targeted | Types$ Vampire | Permanent$ True
SVar:X:Targeted$CardManaCost
AI:RemoveDeck:All
Oracle:As long as it's your turn, creatures and planeswalkers you control have lifelink.\n[+2]: Sorin, Vengeful Bloodlord deals 1 damage to target player or planeswalker.\n-X: Return target creature card with converted mana cost X from your graveyard to the battlefield. That creature is a Vampire in addition to its other types.
diff --git a/forge-gui/res/cardsfolder/s/storm_elemental.txt b/forge-gui/res/cardsfolder/s/storm_elemental.txt
index f4161f70e5c..c1cd409f89d 100644
--- a/forge-gui/res/cardsfolder/s/storm_elemental.txt
+++ b/forge-gui/res/cardsfolder/s/storm_elemental.txt
@@ -4,7 +4,7 @@ Types:Creature Elemental
PT:3/4
K:Flying
A:AB$ Tap | Cost$ U ExileFromTop<1/Card> | ValidTgts$ Creature.withFlying | TgtPrompt$ Select target creature with flying | SpellDescription$ Tap target creature with flying.
-A:AB$ Pump | Cost$ U ExileFromTop<1/Card> | Defined$ Self | NumAtt$ +X | NumDef$ +X | SpellDescription$ If the exiled card is a snow land, CARDNAME gets +1/+1 until end of turn.
+A:AB$ Pump | Cost$ U ExileFromTop<1/Card> | Defined$ Self | NumAtt$ +X | NumDef$ +X | References$ X | SpellDescription$ If the exiled card is a snow land, CARDNAME gets +1/+1 until end of turn.
SVar:X:Exiled$Valid Land.Snow
SVar:Picture:http://www.wizards.com/global/images/magic/general/storm_elemental.jpg
Oracle:Flying\n{U}, Exile the top card of your library: Tap target creature with flying.\n{U}, Exile the top card of your library: If the exiled card is a snow land, Storm Elemental gets +1/+1 until end of turn.
diff --git a/forge-gui/res/cardsfolder/t/teferis_time_twist.txt b/forge-gui/res/cardsfolder/t/teferis_time_twist.txt
new file mode 100644
index 00000000000..0d474640a75
--- /dev/null
+++ b/forge-gui/res/cardsfolder/t/teferis_time_twist.txt
@@ -0,0 +1,10 @@
+Name:Teferi's Time Twist
+ManaCost:1 U
+Types:Instant
+A:SP$ ChangeZone | Cost$ 1 U | ValidTgts$ Permanent.YouCtrl | TgtPrompt$ Select target permanent you control | Origin$ Battlefield | Destination$ Exile | RememberChanged$ True | AILogic$ DelayedBlink | SubAbility$ DBAnimate | SpellDescription$ Exile target permanent you control. Return that card to the battlefield under its owner’s control at the beginning of the next end step. If it enters the battlefield as a creature, it enters with an additional +1/+1 counter on it.
+SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Keywords$ etbCounter:P1P1:1:ValidCard$ Creature.IsRemembered | RememberObjects$ Remembered | SubAbility$ DelTrig | AILogic$ Always
+SVar:DelTrig:DB$ DelayedTrigger | Mode$ Phase | Phase$ End of Turn | Execute$ TrigBounce | RememberObjects$ Remembered | AILogic$ Always | TriggerDescription$ Return that card to the battlefield under its owner’s control at the beginning of the next end step. If it enters the battlefield as a creature, it enters with an additional +1/+1 counter on it.
+SVar:TrigBounce:DB$ ChangeZone | Origin$ Exile | Destination$ Battlefield | Defined$ DelayTriggerRemembered
+SVar:Picture:https://gatherer.wizards.com/Handlers/Image.ashx?multiverseid=460999&type=card
+DeckHas:Ability$Counters
+Oracle:Exile target permanent you control. Return that card to the battlefield under its owner's control at the beginning of the next end step. If it enters the battlefield as a creature, it enters with an additional +1/+1 counter on it.
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/u/ugin_the_ineffable.txt b/forge-gui/res/cardsfolder/u/ugin_the_ineffable.txt
new file mode 100644
index 00000000000..edb7058a944
--- /dev/null
+++ b/forge-gui/res/cardsfolder/u/ugin_the_ineffable.txt
@@ -0,0 +1,16 @@
+Name:Ugin, the Ineffable
+ManaCost:6
+Types:Legendary Planeswalker Ugin
+Loyalty:4
+S:Mode$ ReduceCost | ValidCard$ Card.Colorless | Type$ Spell | Activator$ You | Amount$ 2 | Description$ Colorless spells you cast cost {2} less to cast.
+A:AB$ PeekAndReveal | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | PeekAmount$ 1 | NoReveal$ True | SubAbility$ DBMill | StackDescription$ SpellDescription | SpellDescription$ Exile the top card of your library face down and look at it. Create a 2/2 colorless Spirit creature token. When that token leaves the battlefield, put the exiled card into your hand.
+SVar:DBMill:DB$ Mill | Defined$ You | NumCards$ 1 | Destination$ Exile | RememberMilled$ True | ExileFaceDown$ True | NoReveal$ True | SubAbility$ DBToken
+SVar:DBToken:DB$ Token | TokenScript$ c_2_2_spirit | ImprintTokens$ True | SubAbility$ DBEffect
+SVar:DBEffect:DB$ Effect | Triggers$ TrigLeavesBattlefield | SVars$ DBReturn,DBExile | RememberObjects$ Remembered | ImprintCards$ Imprinted | SubAbility$ DBCleanup
+SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True | ClearImprinted$ True
+SVar:TrigLeavesBattlefield:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.IsImprinted | Execute$ DBReturn | TriggerDescription$ When that token leaves the battlefield, put the exiled card into your hand.
+SVar:DBReturn:DB$ ChangeZoneAll | Origin$ Exile | Destination$ Hand | ChangeType$ Card.IsRemembered | SubAbility$ DBExile
+SVar:DBExile:DB$ ChangeZoneAll | Origin$ Command | Destination$ Exile | ChangeType$ Card.Self
+A:AB$ Destroy | Cost$ SubCounter<3/LOYALTY> | ValidTgts$ Permanent.nonColorless | TgtPrompt$ Select target permanent that's one or more colors | Planeswalker$ True | SpellDescription$ Destroy target permanent that’s one or more colors.
+DeckHas:Ability$Token
+Oracle:Colorless spells you cast cost {2} less to cast.\n+1: Exile the top card of your library face down and look at it. Create a 2/2 colorless Spirit creature token. When that token leaves the battlefield, put the exiled card into your hand.\n−3: Destroy target permanent that’s one or more colors.
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/upcoming/cabal_therapist.txt b/forge-gui/res/cardsfolder/upcoming/cabal_therapist.txt
new file mode 100644
index 00000000000..a97e8a7fc1d
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/cabal_therapist.txt
@@ -0,0 +1,11 @@
+Name:Cabal Therapist
+ManaCost:B
+Types:Creature Horror
+PT:1/1
+K:Menace
+T:Mode$ Phase | Phase$ Main1 | PreCombatMain$ True | ValidPlayer$ You | Execute$ DBImmediateTrigger | TriggerZones$ Battlefield | TriggerDescription$ At the beginning of your precombat main phase, you may sacrifice a creature. When you do, choose a nonland card name, then target player reveals their hand and discards all cards with that name.
+SVar:DBImmediateTrigger:DB$ ImmediateTrigger | Execute$ NameCard | TriggerDescription$ You may sacrifice a creature. When you do, choose a nonland card name, then target player reveals their hand and discards all cards with that name. | UnlessCost$ Sac<1/Creature> | UnlessPayer$ You | UnlessSwitched$ True
+SVar:NameCard:DB$ NameCard | Defined$ You | ValidCards$ Card.nonLand | ValidDesc$ nonland | SubAbility$ DBDiscard | SpellDescription$ Choose a nonland card name. Target player reveals their hand and discards all cards with that name.
+SVar:DBDiscard:DB$ Discard | ValidTgts$ Player | TgtPrompt$ Select target player | Mode$ RevealDiscardAll | DiscardValid$ Card.NamedCard
+AI:RemoveDeck:All
+Oracle:Menace\nAt the beginning of your precombat main phase, you may sacrifice a creature. When you do, choose a nonland card name, then target player reveals their hand and discards all cards with that name.
diff --git a/forge-gui/res/cardsfolder/upcoming/deep_forest_hermit.txt b/forge-gui/res/cardsfolder/upcoming/deep_forest_hermit.txt
new file mode 100644
index 00000000000..4707ef672be
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/deep_forest_hermit.txt
@@ -0,0 +1,11 @@
+Name:Deep Forest Hermit
+ManaCost:3 G G
+Types:Creature Elf Druid
+K:Vanishing:3
+PT:1/1
+S:Mode$ Continuous | Affected$ Creature.Squirrel+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Squirrel creatures you control get +1/+1.
+T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigToken | TriggerDescription$ When CARDNAME enters the battlefield, create four 1/1 green Squirrel creature tokens.
+SVar:TrigToken:DB$Token | TokenAmount$ 4 | TokenScript$ g_1_1_squirrel | TokenOwner$ You | LegacyImage$ g 1 1 squirrel mh1
+SVar:PlayMain1:TRUE
+SVar:Picture:http://mythicspoiler.com/mh1/cards/deepforesthermit.jpg
+Oracle:Vanishing 3 (This permanent enters the battlefield with three time counters on it. At the beginning of your upkeep, remove a time counter from it. When the last is removed, sacrifice it.)\nWhen Deep Forest Hermit enters the battlefield, create four 1/1 green Squirrel creature tokens.\nSquirrels you control get +1/+1.
diff --git a/forge-gui/res/cardsfolder/upcoming/force_of_negation.txt b/forge-gui/res/cardsfolder/upcoming/force_of_negation.txt
new file mode 100644
index 00000000000..0bfe3e7f8b1
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/force_of_negation.txt
@@ -0,0 +1,7 @@
+Name:Force of Negation
+ManaCost:1 U U
+Types:Instant
+A:SP$ Counter | Cost$ 1 U U | TargetType$ Spell | TgtPrompt$ Select target nonCreature Spell | ValidTgts$ Card.nonCreature | Destination$ Exile | SpellDescription$ Counter target noncreature spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyayrd.
+SVar:AltCost:Cost$ ExileFromHand<1/Card.Blue> | OpponentTurn$ True | Description$ If it's not your turn, you may exile a blue card from your hand rather than pay this spell's mana cost.
+Svar:Picture:http://mythicspoiler.com/mh1/cards/forceofnegation.jpg
+Oracle:If it's not your turn, you may exile a blue card from your hand rather than pay this spell's mana cost.\nCounter target noncreature spell. If that spell is countered this way, exile it instead of putting it into its owner's graveyayrd.
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/upcoming/force_of_vigor.txt b/forge-gui/res/cardsfolder/upcoming/force_of_vigor.txt
new file mode 100644
index 00000000000..cbf697bb3a6
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/force_of_vigor.txt
@@ -0,0 +1,6 @@
+Name:Force of Vigor
+ManaCost:2 G G
+Types:Instant
+A:SP$ Destroy | Cost$ 2 G G | TargetMin$ 0 | TargetMax$ 2 | ValidTgts$ Artifact,Enchantment | TgtPrompt$ Select target artifact or enchantment | SpellDescription$ Destroy up to two target artifacts and/or enchantments.
+SVar:AltCost:Cost$ ExileFromHand<1/Card.Green> | OpponentTurn$ True | Description$ If it's not your turn, you may exile a green card from your hand rather than pay this spell's mana cost.
+Oracle:If it's not your turn, you may exile a green card from your hand rather than pay this spell's mana cost.\nDestroy up to two target artifacts and/or enchantments.
diff --git a/forge-gui/res/cardsfolder/upcoming/goblin_war_party.txt b/forge-gui/res/cardsfolder/upcoming/goblin_war_party.txt
new file mode 100644
index 00000000000..040a6c96fc8
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/goblin_war_party.txt
@@ -0,0 +1,8 @@
+Name:Goblin War Party
+ManaCost:3 R
+Types:Sorcery
+K:Entwine:2 R
+A:SP$ Charm | Cost$ 3 R | Choices$ DBTokens,DBBuff | Defined$ You | CharmNum$ 1
+SVar:DBTokens:DB$ Token | TokenAmount$ 3 | TokenScript$ r_1_1_goblin | TokenOwner$ You | LegacyImage$ r 1 1 goblin mh1 | SpellDescription$ Create three 1/1 red Goblin creature tokens.
+SVar:DBBuff:DB$ PumpAll | NumAtt$ 1 | NumDef$ 1 | ValidCards$ Creature.YouCtrl | KW$ Haste | SpellDescription$ Creatures you control get +1/+1 and gain haste until end of turn.
+Oracle:Choose one —\n• Create three 1/1 red Goblin creature tokens.\n• Creatures you control get +1/+1 and gain haste until end of turn. \nEntwine {2}{R}(Choose both if you pay the entwine cost.)
diff --git a/forge-gui/res/cardsfolder/upcoming/headless_specter.txt b/forge-gui/res/cardsfolder/upcoming/headless_specter.txt
new file mode 100644
index 00000000000..b15119f6612
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/headless_specter.txt
@@ -0,0 +1,8 @@
+Name:Headless Specter
+ManaCost:1 B B
+Types:Creature Specter
+PT:2/2
+K:Flying
+T:Mode$ DamageDone | ValidSource$ Card.Self | ValidTarget$ Player | CombatDamage$ True | Hellbent$ True | Execute$ TrigDiscard | TriggerZones$ Battlefield | TriggerDescription$ Hellbent Whenever CARDNAME deals combat damage to a player, if you have no cards in hand, that player discards a card at random.
+SVar:TrigDiscard:DB$ Discard | Defined$ TriggeredTarget | NumCards$ 1 | Mode$ Random
+Oracle:Flying\nHellbent — Whenever Hellbent Specter deals combat damage to a player, if you have no cards in hand, that player discards a card at random.
diff --git a/forge-gui/res/cardsfolder/upcoming/ice_fang_coatl.txt b/forge-gui/res/cardsfolder/upcoming/ice_fang_coatl.txt
new file mode 100644
index 00000000000..bc62f5e8dbb
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/ice_fang_coatl.txt
@@ -0,0 +1,12 @@
+Name:Ice-Fang Coatl
+ManaCost:G U
+Types:Snow Creature Snake
+PT:1/1
+K:Flash
+K:Flying
+T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigDraw | TriggerDescription$ When CARDNAME enters the battlefield, draw a card.
+SVar:TrigDraw:DB$Draw | Defined$ You | NumCards$ 1
+S:Mode$ Continuous | Affected$ Card.Self | AddKeyword$ Deathtouch | CheckSVar$ X | SVarCompare$ GE3 | Description$ CARDNAME has deathtouch as long as you control at least three other snow permanents.
+SVar:X:Count$Valid Permanent.Snow+Other
+SVar:BuffedBy:Permanent.Snow
+Oracle:Flash\nFlying\nWhen Ice-Fang Coatl enters the battlefield, draw a card.\nIce-Fang Coatl has deathtouch as long as you control at least three other snow permanents.
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/upcoming/impostor_of_the_sixth_pride.txt b/forge-gui/res/cardsfolder/upcoming/impostor_of_the_sixth_pride.txt
new file mode 100644
index 00000000000..b3ad474e6a9
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/impostor_of_the_sixth_pride.txt
@@ -0,0 +1,6 @@
+Name:Impostor of the Sixth Pride
+ManaCost:1 W
+Types:Creature Shapeshifter
+PT:3/1
+K:Changeling
+Oracle:Changeling (This card is every creature type.)
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/upcoming/morophon_the_boundless.txt b/forge-gui/res/cardsfolder/upcoming/morophon_the_boundless.txt
new file mode 100644
index 00000000000..9f81a6fc7f7
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/morophon_the_boundless.txt
@@ -0,0 +1,16 @@
+Name:Morophon, the Boundless
+ManaCost:7
+Types:Legendary Creature Shapeshifter
+PT:6/6
+K:Changeling
+K:ETBReplacement:Other:ChooseCT
+SVar:ChooseCT:DB$ ChooseType | Defined$ You | Type$ Creature | SpellDescription$ As CARDNAME enters the battlefield, choose a creature type. | StackDescription$ SpellDescription
+AI:RemoveDeck:Random
+S:Mode$ ReduceCost | ValidCard$ Card.ChosenType | Type$ Spell | Activator$ You | Amount$ 1 | Color$ W | Description$ Spells you cast of the chosen type cost {W}{U}{B}{R}{G} less to cast. This effect reduces only the amount of colored mana you pay.
+S:Mode$ ReduceCost | ValidCard$ Card.ChosenType | Type$ Spell | Activator$ You | Amount$ 1 | Color$ B
+S:Mode$ ReduceCost | ValidCard$ Card.ChosenType | Type$ Spell | Activator$ You | Amount$ 1 | Color$ U
+S:Mode$ ReduceCost | ValidCard$ Card.ChosenType | Type$ Spell | Activator$ You | Amount$ 1 | Color$ R
+S:Mode$ ReduceCost | ValidCard$ Card.ChosenType | Type$ Spell | Activator$ You | Amount$ 1 | Color$ G
+S:Mode$ Continuous | Affected$ Creature.ChosenType+Other+YouCtrl | AddPower$ 1 | AddToughness$ 1 | Description$ Other creatures you control of the chosen type get +1/+1.
+SVar:PlayMain1:TRUE
+Oracle:Changeling (This card is every creature type.)\nAs Morophon, the Boundless enters the battlefield, choose a creature type.\nSpells you cast of the chosen type cost {W}{U}{B}{R}{G} less to cast. This effect reduces only the amount of colored mana you pay.\nOther creatures you control of the chosen type get +1/+1.
\ No newline at end of file
diff --git a/forge-gui/res/cardsfolder/upcoming/prismatic_vista.txt b/forge-gui/res/cardsfolder/upcoming/prismatic_vista.txt
new file mode 100644
index 00000000000..c97a3e2baf5
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/prismatic_vista.txt
@@ -0,0 +1,5 @@
+Name:Prismatic Vista
+ManaCost:
+Types:Land
+A:AB$ ChangeZone | Cost$ T PayLife<1> Sac<1/CARDNAME> | Origin$ Library | Destination$ Battlefield | ChangeType$ Land.Basic | ChangeNum$ 1 | SpellDescription$ Search your library for a basic land card, put it onto the battlefield, then shuffle your library.
+Oracle:{T}, Pay 1 life, Sacrifice Prismatic View: Search your library for a basic land card, put it onto the battlefield, then shuffle your library.
diff --git a/forge-gui/res/cardsfolder/upcoming/savage_swipe.txt b/forge-gui/res/cardsfolder/upcoming/savage_swipe.txt
new file mode 100644
index 00000000000..091b096da5f
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/savage_swipe.txt
@@ -0,0 +1,6 @@
+Name:Savage Swipe
+ManaCost:G
+Types:Sorcery
+A:SP$ Pump | Cost$ G | ValidTgts$ Creature.YouCtrl | AILogic$ Fight | AITgts$ Creature.YouCtrl+powerEQ2 | TgtPrompt$ Select target creature you control | SubAbility$ DBFight | NumAtt$ +2 | NumDef$ +2 | ConditionDefined$ Targeted | ConditionPresent$ Creature.powerEQ2 | SpellDescription$ Target creature you control gets +2/+2 until end of turn. It fights target creature you don't control.
+SVar:DBFight:DB$ Fight | Defined$ ParentTarget | ValidTgts$ Creature.YouDontCtrl | TgtPrompt$ Select target creature you don't control
+Oracle:Target creature you control gets +2/+2 until end of turn if its power is 2. Then it fights target creature you don't control. (Each deals damage equal to its power to the other.)
diff --git a/forge-gui/res/cardsfolder/upcoming/serra_the_benevolent.txt b/forge-gui/res/cardsfolder/upcoming/serra_the_benevolent.txt
new file mode 100644
index 00000000000..86ba1e2eca3
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/serra_the_benevolent.txt
@@ -0,0 +1,11 @@
+Name:Serra the Benevolent
+ManaCost:2 W W
+Types:Legendary Planeswalker Serra
+Loyalty:4
+A:AB$ PumpAll | Cost$ AddCounter<2/LOYALTY> | Planeswalker$ True | ValidCards$ Creature.withFlying+YouCtrl | NumAtt$ +1 | NumDef$ +1 | SpellDescription$ Creatures you control with flying get +1/+1 until end of turn.
+A:AB$ Token | Cost$ SubCounter<3/LOYALTY> | Planeswalker$ True | TokenScript$ w_4_4_angel_flying_vigilance | TokenOwner$ You | LegacyImage$ w 4 4 angel flying vigilance mh1 | SpellDescription$ Create a 4/4 white Angel creature token with flying and vigilance.
+A:AB$ Effect | Cost$ SubCounter<6/LOYALTY> | Name$ Emblem - Serra the Benevolent | Image$ emblem_serra_the_benevolent | StaticAbilities$ STWorship | Planeswalker$ True | Ultimate$ True | Stackable$ False | Duration$ Permanent | AILogic$ Always | SpellDescription$ You get an emblem with "If you control a creature, damage that would reduce your life total to less than 1 reduces it to 1 instead."
+SVar:STWorship:Mode$ Continuous | EffectZone$ Command | Affected$ You | AddKeyword$ DamageLifeThreshold:1 | IsPresent$ Creature.YouCtrl | Description$ If you control a creature, damage that would reduce your life total to less than 1 reduces it to 1 instead.
+DeckHas:Ability$Token
+SVar:Picture:http://www.wizards.com/global/images/magic/general/serra_the_benevolent.jpg
+Oracle:[+2]: Creatures you control with flying get +1/+1 until end of turn.\n[-3]: Create a 4/4 white Angel creature token with flying and vigilance.\n[-6]: You get an emblem with "If you control a creature, damage that would reduce your life total to less than 1 reduces it to 1 instead."
diff --git a/forge-gui/res/cardsfolder/upcoming/stream_of_thought.txt b/forge-gui/res/cardsfolder/upcoming/stream_of_thought.txt
new file mode 100644
index 00000000000..ebd7a385175
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/stream_of_thought.txt
@@ -0,0 +1,7 @@
+Name:Stream of Thought
+ManaCost:U
+Types:Sorcery
+A:SP$ Mill | Cost$ U | NumCards$ 4 | ValidTgts$ Player | TgtPrompt$ Choose a player | RememberMilled$ True | SubAbility$ DBChangeZone | SpellDescription$ Target player puts the top four cards of their library into their graveyard. You shuffle up to four cards from your graveyard into your library.
+SVar:DBChangeZone:DB$ ChangeZone | Origin$ Graveyard | Destination$ Library | DefinedPlayer$ You | Hidden$ True | ChangeNum$ 4 | ChangeType$ Card.YouOwn | Shuffle$ True
+K:Replicate:2 U U
+Oracle:Target player puts the top four cards of their library into their graveyard. You shuffle up to four cards from your graveyard into your library.\nReplicate {2}{U}{U} (When you cast this spell, copy it for each time you paid the replicate cost. You may choose new targets for the copies.)
diff --git a/forge-gui/res/cardsfolder/upcoming/undead_augur.txt b/forge-gui/res/cardsfolder/upcoming/undead_augur.txt
new file mode 100644
index 00000000000..e45033b3f0c
--- /dev/null
+++ b/forge-gui/res/cardsfolder/upcoming/undead_augur.txt
@@ -0,0 +1,9 @@
+Name:Undead Augur
+ManaCost:B B
+Types:Creature Zombie Wizard
+PT:2/2
+T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Card.Self | TriggerController$ TriggeredCardController | Execute$ TrigDraw | TriggerDescription$ Whenever CARDNAME or another Zombie you control dies, you draw a card and you lose 1 life.
+T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Graveyard | ValidCard$ Zombie.Other+YouCtrl | TriggerZones$ Battlefield | TriggerController$ TriggeredCardController | Secondary$ True | Execute$ TrigDraw | TriggerDescription$ Whenever CARDNAME or another Zombie you control dies, you draw a card and you lose 1 life.
+SVar:TrigDraw:DB$ Draw | Defined$ You | NumCards$ 1 | SubAbility$ DBLoseLife
+SVar:DBLoseLife:DB$ LoseLife | LifeAmount$ 1
+Oracle:Whenever Undead Augur or another Zombie you control dies, you draw a card and you lose 1 life.
diff --git a/forge-gui/res/cardsfolder/v/vivien_champion_of_the_wilds.txt b/forge-gui/res/cardsfolder/v/vivien_champion_of_the_wilds.txt
new file mode 100644
index 00000000000..de2ced300c2
--- /dev/null
+++ b/forge-gui/res/cardsfolder/v/vivien_champion_of_the_wilds.txt
@@ -0,0 +1,12 @@
+Name:Vivien, Champion of the Wilds
+ManaCost:2 G
+Types:Legendary Planeswalker Vivien
+Loyalty:4
+S:Mode$ Continuous | Affected$ Creature | WithFlash$ You | AffectedZone$ Exile,Graveyard,Hand,Library,Command | Description$ You may cast creature spells as though they had flash.
+A:AB$ Pump | Cost$ AddCounter<1/LOYALTY> | Planeswalker$ True | ValidTgts$ Creature | TargetMin$ 0 | TargetMax$ 1 | TgtPrompt$ Select target creature | KW$ Vigilance & Reach | UntilYourNextTurn$ True | SpellDescription$ Until your next turn, up to one target creature gains vigilance and reach.
+A:AB$ Dig | Cost$ SubCounter<2/LOYALTY> | Planeswalker$ True | Defined$ You | DigNum$ 3 | DestinationZone$ Exile | ExileFaceDown$ True | RememberChanged$ True | SubAbility$ DBEffect | AILogic$ DigForCreature | SpellDescription$ Look at the top three cards of your library. Exile one face down and put the rest on the bottom of your library in any order. For as long as it remains exiled, you may look at that card and you may cast it if it's a creature card.
+SVar:DBEffect:DB$ Effect | RememberObjects$ Remembered | StaticAbilities$ STPlay1,STPlay2 | Duration$ Permanent | ForgetOnMoved$ Exile | SubAbility$ DBCleanup
+SVar:STPlay1:Mode$ Continuous | MayLookAt$ You | EffectZone$ Command | Affected$ Card.IsRemembered | AffectedZone$ Exile | Description$ You may look at the card and you may cast it if it's a creature.
+SVar:STPlay2:Mode$ Continuous | MayPlay$ True | EffectZone$ Command | Affected$ Creature.IsRemembered | AffectedZone$ Exile | Secondary$ True
+SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
+Oracle:You may cast creature spells as though they had flash.\n[+1]: Until your next turn, up to one target creature gains vigilance and reach.\n[−2]: Look at the top three cards of your library. Exile one face down and put the rest on the bottom of your library in any order. For as long as it remains exiled, you may look at that card and you may cast it if it's a creature card.
diff --git a/forge-gui/res/deckgendecks/Standard.lda.dat b/forge-gui/res/deckgendecks/Standard.lda.dat
index 5c8f0dde1ca..06bc30015ed 100644
Binary files a/forge-gui/res/deckgendecks/Standard.lda.dat and b/forge-gui/res/deckgendecks/Standard.lda.dat differ
diff --git a/forge-gui/res/deckgendecks/Standard.raw.dat b/forge-gui/res/deckgendecks/Standard.raw.dat
index d5f12fd99d5..82e87352d7a 100644
Binary files a/forge-gui/res/deckgendecks/Standard.raw.dat and b/forge-gui/res/deckgendecks/Standard.raw.dat differ
diff --git a/forge-gui/res/editions/Modern Horizons.txt b/forge-gui/res/editions/Modern Horizons.txt
new file mode 100644
index 00000000000..b43897e0380
--- /dev/null
+++ b/forge-gui/res/editions/Modern Horizons.txt
@@ -0,0 +1,46 @@
+[metadata]
+Code=MH1
+Date=2019-6-14
+Name=Modern Horizons
+Code2=MH1
+MciCode=mh1
+Type=Other
+
+[cards]
+1 M Morophon, the Boundless
+14 C Impostor of the Sixth Pride
+19 C Martyr's Soul
+26 M Serra the Benevolent
+36 C Wall of One Thousand Cuts
+38 U Wing Shards
+44 C Choking Tethers
+50 U Fact or Fiction
+52 R Force of Negation
+64 C Prohibit
+71 C Stream of Thought
+80 R Cabal Therapist
+95 C Headless Specter
+114 C Venomous Changling
+122 U Firebolt
+129 U Goblin Matron
+131 C Goblin War Party
+134 C Lava Dart
+145 M Seasoned Pyromancer
+161 R Deep Forest Hermit
+162 C Elvish Fury
+164 R Force of Vigor
+178 C Savage Swipe
+203 R Ice-Fang Coatl
+244 R Prismatic Vista
+250 L Snow-Covered Plains
+251 L Snow-Covered Island
+252 L Snow-Covered Swamp
+253 L Snow-Covered Mountain
+254 L Snow-Covered Forest
+255 R Flusterstorm
+
+
+[tokens]
+w_4_4_angel_flying_vigilance
+g_1_1_squirrel
+r_1_1_goblin
diff --git a/forge-gui/src/main/java/forge/match/input/InputPayMana.java b/forge-gui/src/main/java/forge/match/input/InputPayMana.java
index 809bf29ce0a..516fa477976 100644
--- a/forge-gui/src/main/java/forge/match/input/InputPayMana.java
+++ b/forge-gui/src/main/java/forge/match/input/InputPayMana.java
@@ -316,8 +316,20 @@ public abstract class InputPayMana extends InputSyncronizedBase {
} else {
chosen = chosenAbility;
}
+
ColorSet colors = ColorSet.fromMask(0 == colorNeeded ? colorCanUse : colorNeeded);
- chosen.getManaPartRecursive().setExpressChoice(colors);
+
+ // Filter the colors for the express choice so that only actually producible colors can be chosen
+ int producedColorMask = 0;
+ for (final byte color : ManaAtom.MANATYPES) {
+ if (chosen.getManaPartRecursive().getOrigProduced().contains(MagicColor.toShortString(color))
+ && colors.hasAnyColor(color)) {
+ producedColorMask |= color;
+ }
+ }
+ ColorSet producedAndNeededColors = ColorSet.fromMask(producedColorMask);
+
+ chosen.getManaPartRecursive().setExpressChoice(producedAndNeededColors);
// System.out.println("Chosen sa=" + chosen + " of " + chosen.getHostCard() + " to pay mana");
diff --git a/pom.xml b/pom.xml
index 5ff444063e8..9c0b3538532 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
forge
pom
Forge Parent
- 1.6.25-SNAPSHOT
+ 1.6.26-SNAPSHOT
Forge lets you play the card game Magic: The Gathering against a computer opponent using all of the rules.