Reimplement Animate Dead and friends; fixes #5250 (#9581)

This commit is contained in:
Alex W. Jackson 2022-09-27 20:08:50 -04:00 committed by GitHub
parent c89e5077af
commit b1b78d6db0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 398 additions and 604 deletions

View file

@ -1,64 +1,40 @@
package mage.cards.a; package mage.cards.a;
import java.util.UUID; import java.util.UUID;
import mage.MageObjectReference; import mage.abilities.common.AnimateDeadTriggeredAbility;
import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.LeavesBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.SourceOnBattlefieldCondition; import mage.abilities.effects.common.AttachEffect;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.ContinuousEffect;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.BoostEnchantedEffect; import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
import mage.abilities.effects.common.continuous.SourceEffect;
import mage.abilities.keyword.EnchantAbility; import mage.abilities.keyword.EnchantAbility;
import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterCreatureCard;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetCardInGraveyard;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
/** /**
* * @author LevelX2, awjackson
* @author LevelX2
*/ */
public final class AnimateDead extends CardImpl { public final class AnimateDead extends CardImpl {
private static final FilterCreatureCard filter = new FilterCreatureCard("creature card in a graveyard");
public AnimateDead(UUID ownerId, CardSetInfo setInfo) { public AnimateDead(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}");
this.subtype.add(SubType.AURA); this.subtype.add(SubType.AURA);
// Enchant creature card in a graveyard // Enchant creature card in a graveyard
TargetCardInGraveyard auraTarget = new TargetCardInGraveyard(new FilterCreatureCard("creature card in a graveyard")); TargetCardInGraveyard auraTarget = new TargetCardInGraveyard(filter);
this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new AnimateDeadAttachEffect(Outcome.PutCreatureInPlay)); this.getSpellAbility().addEffect(new AttachEffect(Outcome.PutCreatureInPlay));
Ability enchantAbility = new EnchantAbility(auraTarget.getTargetName()); this.addAbility(new EnchantAbility(auraTarget.getTargetName()));
this.addAbility(enchantAbility);
// When Animate Dead enters the battlefield, if it's on the battlefield, it loses "enchant creature card in a graveyard" this.addAbility(new AnimateDeadTriggeredAbility());
// and gains "enchant creature put onto the battlefield with Animate Dead." Return enchanted creature card to the battlefield
// under your control and attach Animate Dead to it. When Animate Dead leaves the battlefield, that creature's controller sacrifices it.
Ability ability = new ConditionalInterveningIfTriggeredAbility(
new EntersBattlefieldTriggeredAbility(new AnimateDeadReAttachEffect(), false),
SourceOnBattlefieldCondition.instance,
"When {this} enters the battlefield, if it's on the battlefield, it loses \"enchant creature card in a graveyard\" and gains \"enchant creature put onto the battlefield with {this}.\" Return enchanted creature card to the battlefield under your control and attach {this} to it.");
ability.addEffect(new AnimateDeadChangeAbilityEffect());
this.addAbility(ability);
this.addAbility(new LeavesBattlefieldTriggeredAbility(new AnimateDeadLeavesBattlefieldTriggeredEffect(), false));
// Enchanted creature gets -1/-0. // Enchanted creature gets -1/-0.
this.addAbility(new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(-1, 0, Duration.WhileOnBattlefield))); this.addAbility(new SimpleStaticAbility(new BoostEnchantedEffect(-1, 0)));
} }
private AnimateDead(final AnimateDead card) { private AnimateDead(final AnimateDead card) {
@ -70,205 +46,3 @@ public final class AnimateDead extends CardImpl {
return new AnimateDead(this); return new AnimateDead(this);
} }
} }
class AnimateDeadReAttachEffect extends OneShotEffect {
public AnimateDeadReAttachEffect() {
super(Outcome.Benefit);
this.staticText = "return enchanted creature card to the battlefield under your control and attach {this} to it";
}
public AnimateDeadReAttachEffect(final AnimateDeadReAttachEffect effect) {
super(effect);
}
@Override
public AnimateDeadReAttachEffect copy() {
return new AnimateDeadReAttachEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Permanent animateDead = game.getPermanent(source.getSourceId());
if (controller != null && animateDead != null) {
Card cardInGraveyard = game.getCard(animateDead.getAttachedTo());
if (cardInGraveyard == null || game.getState().getZone(cardInGraveyard.getId()) != Zone.GRAVEYARD) {
return true;
}
// put card into play from Graveyard
controller.moveCards(cardInGraveyard, Zone.BATTLEFIELD, source, game);
Permanent enchantedCreature = game.getPermanent(cardInGraveyard.getId());
if (enchantedCreature != null) {
FilterCreaturePermanent filter = new FilterCreaturePermanent("enchant creature put onto the battlefield with Animate Dead");
filter.add(new PermanentIdPredicate(cardInGraveyard.getId()));
Target target = new TargetCreaturePermanent(filter);
target.setNotTarget(true); // Bug #7772
target.addTarget(enchantedCreature.getId(), source, game);
animateDead.getSpellAbility().getTargets().clear();
animateDead.getSpellAbility().getTargets().add(target);
enchantedCreature.addAttachment(animateDead.getId(), source, game);
ContinuousEffect effect = new AnimateDeadAttachToPermanentEffect();
effect.setTargetPointer(new FixedTarget(enchantedCreature, game));
game.addEffect(effect, source);
}
return true;
}
return false;
}
}
class AnimateDeadLeavesBattlefieldTriggeredEffect extends OneShotEffect {
public AnimateDeadLeavesBattlefieldTriggeredEffect() {
super(Outcome.Benefit);
this.staticText = "enchanted creature's controller sacrifices it";
}
public AnimateDeadLeavesBattlefieldTriggeredEffect(final AnimateDeadLeavesBattlefieldTriggeredEffect effect) {
super(effect);
}
@Override
public AnimateDeadLeavesBattlefieldTriggeredEffect copy() {
return new AnimateDeadLeavesBattlefieldTriggeredEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId());
if (controller != null && sourcePermanent != null) {
if (sourcePermanent.getAttachedTo() != null) {
Permanent attachedTo = game.getPermanent(sourcePermanent.getAttachedTo());
if (attachedTo != null && attachedTo.getZoneChangeCounter(game) == sourcePermanent.getAttachedToZoneChangeCounter()) {
attachedTo.sacrifice(source, game);
}
}
return true;
}
return false;
}
}
class AnimateDeadAttachEffect extends OneShotEffect {
public AnimateDeadAttachEffect(Outcome outcome) {
super(outcome);
}
public AnimateDeadAttachEffect(Outcome outcome, String rule) {
super(outcome);
staticText = rule;
}
public AnimateDeadAttachEffect(final AnimateDeadAttachEffect effect) {
super(effect);
}
@Override
public AnimateDeadAttachEffect copy() {
return new AnimateDeadAttachEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getFirstTarget());
if (card != null && game.getState().getZone(source.getFirstTarget()) == Zone.GRAVEYARD) {
// Card have no attachedTo attribute yet so write ref only to enchantment now
Permanent enchantment = game.getPermanent(source.getSourceId());
if (enchantment != null) {
enchantment.attachTo(card.getId(), source, game);
}
return true;
}
return false;
}
}
class AnimateDeadChangeAbilityEffect extends ContinuousEffectImpl implements SourceEffect {
private static final Ability newAbility = new EnchantAbility("creature put onto the battlefield with Animate Dead");
static {
newAbility.setRuleAtTheTop(true);
}
public AnimateDeadChangeAbilityEffect() {
super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
staticText = "it loses \"enchant creature card in a graveyard\" and gains \"enchant creature put onto the battlefield with Animate Dead\"";
}
public AnimateDeadChangeAbilityEffect(final AnimateDeadChangeAbilityEffect effect) {
super(effect);
}
@Override
public AnimateDeadChangeAbilityEffect copy() {
return new AnimateDeadChangeAbilityEffect(this);
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
affectedObjectList.add(new MageObjectReference(source.getSourceId(), game));
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = affectedObjectList.get(0).getPermanent(game);
if (permanent != null) {
Ability abilityToRemove = null;
for (Ability ability : permanent.getAbilities()) {
if (ability instanceof EnchantAbility) {
abilityToRemove = ability;
ability.getTargets().clear();
}
}
permanent.removeAbility(abilityToRemove, source.getSourceId(), game);
permanent.addAbility(newAbility, source.getSourceId(), game);
return true;
}
return false;
}
}
class AnimateDeadAttachToPermanentEffect extends ContinuousEffectImpl {
public AnimateDeadAttachToPermanentEffect() {
super(Duration.Custom, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Neutral);
}
public AnimateDeadAttachToPermanentEffect(final AnimateDeadAttachToPermanentEffect effect) {
super(effect);
}
@Override
public AnimateDeadAttachToPermanentEffect copy() {
return new AnimateDeadAttachToPermanentEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Permanent animateDead = game.getPermanent(source.getSourceId());
if (animateDead != null) {
// The target has to be changed to CreaturePermanent because the reset from card resets it to Card in Graveyard
FilterCreaturePermanent filter = new FilterCreaturePermanent("enchant creature put onto the battlefield with Animate Dead");
filter.add(new PermanentIdPredicate(getTargetPointer().getFirst(game, source)));
Target target = new TargetCreaturePermanent(filter);
target.setNotTarget(true); // Bug #7772
target.addTarget(((FixedTarget) getTargetPointer()).getTarget(), source, game);
animateDead.getSpellAbility().getTargets().clear();
animateDead.getSpellAbility().getTargets().add(target);
}
if (animateDead == null) {
discard();
}
return true;
}
}

