diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BestowTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BestowTest.java index 8ec0d1b5119..dbc750bb4ca 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BestowTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/BestowTest.java @@ -6,12 +6,17 @@ import mage.constants.CardType; import mage.constants.PhaseStep; import mage.constants.SubType; import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.predicate.mageobject.NamePredicate; import mage.game.permanent.Permanent; import org.junit.Assert; import org.junit.Test; import org.mage.test.player.TestPlayer; import org.mage.test.serverside.base.CardTestPlayerBase; +import java.util.List; + /** * @author LevelX2 */ @@ -82,6 +87,7 @@ public class BestowTest extends CardTestPlayerBase { assertSubtype("Hopeful Eidolon", SubType.AURA); assertType("Hopeful Eidolon", CardType.ENCHANTMENT, true); assertType("Hopeful Eidolon", CardType.CREATURE, false); + assertNotSubtype("Hopeful Eidolon", SubType.SPIRIT); } /** @@ -122,59 +128,63 @@ public class BestowTest extends CardTestPlayerBase { @Test public void bestowEnchantmentBecomesCreature() { addCard(Zone.BATTLEFIELD, playerA, "Plains", 4); - addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerA, "Safehold Elite"); // 2/2 creature + Persist to return as a 1/1 addCard(Zone.HAND, playerA, "Hopeful Eidolon"); addCard(Zone.BATTLEFIELD, playerB, "Mountain", 1); addCard(Zone.HAND, playerB, "Lightning Bolt"); - castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hopeful Eidolon using bestow", "Silvercoat Lion"); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Silvercoat Lion"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hopeful Eidolon using bestow", "Safehold Elite"); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Bolt", "Safehold Elite"); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - // because Boon Satyr is no creature on the battlefield, evolve may not trigger assertLife(playerA, 20); assertLife(playerB, 20); - assertPermanentCount(playerA, "Silvercoat Lion", 0); + assertPermanentCount(playerA, "Safehold Elite", 1); + assertPowerToughness(playerA, "Safehold Elite", 1, 1); assertPermanentCount(playerA, "Hopeful Eidolon", 1); assertPowerToughness(playerA, "Hopeful Eidolon", 1, 1); - - Permanent hopefulEidolon = getPermanent("Hopeful Eidolon", playerA); - Assert.assertTrue("Hopeful Eidolon has to be a creature but is not", hopefulEidolon.isCreature(currentGame)); - Assert.assertTrue("Hopeful Eidolon has to be an enchantment but is not", hopefulEidolon.isEnchantment(currentGame)); - + assertType("Hopeful Eidolon", CardType.CREATURE, true); + assertSubtype("Hopeful Eidolon", SubType.SPIRIT); + assertType("Hopeful Eidolon", CardType.ENCHANTMENT, true); + assertNotSubtype("Hopeful Eidolon", SubType.AURA); } /** * Test that card cast with bestow will not be tapped, if creatures come - * into play tapped + * into play tapped. If it is cast with bestow but the target removed, it should be tapped. */ @Test public void bestowEnchantmentWillNotBeTapped() { - addCard(Zone.BATTLEFIELD, playerA, "Forest", 6); + addCustomEffect_TargetDestroy(playerA); + addCard(Zone.BATTLEFIELD, playerA, "Forest", 10); addCard(Zone.BATTLEFIELD, playerA, "Silent Artisan"); addCard(Zone.HAND, playerA, "Boon Satyr"); + addCard(Zone.HAND, playerA, "Leafcrown Dryad"); + // Enchantment {1}{W} // Creatures your opponents control enter the battlefield tapped. addCard(Zone.BATTLEFIELD, playerB, "Imposing Sovereign"); - castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Boon Satyr using bestow", "Silent Artisan"); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Boon Satyr using bestow", "Silent Artisan"); + checkPT("Boon Satyr attached", 1, PhaseStep.BEGIN_COMBAT, playerA, "Silent Artisan", 7, 7); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Leafcrown Dryad using bestow", "Silent Artisan"); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "target destroy", "Silent Artisan"); setStrictChooseMode(true); setStopAt(1, PhaseStep.END_TURN); execute(); - // because Boon Satyr is no creature on the battlefield, evolve may not trigger - assertPermanentCount(playerA, "Silent Artisan", 1); - assertPowerToughness(playerA, "Silent Artisan", 7, 7); - // because cast with bestow, Boon Satyr may not be tapped + // Boon Satyr should not be tapped since it resolved as an aura, Leafcrown Dryad resolved as a creature assertTapped("Boon Satyr", false); + assertTapped("Leafcrown Dryad", true); } @@ -271,7 +281,6 @@ public class BestowTest extends CardTestPlayerBase { assertHandCount(playerB, "Disdainful Stroke", 1); assertPermanentCount(playerA, "Hypnotic Siren", 1); - // because cast with bestow, Boon Satyr may not be tapped assertPermanentCount(playerA, "Silvercoat Lion", 1); assertPowerToughness(playerA, "Silvercoat Lion", 3, 3); @@ -551,4 +560,127 @@ public class BestowTest extends CardTestPlayerBase { assertType("Nylea's Emissary", CardType.CREATURE, false); assertType("Nylea's Emissary", CardType.ENCHANTMENT, SubType.AURA); } + + @Test + public void testCastBestowFlashRootwaterShaman() { + addCard(Zone.HAND, playerA, "Nylea's Emissary"); // +3/+3, only an aura if cast with bestow + addCard(Zone.BATTLEFIELD, playerA, "Forest", 6); + addCard(Zone.BATTLEFIELD, playerA, "Rootwater Shaman"); //aura spells with enchant creature have flash + addCard(Zone.BATTLEFIELD, playerA, "Memnite", 1); // 1/1 + + castSpell(1, PhaseStep.BEGIN_COMBAT, playerA, "Nylea's Emissary using bestow", "Memnite"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, "Nylea's Emissary", 1); + assertPowerToughness(playerA, "Memnite", 4, 4); + assertType("Nylea's Emissary", CardType.CREATURE, false); + assertType("Nylea's Emissary", CardType.ENCHANTMENT, SubType.AURA); + } + + /** + * Tests that copied bestow works correctly both on the stack and battlefield, including with the creature removed + */ + @Test + public void bestowCopiesTest() { + addCard(Zone.BATTLEFIELD, playerA, "Tundra", 18); + addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); + addCard(Zone.BATTLEFIELD, playerA, "Overloaded Mage-Ring", 2); + addCard(Zone.HAND, playerA, "Hopeful Eidolon", 2); + addCard(Zone.HAND, playerA, "Copy Enchantment"); + addCard(Zone.HAND, playerA, "Mythos of Illuna"); + + addCard(Zone.BATTLEFIELD, playerB, "Mountain", 4); + addCard(Zone.HAND, playerB, "Lightning Blast"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hopeful Eidolon using bestow", "Silvercoat Lion"); + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{1}, {T}, Sacrifice", "Hopeful Eidolon"); + setChoice(playerA, false); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Mythos of Illuna", "Hopeful Eidolon"); // Making a token copy of a resolved Bestow aura makes a token creature + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Copy Enchantment"); // Copying a resolved Bestow aura makes a creature + setChoice(playerA, true); + setChoice(playerA, "Hopeful Eidolon"); + checkPT("Lion with 2x Hopeful Eidolon attached", 1, PhaseStep.BEGIN_COMBAT, playerA, "Silvercoat Lion", 4, 4); + checkPermanentCount("Four Hopeful Eidolons resolved", 1, PhaseStep.BEGIN_COMBAT, playerA, "Hopeful Eidolon", 4); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Hopeful Eidolon using bestow", "Silvercoat Lion"); + activateAbility(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "{1}, {T}, Sacrifice", "Hopeful Eidolon"); + setChoice(playerA, false); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerB, "Lightning Blast", "Silvercoat Lion"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Silvercoat Lion", 0); + assertPermanentCount(playerA, "Hopeful Eidolon", 6); + assertTokenCount(playerA, "Hopeful Eidolon", 2); + + FilterPermanent filter = new FilterPermanent(); + filter.add(new NamePredicate("Hopeful Eidolon")); + List eidolons = currentGame.getBattlefield().getAllActivePermanents(filter, currentGame); + Assert.assertEquals("Six Eidolons found with filter", 6, eidolons.size()); + for (Permanent p : eidolons){ + Assert.assertTrue("Is Enchantment", p.getCardType(currentGame).contains(CardType.ENCHANTMENT)); + Assert.assertFalse("Is not Aura", p.getSubtype(currentGame).contains(SubType.AURA)); + Assert.assertTrue("Is Creature", p.getCardType(currentGame).contains(CardType.CREATURE)); + Assert.assertTrue("Is Spirit", p.getSubtype(currentGame).contains(SubType.SPIRIT)); + Assert.assertEquals("Is 1 power", 1, p.getPower().getValue()); + Assert.assertEquals("Is 1 toughness", 1, p.getToughness().getValue()); + } + } + + @Test + public void testBestowSubtype() { + addCard(Zone.HAND, playerA, "Hopeful Eidolon", 2); // Spirit when cast as creature, not when cast as aura + addCard(Zone.BATTLEFIELD, playerA, "Plains", 5); + addCard(Zone.BATTLEFIELD, playerA, "Waxmane Baku"); // May add a counter on casting a spirit spell + addCard(Zone.BATTLEFIELD, playerA, "Drogskol Cavalry"); // +2 life whenever another spirit enters + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hopeful Eidolon using bestow", "Waxmane Baku"); + checkPermanentCounters("Not a Spirit, no KI counters", 1, PhaseStep.BEGIN_COMBAT, playerA, "Waxmane Baku", CounterType.KI, 0); + checkLife("Not a Spirit, no lifegain", 1, PhaseStep.BEGIN_COMBAT, playerA, 20); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Hopeful Eidolon"); + setChoice(playerA, true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Hopeful Eidolon", 2); + assertPowerToughness(playerA, "Waxmane Baku", 3, 3); + assertCounterCount(playerA, "Waxmane Baku", CounterType.KI, 1); + assertLife(playerA, 22); + } + + @Test + public void testBestowSBATiming() { + addCard(Zone.HAND, playerA, "Hopeful Eidolon", 2); // +1/+1 + addCard(Zone.HAND, playerA, "Pyroclasm", 2); // 2 damage + addCard(Zone.BATTLEFIELD, playerA, "Plateau", 10); + addCard(Zone.BATTLEFIELD, playerA, "Crusader of Odric"); + addCard(Zone.BATTLEFIELD, playerA, "Memnite"); + addCard(Zone.BATTLEFIELD, playerA, "Eager Cadet"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hopeful Eidolon using bestow", "Memnite", true); + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Hopeful Eidolon using bestow", "Eager Cadet"); + checkPT("Memnite P/T", 1, PhaseStep.BEGIN_COMBAT, playerA, "Memnite", 2, 2); + checkPT("Eager Cadet P/T", 1, PhaseStep.BEGIN_COMBAT, playerA, "Eager Cadet", 2, 2); + checkPT("Crusader P/T", 1, PhaseStep.BEGIN_COMBAT, playerA, "Crusader of Odric", 3, 3); + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Pyroclasm"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertPermanentCount(playerA, "Hopeful Eidolon", 2); + assertPermanentCount(playerA, "Crusader of Odric", 1); + assertDamageReceived(playerA, "Crusader of Odric", 2); + assertGraveyardCount(playerA, 3); + } + } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java index fc9123a883e..b2d7b4ca535 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/PrototypeTest.java @@ -2,6 +2,7 @@ package org.mage.test.cards.abilities.keywords; import mage.MageObject; import mage.ObjectColor; +import mage.abilities.common.EntersBattlefieldAllTriggeredAbility; import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.keyword.HasteAbility; @@ -9,6 +10,7 @@ import mage.cards.Card; import mage.constants.ComparisonType; import mage.constants.PhaseStep; import mage.constants.Zone; +import mage.filter.FilterPermanent; import mage.filter.FilterSpell; import mage.filter.StaticFilters; import mage.filter.predicate.Predicate; @@ -61,14 +63,22 @@ public class PrototypeTest extends CardTestPlayerBase { } private void makeTester(Predicate... predicates) { - FilterSpell filter = new FilterSpell(); + FilterSpell filterA = new FilterSpell(); + FilterPermanent filterB = new FilterPermanent(); for (Predicate predicate : predicates) { - filter.add(predicate); + filterA.add(predicate); + filterB.add(predicate); } addCustomCardWithAbility( "tester", playerA, new SpellCastControllerTriggeredAbility( - new GainLifeEffect(1), filter, false + new GainLifeEffect(1), filterA, false + ) + ); + addCustomCardWithAbility( + "tester", playerB, + new EntersBattlefieldAllTriggeredAbility( + new GainLifeEffect(1), filterB, false ) ); } @@ -159,6 +169,7 @@ public class PrototypeTest extends CardTestPlayerBase { execute(); assertLife(playerA, 20 + 1); + assertLife(playerB, 20 + 1); } @Test @@ -174,6 +185,7 @@ public class PrototypeTest extends CardTestPlayerBase { execute(); assertLife(playerA, 20 + 1); + assertLife(playerB, 20 + 1); } @Test @@ -192,6 +204,7 @@ public class PrototypeTest extends CardTestPlayerBase { execute(); assertLife(playerA, 20 + 1); + assertLife(playerB, 20 + 1); } @Test @@ -210,6 +223,7 @@ public class PrototypeTest extends CardTestPlayerBase { execute(); assertLife(playerA, 20 + 1); + assertLife(playerB, 20 + 1); } @Test @@ -225,6 +239,7 @@ public class PrototypeTest extends CardTestPlayerBase { execute(); assertLife(playerA, 20 + 1); + assertLife(playerB, 20 + 1); } @Test @@ -240,6 +255,7 @@ public class PrototypeTest extends CardTestPlayerBase { execute(); assertLife(playerA, 20 + 1); + assertLife(playerB, 20 + 1); } @Test diff --git a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java index be58f93c598..4d910579aca 100644 --- a/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java +++ b/Mage.Tests/src/test/java/org/mage/test/serverside/base/impl/CardTestPlayerAPIImpl.java @@ -1127,12 +1127,10 @@ public abstract class CardTestPlayerAPIImpl extends MageTestPlayerBase implement int actualCount = 0; for (Permanent permanent : currentGame.getBattlefield().getAllActivePermanents()) { - if (permanent instanceof PermanentToken) { - if (permanent.getControllerId().equals(player.getId())) { - if (isObjectHaveTargetNameOrAlias(player, permanent, tokenName)) { - actualCount++; - } - } + if (permanent instanceof PermanentToken + && permanent.getControllerId().equals(player.getId()) + && isObjectHaveTargetNameOrAlias(player, permanent, tokenName)) { + actualCount++; } } Assert.assertEquals("(Battlefield) Tokens counts for " + player.getName() + " are not equal (" + tokenName + ')', count, actualCount); diff --git a/Mage/src/main/java/mage/abilities/keyword/BestowAbility.java b/Mage/src/main/java/mage/abilities/keyword/BestowAbility.java index 611d3bb2703..f899e9b9e4f 100644 --- a/Mage/src/main/java/mage/abilities/keyword/BestowAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/BestowAbility.java @@ -6,14 +6,13 @@ import mage.abilities.SpellAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Costs; import mage.abilities.costs.mana.ManaCostsImpl; -import mage.abilities.effects.ReplacementEffectImpl; +import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.AttachEffect; import mage.cards.Card; import mage.constants.*; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; import mage.game.permanent.Permanent; +import mage.game.stack.Spell; import mage.target.TargetPermanent; import mage.target.common.TargetCreaturePermanent; import mage.util.CardUtil; @@ -90,9 +89,9 @@ public class BestowAbility extends SpellAbility { TargetPermanent auraTarget = new TargetCreaturePermanent(); this.addTarget(auraTarget); this.addEffect(new AttachEffect(Outcome.BoostCreature)); - Ability ability = new SimpleStaticAbility(new BestowEntersBattlefieldEffect()); + Ability ability = new SimpleStaticAbility(new BestowTypeEffect()); ability.setRuleVisible(false); - addSubAbility(ability); + this.addSubAbility(ability); } protected BestowAbility(final BestowAbility ability) { @@ -122,70 +121,65 @@ public class BestowAbility extends SpellAbility { sb.append(" (If you cast this card for its bestow cost, it's an Aura spell with enchant creature. It becomes a creature again if it's not attached to a creature.)"); return sb.toString(); } - - public static void becomeCreature(Permanent permanent, Game game) { - // permanently changes to the object - if (permanent != null) { - MageObject basicObject = permanent.getBasicMageObject(); - if (basicObject != null) { - game.checkStateAndTriggered(); // Bug #8157 - basicObject.getSubtype().remove(SubType.AURA); - basicObject.addCardType(CardType.CREATURE); - } - permanent.getSubtype().remove(SubType.AURA); - permanent.addCardType(CardType.CREATURE); - } - } - public static void becomeAura(Card card) { - // permanently changes to the object + // permanently changes to the object, only use on copies if (card != null) { + if (!card.getCardType().contains(CardType.ENCHANTMENT)) { + throw new IllegalStateException("Bestow perpetual becomeAura called on non-enchantment card"); + } card.addSubType(SubType.AURA); card.removeCardType(CardType.CREATURE); - card.addCardType(CardType.ENCHANTMENT); + card.removeAllCreatureTypes(); + if (card instanceof Spell) { + ((Spell) card).addAbilityForCopy(new EnchantAbility(new TargetCreaturePermanent())); + } else { + card.addAbility(new EnchantAbility(new TargetCreaturePermanent())); + } + } + } + public static void becomeAura(Game game, MageObject object) { + // temporary changes only + if (object != null && object.getCardType(game).contains(CardType.ENCHANTMENT)) { + object.addSubType(game, SubType.AURA); + object.removeCardType(game, CardType.CREATURE); + object.removeAllCreatureTypes(game); + if (object instanceof Permanent) { + ((Permanent) object).addAbility(new EnchantAbility(new TargetCreaturePermanent()), object.getId(), game); + } else if (object instanceof Spell) { + game.getState().addOtherAbility(((Spell) object).getCard(), new EnchantAbility(new TargetCreaturePermanent())); + } else if (object instanceof Card) { + game.getState().addOtherAbility((Card) object, new EnchantAbility(new TargetCreaturePermanent())); + } else { + throw new IllegalArgumentException("Bestow temporary becomeAura called on non-Permanent non-Spell object: " + object.getClass().getName()); + } } } } -class BestowEntersBattlefieldEffect extends ReplacementEffectImpl { +class BestowTypeEffect extends ContinuousEffectImpl { - public BestowEntersBattlefieldEffect() { - super(Duration.WhileOnBattlefield, Outcome.Neutral); + BestowTypeEffect() { + super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); } - protected BestowEntersBattlefieldEffect(final BestowEntersBattlefieldEffect effect) { + private BestowTypeEffect(final BestowTypeEffect effect) { super(effect); } @Override - public boolean checksEventType(GameEvent event, Game game) { - return EventType.ENTERS_THE_BATTLEFIELD_SELF == event.getType(); + public BestowTypeEffect copy() { + return new BestowTypeEffect(this); } @Override - public boolean applies(GameEvent event, Ability source, Game game) { - return event.getTargetId().equals(source.getSourceId()); - } - - @Override - public boolean replaceEvent(GameEvent event, Ability source, Game game) { - Permanent bestowPermanent = game.getPermanentEntering(source.getSourceId()); - if (bestowPermanent == null || !bestowPermanent.hasSubtype(SubType.AURA, game)) { + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { return false; } - - // change types permanently - MageObject basicObject = bestowPermanent.getBasicMageObject(); - if (basicObject != null && !basicObject.getSubtype().contains(SubType.AURA)) { - basicObject.addSubType(SubType.AURA); - basicObject.removeCardType(CardType.CREATURE); + if (game.getPermanent(permanent.getAttachedTo()) != null){ + BestowAbility.becomeAura(game, permanent); } - return false; + return true; } - - @Override - public BestowEntersBattlefieldEffect copy() { - return new BestowEntersBattlefieldEffect(this); - } - } diff --git a/Mage/src/main/java/mage/abilities/keyword/ReconfigureAbility.java b/Mage/src/main/java/mage/abilities/keyword/ReconfigureAbility.java index a423bcd0792..303b42e0d29 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ReconfigureAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ReconfigureAbility.java @@ -135,6 +135,7 @@ class ReconfigureTypeEffect extends ContinuousEffectImpl { return false; } permanent.removeCardType(game, CardType.CREATURE); + permanent.removeAllCreatureTypes(game); return true; } } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 62a2b2f17cd..82d0ad5ce63 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -2670,10 +2670,9 @@ public abstract class GameImpl implements Game { Permanent attachedTo = getPermanent(perm.getAttachedTo()); if (attachedTo == null || !attachedTo.getAttachments().contains(perm.getId())) { // handle bestow unattachment - Card card = this.getCard(perm.getId()); - if (card != null && card.isCreature(this)) { + if (perm.getAbilities().stream().anyMatch(x -> x instanceof BestowAbility)) { UUID wasAttachedTo = perm.getAttachedTo(); - perm.attachTo(null, null, this); + perm.unattach(this); fireEvent(new UnattachedEvent(wasAttachedTo, perm.getId(), perm, null)); } else if (movePermanentToGraveyardWithInfo(perm)) { somethingHappened = true; @@ -2683,11 +2682,9 @@ public abstract class GameImpl implements Game { if (auraFilter instanceof FilterPermanent) { if (!((FilterPermanent) auraFilter).match(attachedTo, perm.getControllerId(), perm.getSpellAbility(), this) || attachedTo.cantBeAttachedBy(perm, null, this, true)) { - Card card = this.getCard(perm.getId()); - if (card != null && card.isCreature(this)) { + if (perm.getAbilities().stream().anyMatch(x -> x instanceof BestowAbility)) { UUID wasAttachedTo = perm.getAttachedTo(); - perm.attachTo(null, null, this); - BestowAbility.becomeCreature(perm, this); + perm.unattach(this); fireEvent(new UnattachedEvent(wasAttachedTo, perm.getId(), perm, null)); } else if (movePermanentToGraveyardWithInfo(perm)) { somethingHappened = true; @@ -2695,11 +2692,9 @@ public abstract class GameImpl implements Game { } } else if (!auraFilter.match(attachedTo, this) || attachedTo.cantBeAttachedBy(perm, null, this, true)) { // handle bestow unattachment - Card card = this.getCard(perm.getId()); - if (card != null && card.isCreature(this)) { + if (perm.getAbilities().stream().anyMatch(x -> x instanceof BestowAbility)) { UUID wasAttachedTo = perm.getAttachedTo(); - perm.attachTo(null, null, this); - BestowAbility.becomeCreature(perm, this); + perm.unattach(this); fireEvent(new UnattachedEvent(wasAttachedTo, perm.getId(), perm, null)); } else if (movePermanentToGraveyardWithInfo(perm)) { somethingHappened = true; diff --git a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java index 23422c76a64..f800b17f368 100644 --- a/Mage/src/main/java/mage/game/permanent/PermanentImpl.java +++ b/Mage/src/main/java/mage/game/permanent/PermanentImpl.java @@ -2061,24 +2061,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { return color; } - //20180810 - 701.3d - //If an object leaves the zone it's in, all attached permanents become unattached - //note that this code doesn't actually detach anything, and is a bit of a bandaid - public void detachAllAttachments(Game game) { - for (UUID attachmentId : getAttachments()) { - Permanent attachment = game.getPermanent(attachmentId); - Card attachmentCard = game.getCard(attachmentId); - if (attachment != null && attachmentCard != null) { - //make bestow cards and licids into creatures - //aura test to stop bludgeon brawl shenanigans from using this code - //consider adding code to handle that case? - if (attachment.hasSubtype(SubType.AURA, game) && attachmentCard.isCreature(game)) { - BestowAbility.becomeCreature(attachment, game); - } - } - } - } - @Override public boolean moveToZone(Zone toZone, Ability source, Game game, boolean flag, List appliedEffects) { Zone fromZone = game.getState().getZone(objectId); @@ -2091,12 +2073,7 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { } else { zoneChangeInfo = new ZoneChangeInfo(event); } - boolean successfullyMoved = ZonesHandler.moveCard(zoneChangeInfo, game, source); - //20180810 - 701.3d - if (successfullyMoved) { - detachAllAttachments(game); - } - return successfullyMoved; + return ZonesHandler.moveCard(zoneChangeInfo, game, source); } return false; } @@ -2107,11 +2084,6 @@ public abstract class PermanentImpl extends CardImpl implements Permanent { ZoneChangeEvent event = new ZoneChangeEvent(this, source, ownerId, fromZone, Zone.EXILED, appliedEffects); ZoneChangeInfo.Exile zcInfo = new ZoneChangeInfo.Exile(event, exileId, name); - boolean successfullyMoved = ZonesHandler.moveCard(zcInfo, game, source); - //20180810 - 701.3d - if (successfullyMoved) { - detachAllAttachments(game); - } - return successfullyMoved; + return ZonesHandler.moveCard(zcInfo, game, source); } } diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 2b1ed1937c6..e9de92a4a63 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -20,7 +20,6 @@ import mage.game.MageObjectAttribute; import mage.game.events.CopiedStackObjectEvent; import mage.game.events.ZoneChangeEvent; import mage.game.permanent.Permanent; -import mage.game.permanent.PermanentCard; import mage.game.permanent.token.Token; import mage.players.Player; import mage.util.CardUtil; @@ -335,45 +334,39 @@ public class Spell extends StackObjectImpl implements Card { counter(null, /*this.getSpellAbility()*/ game); return false; } else if (this.isEnchantment(game) && this.hasSubtype(SubType.AURA, game)) { + boolean bestow = SpellAbilityCastMode.BESTOW.equals(ability.getSpellAbilityCastMode()); if (ability.getTargets().stillLegal(ability, game)) { - boolean bestow = SpellAbilityCastMode.BESTOW.equals(ability.getSpellAbilityCastMode()); if (bestow) { // before put to play: // Must be removed first time, after that will be removed by continous effect // Otherwise effects like evolve trigger from creature comes into play event - card.removeCardType(CardType.CREATURE); - card.addSubType(game, SubType.AURA); + BestowAbility.becomeAura(game, card); } UUID permId; - boolean flag; + boolean permanentCreated; if (isCopy()) { Token token = CopyTokenFunction.createTokenCopy(card, game, this); // The token that a resolving copy of a spell becomes isn’t said to have been “created.” (2020-09-25) if (token.putOntoBattlefield(1, game, ability, getControllerId(), false, false, null, null, false)) { permId = token.getLastAddedTokenIds().stream().findFirst().orElse(null); - flag = true; + permanentCreated = true; } else { permId = null; - flag = false; + permanentCreated = false; } } else { permId = card.getId(); MageObjectReference mor = new MageObjectReference(getSpellAbility()); game.storePermanentCostsTags(mor, getSpellAbility()); - flag = controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null); + permanentCreated = controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null); } - if (flag) { + if (permanentCreated) { if (bestow) { - // card will be copied during putOntoBattlefield, so the card of CardPermanent has to be changed - // TODO: Find a better way to prevent bestow creatures from being effected by creature affecting abilities Permanent permanent = game.getPermanent(permId); - if (permanent instanceof PermanentCard) { - // after put to play: - // restore removed stats (see "before put to play" above) - permanent.setSpellAbility(ability); // otherwise spell ability without bestow will be set - card.addCardType(CardType.CREATURE); - card.getSubtype().remove(SubType.AURA); - } + permanent.setSpellAbility(ability); // otherwise spell ability without bestow will be set + // The continuous effect that makes the permanent an aura doesn't apply until after the permanent has already entered, + // so it must be modified manually here first. Same root cause as the Blood Moon problem https://github.com/magefree/mage/issues/4202 + BestowAbility.becomeAura(game, permanent); } if (isCopy()) { Permanent token = game.getPermanent(permId); @@ -381,7 +374,8 @@ public class Spell extends StackObjectImpl implements Card { return false; } for (Ability ability2 : token.getAbilities()) { - if (!bestow || ability2 instanceof BestowAbility) { + if (ability2 instanceof SpellAbility && ability2.getTargets().size() == 1) { + // Copy aura SpellAbility's targets into the new token's SpellAbility ability2.getTargets().get(0).add(ability.getFirstTarget(), game); ability2.getEffects().get(0).apply(game, ability2); return ability2.resolve(game); @@ -391,24 +385,13 @@ public class Spell extends StackObjectImpl implements Card { } return ability.resolve(game); } - if (bestow) { - card.addCardType(game, CardType.CREATURE); - } return false; } // Aura has no legal target and its a bestow enchantment -> Add it to battlefield as creature - if (SpellAbilityCastMode.BESTOW.equals(this.getSpellAbility().getSpellAbilityCastMode())) { + if (bestow) { MageObjectReference mor = new MageObjectReference(getSpellAbility()); game.storePermanentCostsTags(mor, getSpellAbility()); - if (controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null)) { - Permanent permanent = game.getPermanent(card.getId()); - if (permanent instanceof PermanentCard) { - ((PermanentCard) permanent).getCard().addCardType(game, CardType.CREATURE); - ((PermanentCard) permanent).getCard().removeSubType(game, SubType.AURA); - return true; - } - } - return false; + return controller.moveCards(card, Zone.BATTLEFIELD, ability, game, false, faceDown, false, null); } else { //20091005 - 608.2b if (!game.isSimulation()) { @@ -585,10 +568,8 @@ public class Spell extends StackObjectImpl implements Card { return cardTypes; } if (SpellAbilityCastMode.BESTOW.equals(this.getSpellAbility().getSpellAbilityCastMode())) { - List cardTypes = new ArrayList<>(); - cardTypes.addAll(card.getCardType(game)); - cardTypes.remove(CardType.CREATURE); - return cardTypes; + Card modifiedCard = this.getSpellAbility().getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(card, this.getSpellAbility(), game); + return modifiedCard.getCardType(game); } return card.getCardType(game); } @@ -600,26 +581,19 @@ public class Spell extends StackObjectImpl implements Card { @Override public SubTypes getSubtype(Game game) { + // Bestow's changes are non-copiable, and must be reapplied if (SpellAbilityCastMode.BESTOW.equals(this.getSpellAbility().getSpellAbilityCastMode())) { - SubTypes subtypes = card.getSubtype(game); - if (!subtypes.contains(SubType.AURA)) { // do it only once - subtypes.add(SubType.AURA); - } - return subtypes; + Card modifiedCard = this.getSpellAbility().getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(card, this.getSpellAbility(), game); + return modifiedCard.getSubtype(); } return card.getSubtype(game); } @Override public boolean hasSubtype(SubType subtype, Game game) { - if (SpellAbilityCastMode.BESTOW.equals(this.getSpellAbility().getSpellAbilityCastMode())) { // workaround for Bestow (don't like it) - SubTypes subtypes = card.getSubtype(game); - if (!subtypes.contains(SubType.AURA)) { // do it only once - subtypes.add(SubType.AURA); - } - if (subtypes.contains(subtype)) { - return true; - } + if (SpellAbilityCastMode.BESTOW.equals(this.getSpellAbility().getSpellAbilityCastMode())) { + Card modifiedCard = this.getSpellAbility().getSpellAbilityCastMode().getTypeModifiedCardObjectCopy(card, this.getSpellAbility(), game); + return modifiedCard.hasSubtype(subtype, game); } return card.hasSubtype(subtype, game); }