diff --git a/forge-game/src/main/java/forge/game/CardTraitBase.java b/forge-game/src/main/java/forge/game/CardTraitBase.java index 6951c2be1bb..d3ff7689545 100644 --- a/forge-game/src/main/java/forge/game/CardTraitBase.java +++ b/forge-game/src/main/java/forge/game/CardTraitBase.java @@ -44,7 +44,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView { /** The temporarily suppressed. */ protected boolean temporarilySuppressed = false; - private Map sVars = Maps.newHashMap(); + protected Map sVars = Maps.newHashMap(); /** Keys of descriptive (text) parameters. */ private static final ImmutableList descriptiveKeys = ImmutableList.builder() diff --git a/forge-game/src/main/java/forge/game/GameAction.java b/forge-game/src/main/java/forge/game/GameAction.java index 70645eaf92a..3bf8a391842 100644 --- a/forge-game/src/main/java/forge/game/GameAction.java +++ b/forge-game/src/main/java/forge/game/GameAction.java @@ -105,10 +105,6 @@ public class GameAction { boolean fromBattlefield = zoneFrom != null && zoneFrom.is(ZoneType.Battlefield); boolean toHand = zoneTo.is(ZoneType.Hand); - // TODO: part of a workaround for suspend-cast creaturs bounced to hand - boolean zoneChangedEarly = false; - Zone originalZone = c.getZone(); - //Rule 110.5g: A token that has left the battlefield can't move to another zone if (c.isToken() && zoneFrom != null && !fromBattlefield && !zoneFrom.is(ZoneType.Command)) { return c; @@ -158,13 +154,6 @@ public class GameAction { c.setState(CardStateName.Original, true); } - if (fromBattlefield && toHand && c.wasSuspendCast()) { - // TODO: This has to be set early for suspend-cast creatures bounced to hand, otherwise they - // end up in a state when they are considered on the battlefield. There should be a better solution. - c.setZone(zoneTo); - zoneChangedEarly = true; - } - // Clean up the temporary Dash SVar when the Dashed card leaves the battlefield if (fromBattlefield && c.getSVar("EndOfTurnLeavePlay").equals("Dash")) { c.removeSVar("EndOfTurnLeavePlay"); @@ -295,10 +284,6 @@ public class GameAction { ReplacementResult repres = game.getReplacementHandler().run(repParams); if (repres != ReplacementResult.NotReplaced) { - if (zoneChangedEarly) { - c.setZone(originalZone); // TODO: part of a workaround for bounced suspend-cast cards - } - // reset failed manifested Cards back to original if (c.isManifested()) { c.turnFaceUp(false, false); @@ -315,10 +300,6 @@ public class GameAction { copied.getOwner().removeInboundToken(copied); - if (c.wasSuspendCast()) { - copied = GameAction.addSuspendTriggers(copied); - } - if (suppress) { game.getTriggerHandler().suppressMode(TriggerType.ChangesZone); } @@ -396,7 +377,7 @@ public class GameAction { } game.getTriggerHandler().runTrigger(TriggerType.ChangesZone, runParams, true); - if (zoneFrom != null && zoneFrom.is(ZoneType.Battlefield)) { + if (zoneFrom != null && zoneFrom.is(ZoneType.Battlefield) && !zoneFrom.getPlayer().equals(zoneTo.getPlayer())) { final Map runParams2 = Maps.newHashMap(); runParams2.put("Card", lastKnownInfo); runParams2.put("OriginalController", zoneFrom.getPlayer()); @@ -436,7 +417,6 @@ public class GameAction { if (fromBattlefield) { if (!c.isToken()) { - copied.setSuspendCast(false); copied.setState(CardStateName.Original, true); } // Soulbond unpairing @@ -1508,44 +1488,6 @@ public class GameAction { return sacrificed != null; } - private static Card addSuspendTriggers(final Card c) { - if (c.getSVar("HasteFromSuspend").equals("True")) { - return c; - } - c.setSVar("HasteFromSuspend", "True"); - - final GameCommand intoPlay = new GameCommand() { - private static final long serialVersionUID = -4514610171270596654L; - - @Override - public void run() { - if (c.isInPlay() && c.isCreature()) { - c.addExtrinsicKeyword("Haste"); - c.updateStateForView(); - } - } // execute() - }; - - c.addComesIntoPlayCommand(intoPlay); - - final GameCommand loseControl = new GameCommand() { - private static final long serialVersionUID = -4514610171270596654L; - - @Override - public void run() { - if (c.getSVar("HasteFromSuspend").equals("True")) { - c.setSVar("HasteFromSuspend", "False"); - c.removeExtrinsicKeyword("Haste"); - c.updateStateForView(); - } - } // execute() - }; - - c.addChangeControllerCommand(loseControl); - c.addLeavesPlayCommand(loseControl); - return c; - } - /** * @return the sacrificed Card in its new location, or {@code null} if the * sacrifice wasn't successful. 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 2208dea8157..a9a937fead7 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 @@ -185,10 +185,6 @@ public class PlayEffect extends SpellAbilityEffect { } } - if(sa.hasParam("SuspendCast")) { - tgtCard.setSuspendCast(true); - } - // lands will be played if (tgtCard.isLand()) { if (controller.playLand(tgtCard, true)) { diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java index 409105f2f45..8a5d0dfebfb 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PumpAllEffect.java @@ -60,9 +60,6 @@ public class PumpAllEffect extends SpellAbilityEffect { for (String kw : hiddenkws) { tgtC.addHiddenExtrinsicKeyword(kw); } - if (suspend && !tgtC.hasSuspend()) { - tgtC.setSuspend(true); - } if (sa.hasParam("RememberAllPumped")) { sa.getHostCard().addRemembered(tgtC); diff --git a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java index 02a0dd7016f..cbbccffafe2 100644 --- a/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java +++ b/forge-game/src/main/java/forge/game/ability/effects/PumpEffect.java @@ -30,9 +30,11 @@ public class PumpEffect extends SpellAbilityEffect { private static void applyPump(final SpellAbility sa, final Card applyTo, final int a, final int d, final List keywords, final long timestamp) { + final Card host = sa.getHostCard(); //if host is not on the battlefield don't apply + // Suspend should does Affect the Stack if (sa.hasParam("UntilLoseControlOfHost") - && !sa.getHostCard().isInPlay()) { + && !(host.isInPlay() || host.isInZone(ZoneType.Stack))) { return; } final Game game = sa.getActivatingPlayer().getGame(); @@ -45,9 +47,6 @@ public class PumpEffect extends SpellAbilityEffect { redrawPT |= kw.contains("CARDNAME's power and toughness are switched"); } else { kws.add(kw); - if (kw.equals("Suspend") && !applyTo.hasSuspend()) { - applyTo.setSuspend(true); - } } } 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 b3e2ef8b34f..847179329bf 100644 --- a/forge-game/src/main/java/forge/game/card/Card.java +++ b/forge-game/src/main/java/forge/game/card/Card.java @@ -161,8 +161,6 @@ public class Card extends GameEntity implements Comparable { private long bestowTimestamp = -1; private long transformedTimestamp = 0; - private boolean suspendCast = false; - private boolean suspend = false; private boolean tributed = false; private boolean embalmed = false; private boolean eternalized = false; @@ -3754,17 +3752,8 @@ public class Card extends GameEntity implements Comparable { } public final boolean hasSuspend() { - return suspend; - } - public final void setSuspend(final boolean b) { - suspend = b; - } - - public final boolean wasSuspendCast() { - return suspendCast; - } - public final void setSuspendCast(final boolean b) { - suspendCast = b; + return hasKeyword(Keyword.SUSPEND) && getLastKnownZone().is(ZoneType.Exile) + && getCounters(CounterType.TIME) >= 1; } public final boolean isPhasedOut() { 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 3da41b1252d..72f6c28a315 100644 --- a/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java +++ b/forge-game/src/main/java/forge/game/card/CardFactoryUtil.java @@ -2950,10 +2950,27 @@ public class CardFactoryUtil { playTrig.append(" | TriggerDescription$ When the last time counter is removed from this card, if it's exiled, play it without paying its mana cost if able. "); playTrig.append("If you can't, it remains exiled. If you cast a creature spell this way, it gains haste until you lose control of the spell or the permanent it becomes."); - final String abPlay = "DB$ Play | Defined$ Self | WithoutManaCost$ True | SuspendCast$ True"; + String abPlay = "DB$ Play | Defined$ Self | WithoutManaCost$ True | SuspendCast$ True"; + if (card.isPermanent()) { + abPlay += "| RememberPlayed$ True"; + } + + final SpellAbility saPlay = AbilityFactory.getAbility(abPlay, card); + + if (card.isPermanent()) { + final String abPump = "DB$ Pump | Defined$ Remembered | KW$ Haste | PumpZone$ Stack " + + "| ConditionDefined$ Remembered | ConditionPresent$ Creature | UntilLoseControlOfHost$ True"; + final AbilitySub saPump = (AbilitySub)AbilityFactory.getAbility(abPump, card); + + String dbClean = "DB$ Cleanup | ClearRemembered$ True"; + final AbilitySub saCleanup = (AbilitySub) AbilityFactory.getAbility(dbClean, card); + saPump.setSubAbility(saCleanup); + + saPlay.setSubAbility(saPump); + } final Trigger parsedPlayTrigger = TriggerHandler.parseTrigger(playTrig.toString(), card, intrinsic); - parsedPlayTrigger.setOverridingAbility(AbilityFactory.getAbility(abPlay, card)); + parsedPlayTrigger.setOverridingAbility(saPlay); inst.addTrigger(parsedUpkeepTrig); inst.addTrigger(parsedPlayTrigger); @@ -4026,8 +4043,6 @@ public class CardFactoryUtil { inst.addSpellAbility(newSA); } else if (keyword.startsWith("Suspend") && !keyword.equals("Suspend")) { - // really needed? - card.setSuspend(true); // only add it if suspend has counter and cost final String[] k = keyword.split(":"); @@ -4051,8 +4066,6 @@ public class CardFactoryUtil { public void resolve() { final Game game = card.getGame(); final Card c = game.getAction().exile(this.getHostCard(), this); - // better check? - c.setSuspend(true); int counters = AbilityUtils.calculateAmount(c, k[1], this); c.addCounter(CounterType.TIME, counters, c, true); 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 8b39dcefe78..d1d83fe6c1a 100644 --- a/forge-game/src/main/java/forge/game/card/CardProperty.java +++ b/forge-game/src/main/java/forge/game/card/CardProperty.java @@ -1357,8 +1357,7 @@ public class CardProperty { return false; } } else if (property.startsWith("suspended")) { - if (!card.hasSuspend() || !game.isCardExiled(card) - || !(card.getCounters(CounterType.TIME) >= 1)) { + if (!card.hasSuspend()) { return false; } } else if (property.startsWith("delved")) { diff --git a/forge-game/src/main/java/forge/game/spellability/Spell.java b/forge-game/src/main/java/forge/game/spellability/Spell.java index 4c62b6e2fd2..593a672331b 100644 --- a/forge-game/src/main/java/forge/game/spellability/Spell.java +++ b/forge-game/src/main/java/forge/game/spellability/Spell.java @@ -138,7 +138,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable } if (!(isInstant || activator.canCastSorcery() || flash || getRestrictions().isInstantSpeed() - || this.hasSVar("IsCastFromPlayEffect") + || hasSVar("IsCastFromPlayEffect") || (card.isFaceDown() && !card.getLastKnownZone().is(ZoneType.Battlefield) && card.getState(CardStateName.Original).getType().isInstant()))) { return false; } @@ -148,7 +148,11 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable } // for uncastables like lotus bloom, check if manaCost is blank (except for morph spells) - if (!isCastFaceDown() && isBasicSpell() && card.getState(card.isFaceDown() ? CardStateName.Original : card.getCurrentStateName()).getManaCost().isNoCost()) { + // but ignore if it comes from PlayEffect + if (!isCastFaceDown() + && !hasSVar("IsCastFromPlayEffect") + && isBasicSpell() + && card.getState(card.isFaceDown() ? CardStateName.Original : card.getCurrentStateName()).getManaCost().isNoCost()) { return false; } 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 0cf37051c0e..2118184f9cb 100644 --- a/forge-game/src/main/java/forge/game/spellability/SpellAbility.java +++ b/forge-game/src/main/java/forge/game/spellability/SpellAbility.java @@ -854,6 +854,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit // clear maps for copy, the values will be added later clone.additionalAbilities = Maps.newHashMap(); clone.additionalAbilityLists = Maps.newHashMap(); + clone.sVars = Maps.newHashMap(); // run special copy Ability to make a deep copy CardFactory.copySpellAbility(this, clone, host, activ, lki); } catch (final CloneNotSupportedException e) { diff --git a/forge-gui/src/main/java/forge/player/HumanPlay.java b/forge-gui/src/main/java/forge/player/HumanPlay.java index f9ebb4f700d..21f04529811 100644 --- a/forge-gui/src/main/java/forge/player/HumanPlay.java +++ b/forge-gui/src/main/java/forge/player/HumanPlay.java @@ -89,16 +89,7 @@ public class HumanPlay { // extra play check if (sa.isSpell() && !sa.canPlay()) { - // Exceptional cases where canPlay should not run - boolean exemptFromCheck = false; - if (source.hasSuspend() && p.getGame().isCardExiled(source) && source.getCounters(CounterType.TIME) == 0) { - // A card is about to ETB from Suspend - exemptFromCheck = true; - } - - if (!exemptFromCheck) { - return false; - } + return false; } if (flippedToCast && !castFaceDown) {