View file

@ -1,79 +1,60 @@
package mage.cards.d; package mage.cards.d;
import java.util.UUID; import java.util.UUID;
import mage.MageObjectReference;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.Mode; import mage.abilities.Mode;
import mage.abilities.common.AnimateDeadTriggeredAbility;
import mage.abilities.common.BeginningOfUpkeepTriggeredAbility; import mage.abilities.common.BeginningOfUpkeepTriggeredAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.LeavesBattlefieldTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.SourceOnBattlefieldCondition;
import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.costs.mana.ManaCostsImpl;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.Effect; import mage.abilities.effects.Effect;
import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.AttachEffect;
import mage.abilities.effects.common.DoIfCostPaid; import mage.abilities.effects.common.DoIfCostPaid;
import mage.abilities.effects.common.DontUntapInControllersUntapStepEnchantedEffect; import mage.abilities.effects.common.DontUntapInControllersUntapStepEnchantedEffect;
import mage.abilities.effects.common.UntapEnchantedEffect; import mage.abilities.effects.common.UntapEnchantedEffect;
import mage.abilities.effects.common.continuous.BoostEnchantedEffect; import mage.abilities.effects.common.continuous.BoostEnchantedEffect;
import mage.abilities.effects.common.continuous.SourceEffect;
import mage.abilities.keyword.EnchantAbility; import mage.abilities.keyword.EnchantAbility;
import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.CardType;
import mage.constants.Outcome;
import mage.constants.SubType;
import mage.constants.TargetController;
import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterCreatureCard;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.Game; import mage.game.Game;
import mage.game.permanent.Permanent; import mage.game.permanent.Permanent;
import mage.players.Player; import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetCardInGraveyard;
import mage.target.common.TargetCreaturePermanent;
import mage.util.CardUtil; import mage.util.CardUtil;
/** /**
* * @author LevelX2, awjackson
* @author LevelX2
*/ */
public final class DanceOfTheDead extends CardImpl { public final class DanceOfTheDead extends CardImpl {
private static final FilterCreatureCard filter = new FilterCreatureCard("creature card in a graveyard");
public DanceOfTheDead(UUID ownerId, CardSetInfo setInfo) { public DanceOfTheDead(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}");
this.subtype.add(SubType.AURA); this.subtype.add(SubType.AURA);
// Enchant creature card in a graveyard // Enchant creature card in a graveyard
TargetCardInGraveyard auraTarget = new TargetCardInGraveyard(new FilterCreatureCard("creature card in a graveyard")); TargetCardInGraveyard auraTarget = new TargetCardInGraveyard(filter);
this.getSpellAbility().addTarget(auraTarget); this.getSpellAbility().addTarget(auraTarget);
this.getSpellAbility().addEffect(new DanceOfTheDeadAttachEffect(Outcome.PutCreatureInPlay)); this.getSpellAbility().addEffect(new AttachEffect(Outcome.PutCreatureInPlay));
Ability enchantAbility = new EnchantAbility(auraTarget.getTargetName()); this.addAbility(new EnchantAbility(auraTarget.getTargetName()));
this.addAbility(enchantAbility);
// When Dance of the Dead enters the battlefield, if it's on the battlefield, it loses "enchant creature card in a graveyard" and gains "enchant creature put onto the battlefield with Dance of the Dead." Put enchanted creature card to the battlefield tapped under your control and attach Dance of the Dead to it. When Dance of the Dead leaves the battlefield, that creature's controller sacrifices it. this.addAbility(new AnimateDeadTriggeredAbility(false, true));
Ability ability = new ConditionalInterveningIfTriggeredAbility(
new EntersBattlefieldTriggeredAbility(new DanceOfTheDeadReAttachEffect(), false),
SourceOnBattlefieldCondition.instance,
"When {this} enters the battlefield, if it's on the battlefield, "
+ "it loses \"enchant creature card in a graveyard\" and gains "
+ "\"enchant creature put onto the battlefield with {this}.\" "
+ "Return enchanted creature card to the battlefield tapped under your control and attach {this} to it.");
ability.addEffect(new DanceOfTheDeadChangeAbilityEffect());
this.addAbility(ability);
this.addAbility(new LeavesBattlefieldTriggeredAbility(new DanceOfTheDeadLeavesBattlefieldTriggeredEffect(), false));
// Enchanted creature gets +1/+1 and doesn't untap during its controller's untap step. // Enchanted creature gets +1/+1 and doesn't untap during its controller's untap step.
ability = new SimpleStaticAbility(Zone.BATTLEFIELD, new BoostEnchantedEffect(1, 1, Duration.WhileOnBattlefield)); Ability ability = new SimpleStaticAbility(new BoostEnchantedEffect(1, 1));
Effect effect = new DontUntapInControllersUntapStepEnchantedEffect(); Effect effect = new DontUntapInControllersUntapStepEnchantedEffect();
effect.setText("and doesn't untap during its controller's untap step"); effect.setText("and doesn't untap during its controller's untap step");
ability.addEffect(effect); ability.addEffect(effect);
this.addAbility(ability); this.addAbility(ability);
// At the beginning of the upkeep of enchanted creature's controller, that player may pay {1}{B}. If they do, untap that creature. // At the beginning of the upkeep of enchanted creature's controller, that player may pay {1}{B}. If they do, untap that creature.
this.addAbility(new BeginningOfUpkeepTriggeredAbility(Zone.BATTLEFIELD, new DanceOfTheDeadDoIfCostPaidEffect(), TargetController.CONTROLLER_ATTACHED_TO, false)); this.addAbility(new BeginningOfUpkeepTriggeredAbility(new DanceOfTheDeadDoIfCostPaidEffect(), TargetController.CONTROLLER_ATTACHED_TO, false));
} }
private DanceOfTheDead(final DanceOfTheDead card) { private DanceOfTheDead(final DanceOfTheDead card) {
@ -86,169 +67,6 @@ public final class DanceOfTheDead extends CardImpl {
} }
} }
class DanceOfTheDeadReAttachEffect extends OneShotEffect {
public DanceOfTheDeadReAttachEffect() {
super(Outcome.Benefit);
this.staticText = "Return enchanted creature card to the battlefield under your control and attach {this} to it";
}
public DanceOfTheDeadReAttachEffect(final DanceOfTheDeadReAttachEffect effect) {
super(effect);
}
@Override
public DanceOfTheDeadReAttachEffect copy() {
return new DanceOfTheDeadReAttachEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Permanent enchantment = game.getPermanent(source.getSourceId());
if (controller != null && enchantment != null) {
Card cardInGraveyard = game.getCard(enchantment.getAttachedTo());
if (cardInGraveyard == null) {
return true;
}
// put card into play
controller.moveCards(cardInGraveyard, Zone.BATTLEFIELD, source, game, true, false, false, null);
Permanent enchantedCreature = game.getPermanent(cardInGraveyard.getId());
FilterCreaturePermanent filter = new FilterCreaturePermanent("enchant creature put onto the battlefield with Dance of the Dead");
filter.add(new PermanentIdPredicate(cardInGraveyard.getId()));
Target target = new TargetCreaturePermanent(filter);
//enchantAbility.setTargetName(target.getTargetName());
if (enchantedCreature != null) {
target.addTarget(enchantedCreature.getId(), source, game);
enchantment.getSpellAbility().getTargets().clear();
enchantment.getSpellAbility().getTargets().add(target);
enchantedCreature.addAttachment(enchantment.getId(), source, game);
}
return true;
}
return false;
}
}
class DanceOfTheDeadLeavesBattlefieldTriggeredEffect extends OneShotEffect {
public DanceOfTheDeadLeavesBattlefieldTriggeredEffect() {
super(Outcome.Benefit);
this.staticText = "enchanted creature's controller sacrifices it";
}
public DanceOfTheDeadLeavesBattlefieldTriggeredEffect(final DanceOfTheDeadLeavesBattlefieldTriggeredEffect effect) {
super(effect);
}
@Override
public DanceOfTheDeadLeavesBattlefieldTriggeredEffect copy() {
return new DanceOfTheDeadLeavesBattlefieldTriggeredEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId());
if (controller != null && sourcePermanent != null) {
if (sourcePermanent.getAttachedTo() != null) {
Permanent attachedTo = game.getPermanent(sourcePermanent.getAttachedTo());
if (attachedTo != null && attachedTo.getZoneChangeCounter(game) == sourcePermanent.getAttachedToZoneChangeCounter()) {
attachedTo.sacrifice(source, game);
}
}
return true;
}
return false;
}
}
class DanceOfTheDeadAttachEffect extends OneShotEffect {
public DanceOfTheDeadAttachEffect(Outcome outcome) {
super(outcome);
}
public DanceOfTheDeadAttachEffect(Outcome outcome, String rule) {
super(outcome);
staticText = rule;
}
public DanceOfTheDeadAttachEffect(final DanceOfTheDeadAttachEffect effect) {
super(effect);
}
@Override
public DanceOfTheDeadAttachEffect copy() {
return new DanceOfTheDeadAttachEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Card card = game.getCard(source.getFirstTarget());
if (card != null && game.getState().getZone(source.getFirstTarget()) == Zone.GRAVEYARD) {
// Card have no attachedTo attribute yet so write ref only to enchantment now
Permanent enchantment = game.getPermanent(source.getSourceId());
if (enchantment != null) {
enchantment.attachTo(card.getId(), source, game);
}
return true;
}
return false;
}
}
class DanceOfTheDeadChangeAbilityEffect extends ContinuousEffectImpl implements SourceEffect {
private static final Ability newAbility = new EnchantAbility("creature put onto the battlefield with Dance of the Dead");
static {
newAbility.setRuleAtTheTop(true);
}
public DanceOfTheDeadChangeAbilityEffect() {
super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility);
staticText = "it loses \"enchant creature card in a graveyard\" and gains \"enchant creature put onto the battlefield with Dance of the Dead\"";
}
public DanceOfTheDeadChangeAbilityEffect(final DanceOfTheDeadChangeAbilityEffect effect) {
super(effect);
}
@Override
public DanceOfTheDeadChangeAbilityEffect copy() {
return new DanceOfTheDeadChangeAbilityEffect(this);
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
affectedObjectList.add(new MageObjectReference(source.getSourceId(), game));
}
@Override
public boolean apply(Game game, Ability source) {
Permanent permanent = affectedObjectList.get(0).getPermanent(game);
if (permanent != null) {
Ability abilityToRemove = null;
for (Ability ability : permanent.getAbilities()) {
if (ability instanceof EnchantAbility) {
abilityToRemove = ability;
}
}
permanent.removeAbility(abilityToRemove, source.getSourceId(), game);
permanent.addAbility(newAbility, source.getSourceId(), game);
return true;
}
return false;
}
}
class DanceOfTheDeadDoIfCostPaidEffect extends DoIfCostPaid { class DanceOfTheDeadDoIfCostPaidEffect extends DoIfCostPaid {
public DanceOfTheDeadDoIfCostPaidEffect() { public DanceOfTheDeadDoIfCostPaidEffect() {

View file

@ -1,40 +1,27 @@
package mage.cards.n; package mage.cards.n;
import mage.MageObjectReference;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.AnimateDeadTriggeredAbility;
import mage.abilities.common.LeavesBattlefieldTriggeredAbility;
import mage.abilities.common.SacrificeIfCastAtInstantTimeTriggeredAbility; import mage.abilities.common.SacrificeIfCastAtInstantTimeTriggeredAbility;
import mage.abilities.common.SimpleStaticAbility; import mage.abilities.common.SimpleStaticAbility;
import mage.abilities.condition.common.SourceOnBattlefieldCondition;
import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.continuous.CastAsThoughItHadFlashSourceEffect; import mage.abilities.effects.common.continuous.CastAsThoughItHadFlashSourceEffect;
import mage.abilities.effects.common.continuous.SourceEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.cards.Card;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.*; import mage.constants.CardType;
import mage.constants.Duration;
import mage.constants.Zone;
import mage.filter.common.FilterCreatureCard; import mage.filter.common.FilterCreatureCard;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.permanent.PermanentIdPredicate;
import mage.game.Game;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.Target;
import mage.target.common.TargetCardInGraveyard; import mage.target.common.TargetCardInGraveyard;
import mage.target.common.TargetCreaturePermanent;
import java.util.UUID; import java.util.UUID;
/** /**
* @author LevelX2 * @author LevelX2, awjackson
*/ */
public final class Necromancy extends CardImpl { public final class Necromancy extends CardImpl {
private static final FilterCreatureCard filter = new FilterCreatureCard("creature card in a graveyard");
public Necromancy(UUID ownerId, CardSetInfo setInfo) { public Necromancy(UUID ownerId, CardSetInfo setInfo) {
super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}"); super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}");
@ -42,16 +29,9 @@ public final class Necromancy extends CardImpl {
this.addAbility(new SimpleStaticAbility(Zone.ALL, new CastAsThoughItHadFlashSourceEffect(Duration.EndOfGame))); this.addAbility(new SimpleStaticAbility(Zone.ALL, new CastAsThoughItHadFlashSourceEffect(Duration.EndOfGame)));
this.addAbility(new SacrificeIfCastAtInstantTimeTriggeredAbility()); this.addAbility(new SacrificeIfCastAtInstantTimeTriggeredAbility());
// When Necromancy enters the battlefield, if it's on the battlefield, it becomes an Aura with "enchant creature put onto the battlefield with Necromancy." Ability ability = new AnimateDeadTriggeredAbility(true);
// Put target creature card from a graveyard onto the battlefield under your control and attach Necromancy to it. ability.addTarget(new TargetCardInGraveyard(filter));
// When Necromancy leaves the battlefield, that creature's controller sacrifices it.
Ability ability = new ConditionalInterveningIfTriggeredAbility(
new EntersBattlefieldTriggeredAbility(new NecromancyReAttachEffect(), false),
SourceOnBattlefieldCondition.instance,
"When {this} enters the battlefield, if it's on the battlefield, it becomes an Aura with \"enchant creature put onto the battlefield with {this}.\" Put target creature card from a graveyard onto the battlefield under your control and attach {this} to it.");
ability.addTarget(new TargetCardInGraveyard(new FilterCreatureCard("creature card from a graveyard")));
this.addAbility(ability); this.addAbility(ability);
this.addAbility(new LeavesBattlefieldTriggeredAbility(new NecromancyLeavesBattlefieldTriggeredEffect(), false));
} }
private Necromancy(final Necromancy card) { private Necromancy(final Necromancy card) {
@ -63,139 +43,3 @@ public final class Necromancy extends CardImpl {
return new Necromancy(this); return new Necromancy(this);
} }
} }
class NecromancyReAttachEffect extends OneShotEffect {
public NecromancyReAttachEffect() {
super(Outcome.Benefit);
this.staticText = "it becomes an Aura with \"enchant creature put onto the battlefield with {this}\"";
}
public NecromancyReAttachEffect(final NecromancyReAttachEffect effect) {
super(effect);
}
@Override
public NecromancyReAttachEffect copy() {
return new NecromancyReAttachEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Permanent enchantment = game.getPermanent(source.getSourceId());
Card cardInGraveyard = game.getCard(getTargetPointer().getFirst(game, source));
if (controller != null && enchantment != null && cardInGraveyard != null) {
controller.moveCards(cardInGraveyard, Zone.BATTLEFIELD, source, game);
Permanent enchantedCreature = game.getPermanent(cardInGraveyard.getId());
if (enchantedCreature != null) {
enchantedCreature.addAttachment(enchantment.getId(), source, game);
FilterCreaturePermanent filter = new FilterCreaturePermanent("enchant creature put onto the battlefield with " + enchantment.getIdName());
filter.add(new PermanentIdPredicate(cardInGraveyard.getId()));
Target target = new TargetCreaturePermanent(filter);
target.addTarget(enchantedCreature.getId(), source, game);
game.addEffect(new NecromancyChangeAbilityEffect(target), source);
}
return true;
}
return false;
}
}
class NecromancyLeavesBattlefieldTriggeredEffect extends OneShotEffect {
public NecromancyLeavesBattlefieldTriggeredEffect() {
super(Outcome.Benefit);
this.staticText = "enchanted creature's controller sacrifices it";
}
public NecromancyLeavesBattlefieldTriggeredEffect(final NecromancyLeavesBattlefieldTriggeredEffect effect) {
super(effect);
}
@Override
public NecromancyLeavesBattlefieldTriggeredEffect copy() {
return new NecromancyLeavesBattlefieldTriggeredEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player controller = game.getPlayer(source.getControllerId());
Permanent sourcePermanent = game.getPermanentOrLKIBattlefield(source.getSourceId());
if (controller != null && sourcePermanent != null) {
if (sourcePermanent.getAttachedTo() != null) {
Permanent attachedTo = game.getPermanent(sourcePermanent.getAttachedTo());
if (attachedTo != null && attachedTo.getZoneChangeCounter(game) == sourcePermanent.getAttachedToZoneChangeCounter()) {
attachedTo.sacrifice(source, game);
}
}
return true;
}
return false;
}
}
class NecromancyChangeAbilityEffect extends ContinuousEffectImpl implements SourceEffect {
private static final Ability newAbility = new EnchantAbility("creature put onto the battlefield with Necromancy");
static {
newAbility.setRuleAtTheTop(true);
}
Target target;
public NecromancyChangeAbilityEffect(Target target) {
super(Duration.Custom, Outcome.AddAbility);
staticText = "it becomes an Aura with \"enchant creature put onto the battlefield with {this}\"";
this.target = target;
dependencyTypes.add(DependencyType.AuraAddingRemoving);
}
public NecromancyChangeAbilityEffect(final NecromancyChangeAbilityEffect effect) {
super(effect);
this.target = effect.target;
}
@Override
public NecromancyChangeAbilityEffect copy() {
return new NecromancyChangeAbilityEffect(this);
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
affectedObjectList.add(new MageObjectReference(source.getSourceId(), game));
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
Permanent permanent = affectedObjectList.get(0).getPermanent(game);
if (permanent != null) {
switch (layer) {
case TypeChangingEffects_4:
permanent.addSubType(game, SubType.AURA);
break;
case AbilityAddingRemovingEffects_6:
permanent.addAbility(newAbility, source.getSourceId(), game);
permanent.getSpellAbility().getTargets().clear();
permanent.getSpellAbility().getTargets().add(target);
}
return true;
}
this.discard();
return false;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean hasLayer(Layer layer) {
return layer == Layer.AbilityAddingRemovingEffects_6 || layer == Layer.TypeChangingEffects_4;
}
}

View file

@ -1,4 +1,3 @@
package org.mage.test.cards.abilities.other; package org.mage.test.cards.abilities.other;
import mage.constants.PhaseStep; import mage.constants.PhaseStep;
@ -125,4 +124,55 @@ public class NecromancyTest extends CardTestPlayerBase {
assertGraveyardCount(playerA, "Silvercoat Lion", 1); assertGraveyardCount(playerA, "Silvercoat Lion", 1);
assertGraveyardCount(playerB, "Silvercoat Lion", 1); assertGraveyardCount(playerB, "Silvercoat Lion", 1);
} }
@Test
public void testNecromancyWithYarok() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3);
addCard(Zone.BATTLEFIELD, playerA, "Yarok, the Desecrated");
addCard(Zone.HAND, playerA, "Necromancy");
addCard(Zone.GRAVEYARD, playerA, "Craw Wurm");
addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Necromancy");
addTarget(playerA, "Craw Wurm");
addTarget(playerA, "Silvercoat Lion");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPermanentCount(playerA, "Necromancy", 1);
assertPermanentCount(playerA, "Craw Wurm", 1);
assertPermanentCount(playerA, "Silvercoat Lion", 1);
assertGraveyardCount(playerA, "Craw Wurm", 0);
assertGraveyardCount(playerA, "Silvercoat Lion", 0);
}
@Test
public void testNecromancyLeavesWithYarok() {
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 1);
addCard(Zone.BATTLEFIELD, playerA, "Plains", 4);
addCard(Zone.BATTLEFIELD, playerA, "Yarok, the Desecrated");
addCard(Zone.HAND, playerA, "Necromancy");
addCard(Zone.HAND, playerA, "Disenchant");
addCard(Zone.GRAVEYARD, playerA, "Craw Wurm");
addCard(Zone.GRAVEYARD, playerA, "Silvercoat Lion");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Necromancy");
addTarget(playerA, "Craw Wurm");
addTarget(playerA, "Silvercoat Lion");
castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Disenchant", "Necromancy");
setStopAt(1, PhaseStep.END_TURN);
execute();
assertPermanentCount(playerA, "Yarok, the Desecrated", 1);
assertPermanentCount(playerA, "Necromancy", 0);
assertPermanentCount(playerA, "Craw Wurm", 0);
assertPermanentCount(playerA, "Silvercoat Lion", 0);
assertGraveyardCount(playerA, "Disenchant", 1);
assertGraveyardCount(playerA, "Necromancy", 1);
assertGraveyardCount(playerA, "Craw Wurm", 1);
assertGraveyardCount(playerA, "Silvercoat Lion", 1);
}
} }

