forked from External/mage
292 lines
10 KiB
Java
292 lines
10 KiB
Java
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<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());
|
|
}
|
|
}
|