mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-20 20:58:03 +00:00
Merge branch 'suspendHaste' into 'master'
Suspend: reworked suspend See merge request core-developers/forge!573
This commit is contained in:
@@ -44,7 +44,7 @@ public abstract class CardTraitBase extends GameObject implements IHasCardView {
|
|||||||
/** The temporarily suppressed. */
|
/** The temporarily suppressed. */
|
||||||
protected boolean temporarilySuppressed = false;
|
protected boolean temporarilySuppressed = false;
|
||||||
|
|
||||||
private Map<String, String> sVars = Maps.newHashMap();
|
protected Map<String, String> sVars = Maps.newHashMap();
|
||||||
|
|
||||||
/** Keys of descriptive (text) parameters. */
|
/** Keys of descriptive (text) parameters. */
|
||||||
private static final ImmutableList<String> descriptiveKeys = ImmutableList.<String>builder()
|
private static final ImmutableList<String> descriptiveKeys = ImmutableList.<String>builder()
|
||||||
|
|||||||
@@ -105,10 +105,6 @@ public class GameAction {
|
|||||||
boolean fromBattlefield = zoneFrom != null && zoneFrom.is(ZoneType.Battlefield);
|
boolean fromBattlefield = zoneFrom != null && zoneFrom.is(ZoneType.Battlefield);
|
||||||
boolean toHand = zoneTo.is(ZoneType.Hand);
|
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
|
//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)) {
|
if (c.isToken() && zoneFrom != null && !fromBattlefield && !zoneFrom.is(ZoneType.Command)) {
|
||||||
return c;
|
return c;
|
||||||
@@ -158,13 +154,6 @@ public class GameAction {
|
|||||||
c.setState(CardStateName.Original, true);
|
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
|
// Clean up the temporary Dash SVar when the Dashed card leaves the battlefield
|
||||||
if (fromBattlefield && c.getSVar("EndOfTurnLeavePlay").equals("Dash")) {
|
if (fromBattlefield && c.getSVar("EndOfTurnLeavePlay").equals("Dash")) {
|
||||||
c.removeSVar("EndOfTurnLeavePlay");
|
c.removeSVar("EndOfTurnLeavePlay");
|
||||||
@@ -295,10 +284,6 @@ public class GameAction {
|
|||||||
|
|
||||||
ReplacementResult repres = game.getReplacementHandler().run(repParams);
|
ReplacementResult repres = game.getReplacementHandler().run(repParams);
|
||||||
if (repres != ReplacementResult.NotReplaced) {
|
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
|
// reset failed manifested Cards back to original
|
||||||
if (c.isManifested()) {
|
if (c.isManifested()) {
|
||||||
c.turnFaceUp(false, false);
|
c.turnFaceUp(false, false);
|
||||||
@@ -315,10 +300,6 @@ public class GameAction {
|
|||||||
|
|
||||||
copied.getOwner().removeInboundToken(copied);
|
copied.getOwner().removeInboundToken(copied);
|
||||||
|
|
||||||
if (c.wasSuspendCast()) {
|
|
||||||
copied = GameAction.addSuspendTriggers(copied);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (suppress) {
|
if (suppress) {
|
||||||
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
game.getTriggerHandler().suppressMode(TriggerType.ChangesZone);
|
||||||
}
|
}
|
||||||
@@ -396,7 +377,7 @@ public class GameAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
game.getTriggerHandler().runTrigger(TriggerType.ChangesZone, runParams, true);
|
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<String, Object> runParams2 = Maps.newHashMap();
|
final Map<String, Object> runParams2 = Maps.newHashMap();
|
||||||
runParams2.put("Card", lastKnownInfo);
|
runParams2.put("Card", lastKnownInfo);
|
||||||
runParams2.put("OriginalController", zoneFrom.getPlayer());
|
runParams2.put("OriginalController", zoneFrom.getPlayer());
|
||||||
@@ -436,7 +417,6 @@ public class GameAction {
|
|||||||
|
|
||||||
if (fromBattlefield) {
|
if (fromBattlefield) {
|
||||||
if (!c.isToken()) {
|
if (!c.isToken()) {
|
||||||
copied.setSuspendCast(false);
|
|
||||||
copied.setState(CardStateName.Original, true);
|
copied.setState(CardStateName.Original, true);
|
||||||
}
|
}
|
||||||
// Soulbond unpairing
|
// Soulbond unpairing
|
||||||
@@ -1508,44 +1488,6 @@ public class GameAction {
|
|||||||
return sacrificed != null;
|
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
|
* @return the sacrificed Card in its new location, or {@code null} if the
|
||||||
* sacrifice wasn't successful.
|
* sacrifice wasn't successful.
|
||||||
|
|||||||
@@ -185,10 +185,6 @@ public class PlayEffect extends SpellAbilityEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(sa.hasParam("SuspendCast")) {
|
|
||||||
tgtCard.setSuspendCast(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// lands will be played
|
// lands will be played
|
||||||
if (tgtCard.isLand()) {
|
if (tgtCard.isLand()) {
|
||||||
if (controller.playLand(tgtCard, true)) {
|
if (controller.playLand(tgtCard, true)) {
|
||||||
|
|||||||
@@ -60,9 +60,6 @@ public class PumpAllEffect extends SpellAbilityEffect {
|
|||||||
for (String kw : hiddenkws) {
|
for (String kw : hiddenkws) {
|
||||||
tgtC.addHiddenExtrinsicKeyword(kw);
|
tgtC.addHiddenExtrinsicKeyword(kw);
|
||||||
}
|
}
|
||||||
if (suspend && !tgtC.hasSuspend()) {
|
|
||||||
tgtC.setSuspend(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sa.hasParam("RememberAllPumped")) {
|
if (sa.hasParam("RememberAllPumped")) {
|
||||||
sa.getHostCard().addRemembered(tgtC);
|
sa.getHostCard().addRemembered(tgtC);
|
||||||
|
|||||||
@@ -30,9 +30,11 @@ public class PumpEffect extends SpellAbilityEffect {
|
|||||||
private static void applyPump(final SpellAbility sa, final Card applyTo,
|
private static void applyPump(final SpellAbility sa, final Card applyTo,
|
||||||
final int a, final int d, final List<String> keywords,
|
final int a, final int d, final List<String> keywords,
|
||||||
final long timestamp) {
|
final long timestamp) {
|
||||||
|
final Card host = sa.getHostCard();
|
||||||
//if host is not on the battlefield don't apply
|
//if host is not on the battlefield don't apply
|
||||||
|
// Suspend should does Affect the Stack
|
||||||
if (sa.hasParam("UntilLoseControlOfHost")
|
if (sa.hasParam("UntilLoseControlOfHost")
|
||||||
&& !sa.getHostCard().isInPlay()) {
|
&& !(host.isInPlay() || host.isInZone(ZoneType.Stack))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Game game = sa.getActivatingPlayer().getGame();
|
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");
|
redrawPT |= kw.contains("CARDNAME's power and toughness are switched");
|
||||||
} else {
|
} else {
|
||||||
kws.add(kw);
|
kws.add(kw);
|
||||||
if (kw.equals("Suspend") && !applyTo.hasSuspend()) {
|
|
||||||
applyTo.setSuspend(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,8 +161,6 @@ public class Card extends GameEntity implements Comparable<Card> {
|
|||||||
|
|
||||||
private long bestowTimestamp = -1;
|
private long bestowTimestamp = -1;
|
||||||
private long transformedTimestamp = 0;
|
private long transformedTimestamp = 0;
|
||||||
private boolean suspendCast = false;
|
|
||||||
private boolean suspend = false;
|
|
||||||
private boolean tributed = false;
|
private boolean tributed = false;
|
||||||
private boolean embalmed = false;
|
private boolean embalmed = false;
|
||||||
private boolean eternalized = false;
|
private boolean eternalized = false;
|
||||||
@@ -3754,17 +3752,8 @@ public class Card extends GameEntity implements Comparable<Card> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final boolean hasSuspend() {
|
public final boolean hasSuspend() {
|
||||||
return suspend;
|
return hasKeyword(Keyword.SUSPEND) && getLastKnownZone().is(ZoneType.Exile)
|
||||||
}
|
&& getCounters(CounterType.TIME) >= 1;
|
||||||
public final void setSuspend(final boolean b) {
|
|
||||||
suspend = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final boolean wasSuspendCast() {
|
|
||||||
return suspendCast;
|
|
||||||
}
|
|
||||||
public final void setSuspendCast(final boolean b) {
|
|
||||||
suspendCast = b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final boolean isPhasedOut() {
|
public final boolean isPhasedOut() {
|
||||||
|
|||||||
@@ -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(" | 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.");
|
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);
|
final Trigger parsedPlayTrigger = TriggerHandler.parseTrigger(playTrig.toString(), card, intrinsic);
|
||||||
parsedPlayTrigger.setOverridingAbility(AbilityFactory.getAbility(abPlay, card));
|
parsedPlayTrigger.setOverridingAbility(saPlay);
|
||||||
|
|
||||||
inst.addTrigger(parsedUpkeepTrig);
|
inst.addTrigger(parsedUpkeepTrig);
|
||||||
inst.addTrigger(parsedPlayTrigger);
|
inst.addTrigger(parsedPlayTrigger);
|
||||||
@@ -4026,8 +4043,6 @@ public class CardFactoryUtil {
|
|||||||
inst.addSpellAbility(newSA);
|
inst.addSpellAbility(newSA);
|
||||||
|
|
||||||
} else if (keyword.startsWith("Suspend") && !keyword.equals("Suspend")) {
|
} else if (keyword.startsWith("Suspend") && !keyword.equals("Suspend")) {
|
||||||
// really needed?
|
|
||||||
card.setSuspend(true);
|
|
||||||
// only add it if suspend has counter and cost
|
// only add it if suspend has counter and cost
|
||||||
final String[] k = keyword.split(":");
|
final String[] k = keyword.split(":");
|
||||||
|
|
||||||
@@ -4051,8 +4066,6 @@ public class CardFactoryUtil {
|
|||||||
public void resolve() {
|
public void resolve() {
|
||||||
final Game game = card.getGame();
|
final Game game = card.getGame();
|
||||||
final Card c = game.getAction().exile(this.getHostCard(), this);
|
final Card c = game.getAction().exile(this.getHostCard(), this);
|
||||||
// better check?
|
|
||||||
c.setSuspend(true);
|
|
||||||
|
|
||||||
int counters = AbilityUtils.calculateAmount(c, k[1], this);
|
int counters = AbilityUtils.calculateAmount(c, k[1], this);
|
||||||
c.addCounter(CounterType.TIME, counters, c, true);
|
c.addCounter(CounterType.TIME, counters, c, true);
|
||||||
|
|||||||
@@ -1357,8 +1357,7 @@ public class CardProperty {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (property.startsWith("suspended")) {
|
} else if (property.startsWith("suspended")) {
|
||||||
if (!card.hasSuspend() || !game.isCardExiled(card)
|
if (!card.hasSuspend()) {
|
||||||
|| !(card.getCounters(CounterType.TIME) >= 1)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (property.startsWith("delved")) {
|
} else if (property.startsWith("delved")) {
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ public abstract class Spell extends SpellAbility implements java.io.Serializable
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!(isInstant || activator.canCastSorcery() || flash || getRestrictions().isInstantSpeed()
|
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()))) {
|
|| (card.isFaceDown() && !card.getLastKnownZone().is(ZoneType.Battlefield) && card.getState(CardStateName.Original).getType().isInstant()))) {
|
||||||
return false;
|
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)
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -854,6 +854,7 @@ public abstract class SpellAbility extends CardTraitBase implements ISpellAbilit
|
|||||||
// clear maps for copy, the values will be added later
|
// clear maps for copy, the values will be added later
|
||||||
clone.additionalAbilities = Maps.newHashMap();
|
clone.additionalAbilities = Maps.newHashMap();
|
||||||
clone.additionalAbilityLists = Maps.newHashMap();
|
clone.additionalAbilityLists = Maps.newHashMap();
|
||||||
|
clone.sVars = Maps.newHashMap();
|
||||||
// run special copy Ability to make a deep copy
|
// run special copy Ability to make a deep copy
|
||||||
CardFactory.copySpellAbility(this, clone, host, activ, lki);
|
CardFactory.copySpellAbility(this, clone, host, activ, lki);
|
||||||
} catch (final CloneNotSupportedException e) {
|
} catch (final CloneNotSupportedException e) {
|
||||||
|
|||||||
@@ -89,16 +89,7 @@ public class HumanPlay {
|
|||||||
|
|
||||||
// extra play check
|
// extra play check
|
||||||
if (sa.isSpell() && !sa.canPlay()) {
|
if (sa.isSpell() && !sa.canPlay()) {
|
||||||
// Exceptional cases where canPlay should not run
|
return false;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flippedToCast && !castFaceDown) {
|
if (flippedToCast && !castFaceDown) {
|
||||||
|
|||||||
Reference in New Issue
Block a user