Merge pull request #2199 from tool4ever/canthappen

ReplacementLayer.CantHappen
This commit is contained in:
Anthony Calosa
2023-01-05 16:53:00 +08:00
committed by GitHub
14 changed files with 93 additions and 51 deletions

View File

@@ -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();
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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)) {

View File

@@ -6,6 +6,7 @@ package forge.game.replacement;
*
*/
public enum ReplacementLayer {
CantHappen, // 614.17
Control, // 616.1b
Copy, // 616.1c
Transform, // 616.1d

View File

@@ -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

View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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));