getChosenModesTurn(SpellAbility ability) {
SpellAbility original = null;
SpellAbility root = ability.getRootAbility();
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 5dbfabc2740..dce30d6d5b7 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactory.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactory.java
@@ -88,7 +88,7 @@ public class CardFactory {
}
out.setZone(in.getZone());
- out.setState(in.getCurrentStateName(), true);
+ out.setState(in.getFaceupCardStateName(), true);
out.setBackSide(in.isBackSide());
// this's necessary for forge.game.GameAction.unattachCardLeavingBattlefield(Card)
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 f6f4655d747..70da4cfd33d 100644
--- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
+++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java
@@ -66,7 +66,6 @@ import java.util.Map.Entry;
import io.sentry.Sentry;
import io.sentry.event.BreadcrumbBuilder;
-
/**
*
* CardFactoryUtil class.
@@ -1404,6 +1403,13 @@ public class CardFactoryUtil {
return doXMath(StringUtils.isNumeric(v) ? Integer.parseInt(v) : xCount(c, c.getSVar(v)), m, c);
}
+ // Count$Foretold..
+ if (sq[0].startsWith("Foretold")) {
+ String v = c.isForetold() ? sq[1] : sq[2];
+ // TODO move this to AbilityUtils
+ return doXMath(StringUtils.isNumeric(v) ? Integer.parseInt(v) : xCount(c, c.getSVar(v)), m, c);
+ }
+
// Count$Presence_..
if (sq[0].startsWith("Presence")) {
final String type = sq[0].split("_")[1];
@@ -1449,7 +1455,6 @@ public class CardFactoryUtil {
return forge.util.MyRandom.getRandom().nextInt(1+max-min) + min;
}
-
// Count$Domain
if (sq[0].startsWith("Domain")) {
int n = 0;
@@ -1997,7 +2002,6 @@ public class CardFactoryUtil {
final Set hexproofkw = Sets.newHashSet();
final Set allkw = Sets.newHashSet();
-
for (Card c : CardLists.getValidCards(cardlist, restrictions, p, host, null)) {
for (KeywordInterface inst : c.getKeywords()) {
final String k = inst.getOriginal();
@@ -2155,7 +2159,6 @@ public class CardFactoryUtil {
return re;
}
-
public static ReplacementEffect makeEtbCounter(final String kw, final Card card, final boolean intrinsic)
{
String parse = kw;
@@ -4095,6 +4098,60 @@ public class CardFactoryUtil {
newSA.setAlternativeCost(AlternativeCost.Evoke);
newSA.setIntrinsic(intrinsic);
inst.addSpellAbility(newSA);
+ } else if (keyword.startsWith("Foretell")) {
+
+ final SpellAbility foretell = new AbilityStatic(card, new Cost(ManaCost.TWO, false), null) {
+ @Override
+ public boolean canPlay() {
+ if (!getRestrictions().canPlay(getHostCard(), this)) {
+ return false;
+ }
+
+ Player activator = this.getActivatingPlayer();
+ final Game game = activator.getGame();
+
+ if (!activator.hasKeyword("Foretell on any player’s turn") && !game.getPhaseHandler().isPlayerTurn(activator)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean isForetelling() {
+ return true;
+ }
+
+ @Override
+ public void resolve() {
+ final Game game = getHostCard().getGame();
+ final Card c = game.getAction().exile(getHostCard(), this);
+ c.setForetold(true);
+ c.setForetoldThisTurn(true);
+ c.turnFaceDown(true);
+ // look at the exiled card
+ c.addMayLookTemp(getActivatingPlayer());
+
+ // only done when the card is foretold by the static ability
+ getActivatingPlayer().addForetoldThisTurn();
+
+ if (!isIntrinsic()) {
+ // because it doesn't work other wise
+ c.setForetoldByEffect(true);
+ }
+ String sb = TextUtil.concatWithSpace(getActivatingPlayer().toString(),"has foretold.");
+ game.getGameLog().add(GameLogEntryType.STACK_RESOLVE, sb);
+ }
+ };
+ final StringBuilder sbDesc = new StringBuilder();
+ sbDesc.append("Foretell (").append(inst.getReminderText()).append(")");
+ foretell.setDescription(sbDesc.toString());
+ foretell.putParam("Secondary", "True");
+
+ foretell.getRestrictions().setZone(ZoneType.Hand);
+ foretell.setIntrinsic(intrinsic);
+ inst.addSpellAbility(foretell);
+
} else if (keyword.startsWith("Fortify")) {
String[] k = keyword.split(":");
// Get cost string
diff --git a/forge-game/src/main/java/forge/game/card/CardProperty.java b/forge-game/src/main/java/forge/game/card/CardProperty.java
index 57cc898e053..2da6fca758d 100644
--- a/forge-game/src/main/java/forge/game/card/CardProperty.java
+++ b/forge-game/src/main/java/forge/game/card/CardProperty.java
@@ -1670,36 +1670,40 @@ public class CardProperty {
if (property.equals("pseudokicked")) {
if (!card.isOptionalCostPaid(OptionalCost.Generic)) return false;
}
- } else if (property.startsWith("surged")) {
+ } else if (property.equals("surged")) {
if (card.getCastSA() == null) {
return false;
}
return card.getCastSA().isSurged();
- } else if (property.startsWith("dashed")) {
+ } else if (property.equals("dashed")) {
if (card.getCastSA() == null) {
return false;
}
return card.getCastSA().isDash();
- } else if (property.startsWith("escaped")) {
+ } else if (property.equals("escaped")) {
if (card.getCastSA() == null) {
return false;
}
return card.getCastSA().isEscape();
- } else if (property.startsWith("evoked")) {
+ } else if (property.equals("evoked")) {
if (card.getCastSA() == null) {
return false;
}
return card.getCastSA().isEvoke();
- } else if (property.startsWith("prowled")) {
+ } else if (property.equals("prowled")) {
if (card.getCastSA() == null) {
return false;
}
return card.getCastSA().isProwl();
- } else if (property.startsWith("spectacle")) {
+ } else if (property.equals("spectacle")) {
if (card.getCastSA() == null) {
return false;
}
return card.getCastSA().isSpectacle();
+ } else if (property.equals("foretold")) {
+ if (!card.isForetold()) {
+ return false;
+ }
} else if (property.equals("HasDevoured")) {
if (card.getDevouredCards().isEmpty()) {
return false;
diff --git a/forge-game/src/main/java/forge/game/card/CardUtil.java b/forge-game/src/main/java/forge/game/card/CardUtil.java
index ce15796ef90..e1726689621 100644
--- a/forge-game/src/main/java/forge/game/card/CardUtil.java
+++ b/forge-game/src/main/java/forge/game/card/CardUtil.java
@@ -278,6 +278,10 @@ public final class CardUtil {
newCopy.copyChangedTextFrom(in);
+ newCopy.setForetold(in.isForetold());
+ newCopy.setForetoldThisTurn(in.isForetoldThisTurn());
+ newCopy.setForetoldByEffect(in.isForetoldByEffect());
+
newCopy.setMeldedWith(getLKICopy(in.getMeldedWith(), cachedMap));
newCopy.setTimestamp(in.getTimestamp());
diff --git a/forge-game/src/main/java/forge/game/keyword/Keyword.java b/forge-game/src/main/java/forge/game/keyword/Keyword.java
index b244e3c5ba8..96a11fb2ef4 100644
--- a/forge-game/src/main/java/forge/game/keyword/Keyword.java
+++ b/forge-game/src/main/java/forge/game/keyword/Keyword.java
@@ -70,6 +70,7 @@ public enum Keyword {
FLASH("Flash", SimpleKeyword.class, true, "You may cast this spell any time you could cast an instant."),
FLASHBACK("Flashback", KeywordWithCost.class, false, "You may cast this card from your graveyard by paying %s rather than paying its mana cost. If you do, exile it as it resolves."),
FLYING("Flying", SimpleKeyword.class, true, "This creature can't be blocked except by creatures with flying or reach."),
+ FORETELL("Foretell", KeywordWithCost.class, false, "During your turn, you may pay {2} and exile this card from your hand face down. Cast it on a later turn for its foretell cost."),
FORTIFY("Fortify", KeywordWithCost.class, false, "%s: Attach to target land you control. Fortify only as a sorcery."),
FRENZY("Frenzy", KeywordWithAmount.class, false, "Whenever this creature attacks and isn't blocked, it gets +%d/+0 until end of turn."),
GRAFT("Graft", KeywordWithAmount.class, false, "This permanent enters the battlefield with {%d:+1/+1 counter} on it. Whenever another creature enters the battlefield, you may move a +1/+1 counter from this permanent onto it."),
diff --git a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
index bc2f165b584..782bce143af 100644
--- a/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
+++ b/forge-game/src/main/java/forge/game/phase/PhaseHandler.java
@@ -26,7 +26,6 @@ import forge.game.*;
import forge.game.ability.AbilityKey;
import forge.game.card.Card;
import forge.game.card.CardCollection;
-import forge.game.card.CardCollectionView;
import forge.game.card.CardLists;
import forge.game.card.CardPredicates.Presets;
import forge.game.card.CardZoneTable;
@@ -175,8 +174,7 @@ public class PhaseHandler implements java.io.Serializable {
game.fireEvent(new GameEventTurnBegan(playerTurn, turn));
// Tokens starting game in play should suffer from Sum. Sickness
- final CardCollectionView list = playerTurn.getCardsIncludePhasingIn(ZoneType.Battlefield);
- for (final Card c : list) {
+ for (final Card c : playerTurn.getCardsIncludePhasingIn(ZoneType.Battlefield)) {
if (playerTurn.getTurn() > 0 || !c.isStartsGameInPlay()) {
c.setSickness(false);
}
diff --git a/forge-game/src/main/java/forge/game/player/Player.java b/forge-game/src/main/java/forge/game/player/Player.java
index 877158b6a3f..1ee63399db8 100644
--- a/forge-game/src/main/java/forge/game/player/Player.java
+++ b/forge-game/src/main/java/forge/game/player/Player.java
@@ -111,6 +111,7 @@ public class Player extends GameEntity implements Comparable {
private int numDrawnThisDrawStep = 0;
private int numDiscardedThisTurn = 0;
private int numTokenCreatedThisTurn = 0;
+ private int numForetoldThisTurn = 0;
private int numCardsInHandStartedThisTurnWith = 0;
private final Map> notes = Maps.newHashMap();
@@ -1666,6 +1667,22 @@ public class Player extends GameEntity implements Comparable {
numTokenCreatedThisTurn = 0;
}
+ public final int getNumForetoldThisTurn() {
+ return numForetoldThisTurn;
+ }
+
+ public final void addForetoldThisTurn() {
+ numForetoldThisTurn++;
+ final Map runParams = AbilityKey.newMap();
+ runParams.put(AbilityKey.Player, this);
+ runParams.put(AbilityKey.Num, numForetoldThisTurn);
+ game.getTriggerHandler().runTrigger(TriggerType.Foretell, runParams, false);
+ }
+
+ public final void resetNumForetoldThisTurn() {
+ numForetoldThisTurn = 0;
+ }
+
public final int getNumDiscardedThisTurn() {
return numDiscardedThisTurn;
}
@@ -2581,7 +2598,7 @@ public class Player extends GameEntity implements Comparable {
controlledBy.remove(timestamp);
getView().updateMindSlaveMaster(this);
-
+
if (event) {
game.fireEvent(new GameEventPlayerControl(this, oldLobbyPlayer, oldController, getLobbyPlayer(), getController()));
}
diff --git a/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java b/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java
index bb8029e89aa..7f23a7fdbc6 100644
--- a/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java
+++ b/forge-game/src/main/java/forge/game/spellability/AlternativeCost.java
@@ -9,6 +9,7 @@ public enum AlternativeCost {
Escape,
Evoke,
Flashback,
+ Foretold,
Madness,
Offering,
Outlast, // ActivatedAbility
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 344345b9dd5..bc63cb582f7 100644
--- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
+++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java
@@ -466,23 +466,23 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
public final void clearManaPaid() {
payingMana.clear();
}
-
+
public final void applyPayingManaEffects() {
Card host = getHostCard();
-
+
for (Mana mana : getPayingMana()) {
if (mana.triggersWhenSpent()) {
mana.getManaAbility().addTriggersWhenSpent(this, host);
}
-
+
if (mana.addsCounters(this)) {
mana.getManaAbility().createETBCounters(host, getActivatingPlayer());
}
-
+
if (mana.addsNoCounterMagic(this) && host != null) {
host.setCanCounter(false);
}
-
+
if (isSpell() && host != null) {
if (mana.addsKeywords(this) && mana.addsKeywordsType()
&& host.getType().hasStringType(mana.getManaAbility().getAddsKeywordsType())) {
@@ -823,6 +823,14 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return this.isAlternativeCost(AlternativeCost.Flashback);
}
+ public boolean isForetelling() {
+ return false;
+ }
+ public boolean isForetold() {
+ return this.isAlternativeCost(AlternativeCost.Foretold);
+ }
+
+
/**
* @return the aftermath
*/
@@ -1780,6 +1788,11 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
return false;
}
}
+ else if (incR[0].equals("Static")) {
+ if (!(root instanceof AbilityStatic)) {
+ return false;
+ }
+ }
else { //not a spell/ability type
return false;
}
diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java
index 687d58ffdad..416e3a05f65 100644
--- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java
+++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityCondition.java
@@ -118,6 +118,10 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
this.optionalCostPaid = true;
}
+ if (value.equals("Foretold")) {
+ this.foretold = true;
+ }
+
if (params.containsKey("ConditionOptionalPaid")) {
this.optionalBoolean = Boolean.parseBoolean(params.get("ConditionOptionalPaid"));
}
@@ -250,6 +254,7 @@ public class SpellAbilityCondition extends SpellAbilityVariables {
if (this.kicked2 && !sa.isOptionalCostPaid(OptionalCost.Kicker2)) return false;
if (this.altCostPaid && !sa.isOptionalCostPaid(OptionalCost.AltCost)) return false;
if (this.surgeCostPaid && !sa.isSurged()) return false;
+ if (this.foretold && !sa.isForetold()) return false;
if (this.optionalCostPaid && this.optionalBoolean && !sa.isOptionalCostPaid(OptionalCost.Generic)) return false;
if (this.optionalCostPaid && !this.optionalBoolean && sa.isOptionalCostPaid(OptionalCost.Generic)) return false;
diff --git a/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java b/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java
index 69a4fd24d49..00120c5b815 100644
--- a/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java
+++ b/forge-game/src/main/java/forge/game/spellability/SpellAbilityVariables.java
@@ -405,6 +405,7 @@ public class SpellAbilityVariables implements Cloneable {
protected boolean optionalCostPaid = false; // Undergrowth other Pseudo-kickers
protected boolean optionalBoolean = true; // Just in case you need to check if something wasn't kicked, etc
protected boolean surgeCostPaid = false;
+ protected boolean foretold = false;
/**
* @return the allTargetsLegal
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerForetell.java b/forge-game/src/main/java/forge/game/trigger/TriggerForetell.java
new file mode 100644
index 00000000000..b051315ffff
--- /dev/null
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerForetell.java
@@ -0,0 +1,78 @@
+/*
+ * Forge: Play Magic: the Gathering.
+ * Copyright (C) 2011 Forge Team
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * 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 .
+ */
+package forge.game.trigger;
+
+import forge.game.ability.AbilityKey;
+import forge.game.card.Card;
+import forge.game.player.Player;
+import forge.game.spellability.SpellAbility;
+import forge.util.Localizer;
+
+import java.util.Map;
+
+/**
+ * @author Forge
+ */
+public class TriggerForetell extends Trigger {
+
+ /**
+ *
+ * @param params
+ * a {@link java.util.HashMap} object.
+ * @param host
+ * a {@link forge.game.card.Card} object.
+ * @param intrinsic
+ * the intrinsic
+ */
+ public TriggerForetell(final Map params, final Card host, final boolean intrinsic) {
+ super(params, host, intrinsic);
+ }
+
+ @Override
+ public String getImportantStackObjects(SpellAbility sa) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Localizer.getInstance().getMessage("lblPlayer")).append(": ").append(sa.getTriggeringObject(AbilityKey.Player));
+ return sb.toString();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public final void setTriggeringObjects(final SpellAbility sa, Map runParams) {
+ sa.setTriggeringObjectsFrom(runParams, AbilityKey.Player);
+ }
+
+ /** {@inheritDoc}
+ * @param runParams*/
+ @Override
+ public final boolean performTest(final Map runParams) {
+ Player p = (Player) runParams.get(AbilityKey.Player);
+ if (hasParam("ValidPlayer")) {
+ if (!matchesValid(p, getParam("ValidPlayer").split(","), getHostCard())) {
+ return false;
+ }
+ }
+
+ if (hasParam("OnlyFirst")) {
+ if ((int) runParams.get(AbilityKey.Num) != 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}
diff --git a/forge-game/src/main/java/forge/game/trigger/TriggerType.java b/forge-game/src/main/java/forge/game/trigger/TriggerType.java
index d31de582d13..bd3175bf094 100644
--- a/forge-game/src/main/java/forge/game/trigger/TriggerType.java
+++ b/forge-game/src/main/java/forge/game/trigger/TriggerType.java
@@ -64,6 +64,7 @@ public enum TriggerType {
Fight(TriggerFight.class),
FightOnce(TriggerFightOnce.class),
FlippedCoin(TriggerFlippedCoin.class),
+ Foretell(TriggerForetell.class),
Immediate(TriggerImmediate.class),
Investigated(TriggerInvestigated.class),
LandPlayed(TriggerLandPlayed.class),
diff --git a/forge-game/src/main/java/forge/game/zone/MagicStack.java b/forge-game/src/main/java/forge/game/zone/MagicStack.java
index 213755d6d02..ea9308f0895 100644
--- a/forge-game/src/main/java/forge/game/zone/MagicStack.java
+++ b/forge-game/src/main/java/forge/game/zone/MagicStack.java
@@ -52,7 +52,6 @@ import forge.game.keyword.Keyword;
import forge.game.player.Player;
import forge.game.spellability.AbilityStatic;
import forge.game.spellability.OptionalCost;
-import forge.game.spellability.Spell;
import forge.game.spellability.SpellAbility;
import forge.game.spellability.SpellAbilityStackInstance;
import forge.game.spellability.TargetChoices;
@@ -241,8 +240,7 @@ public class MagicStack /* extends MyObservable */ implements Iterable= 0 ? Integer.valueOf(zonePosition) : null, null);
+ oldCard.setCastSA(null);
+ oldCard.setCastFrom(null);
+ // add back to where it came from, hopefully old state
+ // skip GameAction
+ oldCard.getZone().remove(oldCard);
+ fromZone.add(oldCard, zonePosition >= 0 ? Integer.valueOf(zonePosition) : null);
}
ability.clearTargets();