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