diff --git a/res/cardsfolder/a/animate_dead.txt b/res/cardsfolder/a/animate_dead.txt index 47fe24832ed..0a16e724b79 100644 --- a/res/cardsfolder/a/animate_dead.txt +++ b/res/cardsfolder/a/animate_dead.txt @@ -1,7 +1,16 @@ Name:Animate Dead ManaCost:1 B Types:Enchantment Aura -Text:Enchant creature card in a graveyard\r\nWhen Animate 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 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. +Text:no text +K:Enchant creature card in a graveyard +A:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature | TgtZone$ Graveyard | AILogic$ Reanimate +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME 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 CARDNAME." Return enchanted creature card to the battlefield under your control and attach CARDNAME to it. +SVar:TrigReanimate:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ Enchanted | RememberChanged$ True | SubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | Defined$ Self | OverwriteSpells$ True | Abilities$ NewAttach | Keywords$ Enchant creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant creature card in a graveyard | Permanent$ True | SubAbility$ DBAttach +SVar:DBAttach:DB$ Attach | Defined$ Remembered +SVar:NewAttach:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature.IsRemembered | AILogic$ Pump +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigSacrifice | TriggerDescription$ When Animate Dead leaves the battlefield, that creature's controller sacrifices it. +SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ Remembered S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ -1 | Description$ Enchanted creature gets -1/-0. SVar:Rarity:Uncommon SVar:Picture:http://resources.wizards.com/magic/cards/5e/en-us/card3823.jpg diff --git a/res/cardsfolder/d/dance_of_the_dead.txt b/res/cardsfolder/d/dance_of_the_dead.txt index 0311cceff01..6cfb4d0011c 100644 --- a/res/cardsfolder/d/dance_of_the_dead.txt +++ b/res/cardsfolder/d/dance_of_the_dead.txt @@ -1,7 +1,16 @@ Name:Dance of the Dead ManaCost:1 B Types:Enchantment Aura -Text:Enchant creature card in a graveyard\r\n\r\nWhen CARDNAME 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 CARDNAME." Return enchanted creature card to the battlefield tapped under your control and attach CARDNAME to it. When CARDNAME leaves the battlefield, that creature's controller sacrifices it. +Text:no text +K:Enchant creature card in a graveyard +A:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature | TgtZone$ Graveyard | AILogic$ Reanimate +T:Mode$ ChangesZone | Origin$ Any | Destination$ Battlefield | ValidCard$ Card.Self | Execute$ TrigReanimate | TriggerDescription$ When CARDNAME 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 CARDNAME." Return enchanted creature card to the battlefield under your control and attach CARDNAME to it. +SVar:TrigReanimate:DB$ ChangeZone | Origin$ Graveyard | Destination$ Battlefield | Defined$ Enchanted | RememberChanged$ True | SubAbility$ DBAnimate +SVar:DBAnimate:DB$ Animate | Defined$ Self | OverwriteSpells$ True | Abilities$ NewAttach | Keywords$ Enchant creature put onto the battlefield with CARDNAME | RemoveKeywords$ Enchant creature card in a graveyard | Permanent$ True | SubAbility$ DBAttach +SVar:DBAttach:DB$ Attach | Defined$ Remembered +SVar:NewAttach:SP$ Attach | Cost$ 1 B | ValidTgts$ Creature.IsRemembered | AILogic$ Pump +T:Mode$ ChangesZone | Origin$ Battlefield | Destination$ Any | ValidCard$ Card.Self | Execute$ TrigSacrifice | TriggerDescription$ When Animate Dead leaves the battlefield, that creature's controller sacrifices it. +SVar:TrigSacrifice:DB$ Destroy | Sacrifice$ True | Defined$ Remembered S:Mode$ Continuous | Affected$ Creature.EnchantedBy | AddPower$ 1 | AddToughness$ 1 | AddHiddenKeyword$ CARDNAME doesn't untap during your untap step. | Description$ Enchanted creature gets +1/+1 and doesn't untap during its controller's untap step. T:Mode$ Phase | Phase$ Upkeep | ValidPlayer$ EnchantedController | TriggerZones$ Battlefield | OptionalDecider$ EnchantedController | Execute$ TrigUntap | TriggerDescription$ At the beginning of the upkeep of enchanted creature's controller, that player may pay 1 B. If he or she does, untap that creature. SVar:TrigUntap:AB$Untap | Cost$ 1 B | Defined$ Enchanted diff --git a/src/main/java/forge/card/abilityfactory/ai/AttachAi.java b/src/main/java/forge/card/abilityfactory/ai/AttachAi.java index 4b07b1895e7..ec02b13a95d 100644 --- a/src/main/java/forge/card/abilityfactory/ai/AttachAi.java +++ b/src/main/java/forge/card/abilityfactory/ai/AttachAi.java @@ -309,6 +309,34 @@ public class AttachAi extends SpellAiLogic { return c; } + + /** + * Attach ai control preference. + * + * @param sa + * the sa + * @param list + * the list + * @param mandatory + * the mandatory + * @param attachSource + * the attach source + * @return the card + */ + private static Card attachAIReanimatePreference(final SpellAbility sa, final List list, final boolean mandatory, + final Card attachSource) { + // AI For choosing a Card to Animate. + // TODO Add some more restrictions for Reanimation Auras + final Card c = CardFactoryUtil.getBestCreatureAI(list); + + // If Mandatory (brought directly into play without casting) gotta + // choose something + if (c == null && mandatory) { + return chooseLessPreferred(mandatory, list); + } + + return c; + } // Should generalize this code a bit since they all have similar structures /** @@ -786,6 +814,8 @@ public class AttachAi extends SpellAiLogic { c = attachAIKeepTappedPreference(sa, prefList, mandatory, attachSource); } else if ("Animate".equals(logic)) { c = attachAIAnimatePreference(sa, prefList, mandatory, attachSource); + } else if ("Reanimate".equals(logic)) { + c = attachAIReanimatePreference(sa, prefList, mandatory, attachSource); } return c; diff --git a/src/main/java/forge/card/abilityfactory/effects/AnimateEffect.java b/src/main/java/forge/card/abilityfactory/effects/AnimateEffect.java index ef75e0b82bf..6d30896363f 100644 --- a/src/main/java/forge/card/abilityfactory/effects/AnimateEffect.java +++ b/src/main/java/forge/card/abilityfactory/effects/AnimateEffect.java @@ -147,6 +147,22 @@ public class AnimateEffect extends AnimateEffectBase { final long colorTimestamp = doAnimate(c, sa, power, toughness, types, removeTypes, finalDesc, keywords, removeKeywords, hiddenKeywords, timestamp); + // remove abilities + final ArrayList removedAbilities = new ArrayList(); + boolean clearAbilities = sa.hasParam("OverwriteAbilities"); + boolean clearSpells = sa.hasParam("OverwriteSpells"); + boolean removeAll = sa.hasParam("RemoveAllAbilities"); + + if (clearAbilities || clearSpells || removeAll) { + for (final SpellAbility ab : c.getSpellAbilities()) { + if (removeAll || (ab.isAbility() && clearAbilities) || + (ab.isSpell() && clearSpells)) { + c.removeSpellAbility(ab); + removedAbilities.add(ab); + } + } + } + // give abilities final ArrayList addedAbilities = new ArrayList(); if (abilities.size() > 0) { @@ -159,17 +175,6 @@ public class AnimateEffect extends AnimateEffectBase { } } - // remove abilities - final ArrayList removedAbilities = new ArrayList(); - if (sa.hasParam("OverwriteAbilities") || sa.hasParam("RemoveAllAbilities")) { - for (final SpellAbility ab : c.getSpellAbilities()) { - if (ab.isAbility()) { - c.removeSpellAbility(ab); - removedAbilities.add(ab); - } - } - } - // Grant triggers final ArrayList addedTriggers = new ArrayList(); if (triggers.size() > 0) { @@ -182,7 +187,7 @@ public class AnimateEffect extends AnimateEffectBase { // suppress triggers from the animated card final ArrayList removedTriggers = new ArrayList(); - if (sa.hasParam("OverwriteTriggers") || sa.hasParam("RemoveAllAbilities")) { + if (sa.hasParam("OverwriteTriggers") || removeAll) { final List triggersToRemove = c.getTriggers(); for (final Trigger trigger : triggersToRemove) { trigger.setSuppressed(true); @@ -209,7 +214,7 @@ public class AnimateEffect extends AnimateEffectBase { // suppress static abilities from the animated card final ArrayList removedStatics = new ArrayList(); - if (sa.hasParam("OverwriteStatics") || sa.hasParam("RemoveAllAbilities")) { + if (sa.hasParam("OverwriteStatics") || removeAll) { final ArrayList staticsToRemove = c.getStaticAbilities(); for (final StaticAbility stAb : staticsToRemove) { stAb.setTemporarilySuppressed(true); @@ -219,7 +224,7 @@ public class AnimateEffect extends AnimateEffectBase { // suppress static abilities from the animated card final ArrayList removedReplacements = new ArrayList(); - if (sa.hasParam("OverwriteReplacements") || sa.hasParam("RemoveAllAbilities")) { + if (sa.hasParam("OverwriteReplacements") || removeAll) { final ArrayList replacementsToRemove = c.getReplacementEffects(); for (final ReplacementEffect re : replacementsToRemove) { re.setTemporarilySuppressed(true); diff --git a/src/main/java/forge/card/abilityfactory/effects/AttachEffect.java b/src/main/java/forge/card/abilityfactory/effects/AttachEffect.java index 6dacd3e5acd..916c1198b9c 100644 --- a/src/main/java/forge/card/abilityfactory/effects/AttachEffect.java +++ b/src/main/java/forge/card/abilityfactory/effects/AttachEffect.java @@ -25,7 +25,7 @@ public class AttachEffect extends SpellEffect { */ @Override public void resolve(SpellAbility sa) { - if (sa.getSourceCard().isAura()) { + if (sa.getSourceCard().isAura() && sa.isSpell()) { // The Spell_Permanent (Auras) version of this AF needs to // move the card into play before Attaching diff --git a/src/main/java/forge/card/cardfactory/CardFactoryAuras.java b/src/main/java/forge/card/cardfactory/CardFactoryAuras.java index a4c43b9198c..cee051577c5 100644 --- a/src/main/java/forge/card/cardfactory/CardFactoryAuras.java +++ b/src/main/java/forge/card/cardfactory/CardFactoryAuras.java @@ -436,164 +436,6 @@ class CardFactoryAuras { card.addSpellAbility(spell); } // *************** END ************ END ************************** - // *************** START *********** START ************************** - else if (cardName.equals("Animate Dead") || cardName.equals("Dance of the Dead")) { - final Card[] targetC = new Card[1]; - // need to override what happens when this is cast. - final SpellPermanent animate = new SpellPermanent(card) { - private static final long serialVersionUID = 7126615291288065344L; - - public List getCreturesInGrave() { - // This includes creatures Animate Dead can't enchant once - // in play. - // The human may try to Animate them, the AI will not. - return CardLists.filter(Singletons.getModel().getGame().getCardsIn(ZoneType.Graveyard), Presets.CREATURES); - } - - @Override - public boolean canPlay() { - return super.canPlay() && (this.getCreturesInGrave().size() != 0); - } - - @Override - public boolean canPlayAI() { - List cList = this.getCreturesInGrave(); - // AI will only target something that will stick in play. - cList = CardLists.getTargetableCards(cList, this); - if (cList.size() == 0) { - return false; - } - - final Card c = CardFactoryUtil.getBestCreatureAI(cList); - - this.setTargetCard(c); - final boolean playable = (2 < c.getNetAttack()) && (2 < c.getNetDefense()) && super.canPlayAI(); - return playable; - } // canPlayAI - - @Override - public void resolve() { - targetC[0] = this.getTargetCard(); - super.resolve(); - } - - }; // addSpellAbility - - // Target AbCost and Restriction are set here to get this working as - // expected - final Target tgt = new Target(card, "Select a creature in a graveyard", "Creature".split(",")); - tgt.setZone(ZoneType.Graveyard); - animate.setTarget(tgt); - - final Cost cost = new Cost(card, "1 B", false); - animate.setPayCosts(cost); - - animate.getRestrictions().setZone(ZoneType.Hand); - - final Ability attach = new Ability(card, "0") { - @Override - public void resolve() { - final PlayerZone play = card.getController().getZone(ZoneType.Battlefield); - - // Animate Dead got destroyed before its ability resolved - if (!play.contains(card)) { - return; - } - - final Card animated = targetC[0]; - final Zone grave = Singletons.getModel().getGame().getZoneOf(animated); - - if (!grave.is(ZoneType.Graveyard)) { - // Animated Creature got removed before ability resolved - Singletons.getModel().getGame().getAction().sacrifice(card, null); - return; - } - - // Bring creature onto the battlefield under your control - // (should trigger etb Abilities) - animated.addController(card.getController()); - Singletons.getModel().getGame().getAction().moveToPlay(animated, card.getController()); - if (cardName.equals("Dance of the Dead")) { - animated.tap(); - } - card.enchantEntity(animated); // Attach before Targeting so - // detach Command will trigger - - if (CardFactoryUtil.hasProtectionFrom(card, animated)) { - // Animated a creature with protection - Singletons.getModel().getGame().getAction().sacrifice(card, null); - return; - } - - // Everything worked out perfectly. - } - }; // Ability - - final Command attachCmd = new Command() { - private static final long serialVersionUID = 3595188622377350327L; - - @Override - public void execute() { - if (targetC[0] != null) { - //too slow - must be done immediately - //otherwise before attach is resolved state effect kills aura as it has no target... -// AllZone.getStack().addSimultaneousStackEntry(attach); - - //this seems to work, but I'm not 100% sure of possible side effects (hopefully none) - attach.resolve(); - } else { - // note: this should be a state-based action, but it doesn't work currently. - // I don't know if that because it's hard-coded or what, but this fixes - // these cards being put on the battlefield not attached to anything. - Singletons.getModel().getGame().getAction().moveToGraveyard(card); - } - } - }; - - final Ability detach = new Ability(card, "0") { - - @Override - public void resolve() { - final Card c = targetC[0]; - - final PlayerZone play = card.getController().getZone(ZoneType.Battlefield); - - if (play.contains(c)) { - Singletons.getModel().getGame().getAction().sacrifice(c, null); - } - } - }; // Detach - - final Command detachCmd = new Command() { - private static final long serialVersionUID = 2425333033834543422L; - - @Override - public void execute() { - final Card c = targetC[0]; - - final PlayerZone play = card.getController().getZone(ZoneType.Battlefield); - - if (play.contains(c)) { - Singletons.getModel().getGame().getStack().addSimultaneousStackEntry(detach); - } - - } - }; - - card.addSpellAbility(animate); - - final StringBuilder sbA = new StringBuilder(); - sbA.append("Attaching ").append(cardName).append(" to creature in graveyard."); - attach.setStackDescription(sbA.toString()); - card.addComesIntoPlayCommand(attachCmd); - final StringBuilder sbD = new StringBuilder(); - sbD.append(cardName).append(" left play. Sacrificing creature if still around."); - detach.setStackDescription(sbD.toString()); - card.addLeavesPlayCommand(detachCmd); - card.addUnEnchantCommand(detachCmd); - } // *************** END ************ END ************************** - - // *************** START *********** START ************************** else if (CardFactoryUtil.hasKeyword(card, "enchant") != -1) { final int n = CardFactoryUtil.hasKeyword(card, "enchant"); if (n != -1) {