mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-17 19:28:01 +00:00
Merge pull request #2199 from tool4ever/canthappen
ReplacementLayer.CantHappen
This commit is contained in:
@@ -164,8 +164,8 @@ public class GameAction {
|
||||
}
|
||||
if (!found) {
|
||||
c.clearControllers();
|
||||
if (c.removeChangedState()) {
|
||||
c.updateStateForView();
|
||||
if (cause != null) {
|
||||
unanimateOnAbortedChange(cause, c);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
@@ -365,7 +365,18 @@ public class GameAction {
|
||||
copied.getOwner().removeInboundToken(copied);
|
||||
|
||||
if (repres == ReplacementResult.Prevented) {
|
||||
if (game.getStack().isResolving(c) && !zoneTo.is(ZoneType.Graveyard)) {
|
||||
c.clearEtbCounters();
|
||||
c.clearControllers();
|
||||
if (cause != null) {
|
||||
unanimateOnAbortedChange(cause, c);
|
||||
if (cause.hasParam("Transformed") || cause.hasParam("FaceDown")) {
|
||||
c.setBackSide(false);
|
||||
c.changeToState(CardStateName.Original);
|
||||
}
|
||||
unattachCardLeavingBattlefield(c);
|
||||
}
|
||||
|
||||
if (c.isInZone(ZoneType.Stack) && !zoneTo.is(ZoneType.Graveyard)) {
|
||||
return moveToGraveyard(c, cause, params);
|
||||
}
|
||||
|
||||
@@ -373,10 +384,8 @@ public class GameAction {
|
||||
copied.clearDelved();
|
||||
copied.clearConvoked();
|
||||
copied.clearExploited();
|
||||
}
|
||||
|
||||
// was replaced with another Zone Change
|
||||
if (toBattlefield && !c.isInPlay()) {
|
||||
} else if (toBattlefield && !c.isInPlay()) {
|
||||
// was replaced with another Zone Change
|
||||
if (c.removeChangedState()) {
|
||||
c.updateStateForView();
|
||||
}
|
||||
@@ -2560,4 +2569,17 @@ public class GameAction {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void unanimateOnAbortedChange(final SpellAbility cause, final Card c) {
|
||||
if (cause.hasParam("AnimateSubAbility")) {
|
||||
long unanimateTimestamp = Long.valueOf(cause.getAdditionalAbility("AnimateSubAbility").getSVar("unanimateTimestamp"));
|
||||
c.removeChangedCardKeywords(unanimateTimestamp, 0);
|
||||
c.removeChangedCardTypes(unanimateTimestamp, 0);
|
||||
c.removeChangedName(unanimateTimestamp, 0);
|
||||
c.removeNewPT(unanimateTimestamp, 0);
|
||||
if (c.removeChangedCardTraits(unanimateTimestamp, 0)) {
|
||||
c.updateStateForView();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,10 +136,10 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
|
||||
CardCollectionView list;
|
||||
|
||||
if (!sa.usesTargeting() && !sa.hasParam("Defined")) {
|
||||
list = game.getCardsIn(ZoneType.Battlefield);
|
||||
} else {
|
||||
if (sa.usesTargeting() || sa.hasParam("Defined")) {
|
||||
list = getTargetPlayers(sa).getCardsIn(ZoneType.Battlefield);
|
||||
} else {
|
||||
list = game.getCardsIn(ZoneType.Battlefield);
|
||||
}
|
||||
|
||||
list = CardLists.getValidCards(list, valid, sa.getActivatingPlayer(), host, sa);
|
||||
@@ -155,18 +155,18 @@ public class AnimateAllEffect extends AnimateEffectBase {
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(c));
|
||||
|
||||
final GameCommand unanimate = new GameCommand() {
|
||||
private static final long serialVersionUID = -5861759814760561373L;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
doUnanimate(c, timestamp);
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(c));
|
||||
}
|
||||
};
|
||||
|
||||
if (!permanent) {
|
||||
final GameCommand unanimate = new GameCommand() {
|
||||
private static final long serialVersionUID = -5861759814760561373L;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
doUnanimate(c, timestamp);
|
||||
|
||||
game.fireEvent(new GameEventCardStatsChanged(c));
|
||||
}
|
||||
};
|
||||
|
||||
addUntilCommand(sa, unanimate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,9 +171,11 @@ public class ChangeZoneAllEffect extends SpellAbilityEffect {
|
||||
// need LKI before Animate does apply
|
||||
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c));
|
||||
|
||||
final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility");
|
||||
source.addRemembered(c);
|
||||
AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility"));
|
||||
AbilityUtils.resolve(animate);
|
||||
source.removeRemembered(c);
|
||||
animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp()));
|
||||
}
|
||||
if (sa.hasParam("Tapped")) {
|
||||
c.setTapped(true);
|
||||
|
||||
@@ -575,6 +575,17 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
movedCard = game.getAction().moveToLibrary(gameCard, libraryPosition, sa);
|
||||
} else {
|
||||
if (destination.equals(ZoneType.Battlefield)) {
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
|
||||
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
|
||||
if (sa.isReplacementAbility()) {
|
||||
ReplacementEffect re = sa.getReplacementEffect();
|
||||
moveParams.put(AbilityKey.ReplacementEffect, re);
|
||||
if (ReplacementType.Moved.equals(re.getMode()) && sa.getReplacingObject(AbilityKey.CardLKI) != null) {
|
||||
moveParams.put(AbilityKey.CardLKI, sa.getReplacingObject(AbilityKey.CardLKI));
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasParam("Tapped") || sa.isNinjutsu()) {
|
||||
gameCard.setTapped(true);
|
||||
}
|
||||
@@ -583,6 +594,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if (sa.hasParam("Transformed")) {
|
||||
if (gameCard.isDoubleFaced()) {
|
||||
// need LKI before Animate does apply
|
||||
if (!moveParams.containsKey(AbilityKey.CardLKI)) {
|
||||
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(gameCard));
|
||||
}
|
||||
gameCard.changeCardState("Transform", null, sa);
|
||||
} else {
|
||||
// If it can't Transform, don't change zones.
|
||||
@@ -650,26 +665,17 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
}
|
||||
|
||||
Map<AbilityKey, Object> moveParams = AbilityKey.newMap();
|
||||
moveParams.put(AbilityKey.LastStateBattlefield, lastStateBattlefield);
|
||||
moveParams.put(AbilityKey.LastStateGraveyard, lastStateGraveyard);
|
||||
if (sa.isReplacementAbility()) {
|
||||
ReplacementEffect re = sa.getReplacementEffect();
|
||||
moveParams.put(AbilityKey.ReplacementEffect, re);
|
||||
if (ReplacementType.Moved.equals(re.getMode()) && sa.getReplacingObject(AbilityKey.CardLKI) != null) {
|
||||
moveParams.put(AbilityKey.CardLKI, sa.getReplacingObject(AbilityKey.CardLKI));
|
||||
}
|
||||
}
|
||||
|
||||
if (sa.hasAdditionalAbility("AnimateSubAbility")) {
|
||||
// need LKI before Animate does apply
|
||||
if (!moveParams.containsKey(AbilityKey.CardLKI)) {
|
||||
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(gameCard));
|
||||
}
|
||||
|
||||
final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility");
|
||||
hostCard.addRemembered(gameCard);
|
||||
AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility"));
|
||||
AbilityUtils.resolve(animate);
|
||||
hostCard.removeRemembered(gameCard);
|
||||
animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp()));
|
||||
}
|
||||
|
||||
// need to be facedown before it hits the battlefield in case of Replacement Effects or Trigger
|
||||
@@ -1310,9 +1316,11 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
// need LKI before Animate does apply
|
||||
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c));
|
||||
|
||||
final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility");
|
||||
source.addRemembered(c);
|
||||
AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility"));
|
||||
AbilityUtils.resolve(animate);
|
||||
source.removeRemembered(c);
|
||||
animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp()));
|
||||
}
|
||||
if (sa.hasParam("GainControl")) {
|
||||
final String g = sa.getParam("GainControl");
|
||||
@@ -1331,6 +1339,10 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if (sa.hasParam("Transformed")) {
|
||||
if (c.isDoubleFaced()) {
|
||||
// need LKI before Animate does apply
|
||||
if (!moveParams.containsKey(AbilityKey.CardLKI)) {
|
||||
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c));
|
||||
}
|
||||
c.changeCardState("Transform", null, sa);
|
||||
} else {
|
||||
// If it can't Transform, don't change zones.
|
||||
@@ -1390,7 +1402,7 @@ public class ChangeZoneEffect extends SpellAbilityEffect {
|
||||
|
||||
movedCard.setTimestamp(ts);
|
||||
|
||||
if (sa.hasParam("AttachAfter") && movedCard.isAttachment()) {
|
||||
if (sa.hasParam("AttachAfter") && movedCard.isAttachment() && movedCard.isInPlay()) {
|
||||
CardCollection list = AbilityUtils.getDefinedCards(source, sa.getParam("AttachAfter"), sa);
|
||||
if (list.isEmpty()) {
|
||||
list = CardLists.getValidCards(game.getCardsIn(ZoneType.Battlefield), sa.getParam("AttachAfter"), c.getController(), c, sa);
|
||||
|
||||
@@ -417,13 +417,13 @@ public class DigEffect extends SpellAbilityEffect {
|
||||
}
|
||||
if (sa.hasAdditionalAbility("AnimateSubAbility")) {
|
||||
// need LKI before Animate does apply
|
||||
if (!moveParams.containsKey(AbilityKey.CardLKI)) {
|
||||
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c));
|
||||
}
|
||||
moveParams.put(AbilityKey.CardLKI, CardUtil.getLKICopy(c));
|
||||
|
||||
final SpellAbility animate = sa.getAdditionalAbility("AnimateSubAbility");
|
||||
host.addRemembered(c);
|
||||
AbilityUtils.resolve(sa.getAdditionalAbility("AnimateSubAbility"));
|
||||
AbilityUtils.resolve(animate);
|
||||
host.removeRemembered(c);
|
||||
animate.setSVar("unanimateTimestamp", String.valueOf(game.getTimestamp()));
|
||||
}
|
||||
c = game.getAction().moveTo(zone, c, sa, moveParams);
|
||||
if (destZone1.equals(ZoneType.Battlefield)) {
|
||||
|
||||
@@ -6,6 +6,7 @@ package forge.game.replacement;
|
||||
*
|
||||
*/
|
||||
public enum ReplacementLayer {
|
||||
CantHappen, // 614.17
|
||||
Control, // 616.1b
|
||||
Copy, // 616.1c
|
||||
Transform, // 616.1d
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Grafdigger's Cage
|
||||
ManaCost:1
|
||||
Types:Artifact
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Description$ Creature cards in graveyards and libraries can't enter the battlefield.
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Layer$ CantHappen | Description$ Creature cards in graveyards and libraries can't enter the battlefield.
|
||||
S:Mode$ CantBeCast | Origin$ Graveyard,Library | Description$ Players can't cast spells from graveyards or libraries.
|
||||
SVar:NonStackingEffect:True
|
||||
AI:RemoveDeck:Random
|
||||
|
||||
@@ -3,8 +3,9 @@ ManaCost:1 W B
|
||||
Types:Legendary Creature Rat Pilot
|
||||
PT:4/3
|
||||
T:Mode$ Phase | Phase$ BeginCombat | ValidPlayer$ You | TriggerZones$ Battlefield | Execute$ TrigReturn | TriggerDescription$ At the beginning of combat on your turn, return target Vehicle card from your graveyard to the battlefield. It gains haste. Return it to its owner's hand at the beginning of your next end step.
|
||||
SVar:TrigReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Select target Vehicle card in your graveyard | ValidTgts$ Vehicle.YouOwn | AnimateSubAbility$ Animate
|
||||
SVar:Animate:DB$ Animate | Keywords$ Haste | Defined$ Remembered | Duration$ Permanent | AtEOT$ Hand
|
||||
SVar:TrigReturn:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | TgtPrompt$ Select target Vehicle card in your graveyard | ValidTgts$ Vehicle.YouOwn | SubAbility$ Animate | RememberChanged$ True | AtEOT$ Hand
|
||||
SVar:Animate:DB$ Animate | Keywords$ Haste | Defined$ Remembered | Duration$ Permanent | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
DeckHas:Ability$Graveyard
|
||||
DeckNeeds:Type$Vehicle
|
||||
Oracle:At the beginning of combat on your turn, return target Vehicle card from your graveyard to the battlefield. It gains haste. Return it to its owner's hand at the beginning of your next end step.
|
||||
|
||||
@@ -5,7 +5,7 @@ PT:3/3
|
||||
K:Vigilance
|
||||
K:Menace
|
||||
K:Lifelink
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Description$ Creature cards in graveyards can't enter the battlefield.
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard | Destination$ Battlefield | ValidLKI$ Creature.Other | Prevent$ True | Layer$ CantHappen | Description$ Creature cards in graveyards can't enter the battlefield.
|
||||
S:Mode$ CantBeCast | Origin$ Graveyard | Description$ Players can't cast spells from graveyards.
|
||||
SVar:NonStackingEffect:True
|
||||
Oracle:Vigilance, menace, lifelink\nCreature cards in graveyards can't enter the battlefield.\nPlayers can't cast spells from graveyards.
|
||||
|
||||
@@ -5,8 +5,9 @@ PT:3/4
|
||||
K:Flying
|
||||
K:Haste
|
||||
T:Mode$ Attacks | ValidCard$ Card.Self | Execute$ TrigChange | TriggerZones$ Battlefield | TriggerDescription$ Whenever CARDNAME attacks, return target creature card from your graveyard to the battlefield tapped and attacking. It gains "When you don't control a legendary Vampire, exile this creature."
|
||||
SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouOwn | Tapped$ True | Attacking$ True | AnimateSubAbility$ DBAnimate
|
||||
SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Duration$ Permanent | Triggers$ TrigOlivia
|
||||
SVar:TrigChange:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | ValidTgts$ Creature.YouOwn | Tapped$ True | Attacking$ True | RememberChanged$ True | SubAbility$ DBAnimate
|
||||
SVar:DBAnimate:DB$ Animate | Defined$ Remembered | Duration$ Permanent | Triggers$ TrigOlivia | SubAbility$ DBCleanup
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:TrigOlivia:Mode$ Always | TriggerZones$ Battlefield | IsPresent$ Vampire.YouCtrl+Legendary | PresentCompare$ EQ0 | Execute$ TrigExile | TriggerDescription$ When you don't control a legendary Vampire, exile this creature.
|
||||
SVar:TrigExile:DB$ ChangeZone | Defined$ Self | Origin$ Battlefield | Destination$ Exile
|
||||
SVar:HasAttackEffect:TRUE
|
||||
|
||||
@@ -2,6 +2,7 @@ Name:Swords to Plowshares
|
||||
ManaCost:W
|
||||
Types:Instant
|
||||
A:SP$ ChangeZone | ValidTgts$ Creature | Origin$ Battlefield | Destination$ Exile | RememberLKI$ True | SubAbility$ DBGainLife | SpellDescription$ Exile target creature.
|
||||
SVar:DBGainLife:DB$ GainLife | Defined$ RememberedController | LifeAmount$ X | SpellDescription$ Its controller gains life equal to its power.
|
||||
SVar:DBGainLife:DB$ GainLife | Defined$ RememberedController | LifeAmount$ X | SubAbility$ DBCleanup | SpellDescription$ Its controller gains life equal to its power.
|
||||
SVar:DBCleanup:DB$ Cleanup | ClearRemembered$ True
|
||||
SVar:X:RememberedLKI$CardPower
|
||||
Oracle:Exile target creature. Its controller gains life equal to its power.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Name:Weathered Runestone
|
||||
ManaCost:2
|
||||
Types:Artifact
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Permanent.nonland | Prevent$ True | Description$ Nonland permanent cards in graveyards and libraries can't enter the battlefield.
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | Origin$ Graveyard,Library | Destination$ Battlefield | ValidLKI$ Permanent.nonland | Prevent$ True | Layer$ CantHappen | Description$ Nonland permanent cards in graveyards and libraries can't enter the battlefield.
|
||||
S:Mode$ CantBeCast | Origin$ Graveyard,Library | Description$ Players can't cast spells from graveyards or libraries.
|
||||
SVar:NonStackingEffect:True
|
||||
AI:RemoveDeck:Random
|
||||
|
||||
@@ -2,7 +2,7 @@ Name:Worms of the Earth
|
||||
ManaCost:2 B B B
|
||||
Types:Enchantment
|
||||
S:Mode$ CantPlayLand | Description$ Players can't play lands.
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | Destination$ Battlefield | ValidCard$ Land | Prevent$ True | Description$ Lands can't enter the battlefield.
|
||||
R:Event$ Moved | ActiveZones$ Battlefield | Destination$ Battlefield | ValidCard$ Land | Prevent$ True | Layer$ CantHappen | Description$ Lands can't enter the battlefield.
|
||||
T:Mode$ Phase | Phase$ Upkeep | TriggerZones$ Battlefield | Execute$ RepeatAbility | TriggerDescription$ At the beginning of each upkeep, any player may sacrifice two lands or have CARDNAME deal 5 damage to that player. If a player does either, destroy CARDNAME.
|
||||
SVar:RepeatAbility:DB$ RepeatEach | RepeatPlayers$ Player | RepeatSubAbility$ DBChoose
|
||||
SVar:DBChoose:DB$ GenericChoice | Defined$ Player.IsRemembered | Choices$ SacTwoLands,DealDmg | AILogic$ PayUnlessCost
|
||||
|
||||
@@ -114,9 +114,11 @@ public class HumanPlay {
|
||||
|
||||
final HumanPlaySpellAbility req = new HumanPlaySpellAbility(controller, sa);
|
||||
if (!req.playAbility(true, false, false)) {
|
||||
if (flippedToCast && !castFaceDown) {
|
||||
Card rollback = p.getGame().getCardState(sa.getHostCard());
|
||||
if (castFaceDown) {
|
||||
rollback.setFaceDown(false);
|
||||
} else if (flippedToCast) {
|
||||
// need to get the changed card if able
|
||||
Card rollback = p.getGame().getCardState(sa.getHostCard());
|
||||
rollback.turnFaceDown(true);
|
||||
//need to set correct imagekey when forcing facedown
|
||||
rollback.setImageKey(ImageKeys.getTokenKey(isforetold ? ImageKeys.FORETELL_IMAGE : ImageKeys.HIDDEN_CARD));
|
||||
|
||||
Reference in New Issue
Block a user