View file

@ -202,4 +202,19 @@ public class AnimateDeadTest extends CardTestPlayerBase {
assertPermanentCount(playerA, "Animate Dead", 1); assertPermanentCount(playerA, "Animate Dead", 1);
assertPermanentCount(playerA, "Dragonlord Atarka", 1); assertPermanentCount(playerA, "Dragonlord Atarka", 1);
} }
@Test
public void testAnimateMDFC() {
addCard(Zone.GRAVEYARD, playerA, "Blackbloom Rogue");
addCard(Zone.BATTLEFIELD, playerA, "Swamp", 2);
addCard(Zone.HAND, playerA, "Animate Dead");
castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Animate Dead", "Blackbloom Rogue");
setStopAt(1, PhaseStep.BEGIN_COMBAT);
execute();
assertPowerToughness(playerA, "Blackbloom Rogue", 1, 3);
assertPermanentCount(playerA, "Animate Dead", 1);
}
} }

View file

@ -0,0 +1,293 @@
package mage.abilities.common;
import mage.MageObjectReference;
import mage.abilities.Ability;
import mage.abilities.DelayedTriggeredAbility;
import mage.abilities.effects.ContinuousEffectImpl;
import mage.abilities.effects.OneShotEffect;
import mage.abilities.effects.common.SacrificeTargetEffect;
import mage.abilities.effects.common.continuous.SourceEffect;
import mage.abilities.keyword.EnchantAbility;
import mage.cards.Card;
import mage.constants.*;
import mage.filter.common.FilterCreaturePermanent;
import mage.filter.predicate.Predicate;
import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.ZoneChangeEvent;
import mage.game.permanent.Permanent;
import mage.players.Player;
import mage.target.common.TargetCreaturePermanent;
import mage.target.targetpointer.FixedTarget;
import mage.util.CardUtil;
import mage.watchers.Watcher;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* @author LevelX2, awjackson
*/
public class AnimateDeadTriggeredAbility extends EntersBattlefieldTriggeredAbility {
public AnimateDeadTriggeredAbility() {
this(false);
}
public AnimateDeadTriggeredAbility(boolean becomesAura) {
this(becomesAura, false);
}
public AnimateDeadTriggeredAbility(boolean becomesAura, boolean tapped) {
super(new AnimateDeadReplaceAbilityEffect(becomesAura));
addEffect(new AnimateDeadPutOntoBattlefieldEffect(becomesAura, tapped));
addWatcher(new AnimateDeadWatcher());
setTriggerPhrase("When {this} enters the battlefield, if it's on the battlefield, ");
}
private AnimateDeadTriggeredAbility(final AnimateDeadTriggeredAbility ability) {
super(ability);
}
@Override
public AnimateDeadTriggeredAbility copy() {
return new AnimateDeadTriggeredAbility(this);
}
@Override
public boolean checkInterveningIfClause(Game game) {
return getSourcePermanentIfItStillExists(game) != null;
}
}
class AnimateDeadReplaceAbilityEffect extends ContinuousEffectImpl implements SourceEffect {
private final boolean becomesAura;
private Ability newAbility;
private TargetCreaturePermanent newTarget;
public AnimateDeadReplaceAbilityEffect(boolean becomesAura) {
super(Duration.Custom, Outcome.AddAbility);
this.becomesAura = becomesAura;
staticText = (becomesAura ? "it becomes an Aura with" :
"it loses \"enchant creature card in a graveyard\" and gains"
) + " \"enchant creature put onto the battlefield with {this}.\"";
if (becomesAura) {
dependencyTypes.add(DependencyType.AuraAddingRemoving);
}
}
private AnimateDeadReplaceAbilityEffect(final AnimateDeadReplaceAbilityEffect effect) {
super(effect);
this.becomesAura = effect.becomesAura;
this.newAbility = effect.newAbility;
this.newTarget = effect.newTarget;
}
@Override
public AnimateDeadReplaceAbilityEffect copy() {
return new AnimateDeadReplaceAbilityEffect(this);
}
@Override
public void init(Ability source, Game game) {
super.init(source, game);
affectedObjectList.add(new MageObjectReference(source.getSourceId(), game));
FilterCreaturePermanent filter = new FilterCreaturePermanent("creature put onto the battlefield with {this}");
filter.add(new AnimateDeadPredicate(source.getSourceId()));
newTarget = new TargetCreaturePermanent(filter);
newAbility = new EnchantAbility(newTarget.getTargetName());
}
@Override
public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) {
Permanent permanent = affectedObjectList.get(0).getPermanent(game);
if (permanent == null) {
discard();
return true;
}
switch (layer) {
case TypeChangingEffects_4:
if (becomesAura) {
permanent.addSubType(game, SubType.AURA);
}
break;
case AbilityAddingRemovingEffects_6:
if (!becomesAura) {
List<Ability> toRemove = new ArrayList<>();
for (Ability ability : permanent.getAbilities(game)) {
if (ability instanceof EnchantAbility &&
ability.getRule().equals("Enchant creature card in a graveyard")) {
toRemove.add(ability);
}
}
permanent.removeAbilities(toRemove, source.getSourceId(), game);
}
permanent.addAbility(newAbility, source.getSourceId(), game);
permanent.getSpellAbility().getTargets().clear();
permanent.getSpellAbility().getTargets().add(newTarget);
}
return true;
}
@Override
public boolean apply(Game game, Ability source) {
return false;
}
@Override
public boolean hasLayer(Layer layer) {
return Layer.AbilityAddingRemovingEffects_6 == layer || becomesAura && Layer.TypeChangingEffects_4 == layer;
}
}
class AnimateDeadPutOntoBattlefieldEffect extends OneShotEffect {
private final boolean becomesAura;
private final boolean tapped;
public AnimateDeadPutOntoBattlefieldEffect(boolean becomesAura, boolean tapped) {
super(Outcome.PutCreatureInPlay);
this.becomesAura = becomesAura;
this.tapped = tapped;
StringBuilder sb = new StringBuilder(becomesAura || tapped ? "put " : "return ");
sb.append(becomesAura ? "target creature card from a graveyard" : "enchanted creature card");
sb.append(becomesAura || tapped ? " onto" : " to");
sb.append(" the battlefield ");
if (tapped) {
sb.append("tapped ");
}
sb.append("under your control and attach {this} to it. When {this} leaves the battlefield, that creature's controller sacrifices it");
staticText = sb.toString();
}
private AnimateDeadPutOntoBattlefieldEffect(final AnimateDeadPutOntoBattlefieldEffect effect) {
super(effect);
this.becomesAura = effect.becomesAura;
this.tapped = effect.tapped;
}
@Override
public AnimateDeadPutOntoBattlefieldEffect copy() {
return new AnimateDeadPutOntoBattlefieldEffect(this);
}
@Override
public boolean apply(Game game, Ability source) {
Player player = game.getPlayer(source.getControllerId());
Permanent enchantment = source.getSourcePermanentIfItStillExists(game);
if (player == null || enchantment == null) {
return false;
}
Card card = game.getCard(becomesAura ? getTargetPointer().getFirst(game, source) : enchantment.getAttachedTo());
// If this ability was copied or made to trigger an additional time, the card might no longer be in the graveyard
// See https://github.com/magefree/mage/issues/8253
if (card == null || game.getState().getZone(card.getId()) != Zone.GRAVEYARD) {
return true;
}
// Put card onto the battlefield under your control...
player.moveCards(card, Zone.BATTLEFIELD, source, game, tapped, false, false, null);
game.getState().processAction(game);
Permanent creature = game.getPermanent(CardUtil.getDefaultCardSideForBattlefield(game, card).getId());
if (creature == null) {
return true;
}
// ...and attach {this} to it
creature.addAttachment(enchantment.getId(), source, game);
// When {this} leaves the battlefield, that creature's controller sacrifices it
game.addDelayedTriggeredAbility(new AnimateDeadDelayedTriggeredAbility(new FixedTarget(creature, game)), source);
return true;
}
}
class AnimateDeadDelayedTriggeredAbility extends DelayedTriggeredAbility {
public AnimateDeadDelayedTriggeredAbility(FixedTarget fixedTarget) {
super(new SacrificeTargetEffect("that creature's controller sacrifices it"));
setTriggerPhrase("When {this} leaves the battlefield, ");
getEffects().setTargetPointer(fixedTarget);
}
private AnimateDeadDelayedTriggeredAbility(final AnimateDeadDelayedTriggeredAbility ability) {
super(ability);
}
@Override
public AnimateDeadDelayedTriggeredAbility copy() {
return new AnimateDeadDelayedTriggeredAbility(this);
}
@Override
public boolean checkEventType(GameEvent event, Game game) {
return event.getType() == GameEvent.EventType.ZONE_CHANGE;
}
@Override
public boolean checkTrigger(GameEvent event, Game game) {
return getSourceId().equals(event.getTargetId()) && ((ZoneChangeEvent) event).getFromZone() == Zone.BATTLEFIELD;
}
}
class AnimateDeadPredicate implements Predicate<Permanent> {
private final UUID sourceId;
public AnimateDeadPredicate(UUID sourceId) {
this.sourceId = sourceId;
}
@Override
public boolean apply(Permanent input, Game game) {
AnimateDeadWatcher watcher = game.getState().getWatcher(AnimateDeadWatcher.class, sourceId);
return watcher != null && watcher.check(input);
}
@Override
public String toString() {
return "AnimateDeadPredicate(" + sourceId + ')';
}
}
class AnimateDeadWatcher extends Watcher {
private final Set<UUID> putBySource = new HashSet<>();
public AnimateDeadWatcher() {
super(WatcherScope.CARD);
}
@Override
public void watch(GameEvent event, Game game) {
switch (event.getType()) {
case ZONE_CHANGE:
ZoneChangeEvent zEvent = (ZoneChangeEvent) event;
if (sourceId.equals(zEvent.getTargetId())) {
// clear all data when source enchantment changes zones
putBySource.clear();
return;
}
if (zEvent.getToZone() != Zone.BATTLEFIELD) {
return;
}
if (sourceId.equals(zEvent.getSourceId())) {
putBySource.add(event.getTargetId());
} else {
putBySource.remove(event.getTargetId());
}
return;
case BEGINNING_PHASE_PRE:
if (game.getTurnNum() == 1) {
putBySource.clear();
}
}
}
public boolean check(Permanent permanent) {
return putBySource.contains(permanent.getId());
}
}