From 5fcc037caed48871889da794ac0a3634bfd2dd2f Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:04:28 -0500 Subject: [PATCH 1/3] [ACR] Implement Kassandra, Eagle Bearer --- .../mage/cards/k/KassandraEagleBearer.java | 161 ++++++++++++++++++ Mage.Sets/src/mage/sets/AssassinsCreed.java | 1 + 2 files changed, 162 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/k/KassandraEagleBearer.java diff --git a/Mage.Sets/src/mage/cards/k/KassandraEagleBearer.java b/Mage.Sets/src/mage/cards/k/KassandraEagleBearer.java new file mode 100644 index 000000000000..c1769f27084a --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KassandraEagleBearer.java @@ -0,0 +1,161 @@ +package mage.cards.k; + +import java.util.Objects; +import java.util.UUID; +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.*; +import mage.constants.*; +import mage.abilities.keyword.HasteAbility; +import mage.filter.FilterCard; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInLibrary; + +/** + * + * @author Grath + */ +public final class KassandraEagleBearer extends CardImpl { + private static final FilterControlledCreaturePermanent filter = new FilterControlledCreaturePermanent(); + + static { + filter.add(KassandraEagleBearerPredicate.instance); + } + + public KassandraEagleBearer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.subtype.add(SubType.WARRIOR); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Haste + this.addAbility(HasteAbility.getInstance()); + + // When Kassandra enters the battlefield, search your graveyard, hand, and library for a card named The Spear of Leonidas, put it onto the battlefield, then shuffle. + this.addAbility(new EntersBattlefieldTriggeredAbility(new KassandraEagleBearerEffect(), false)); + + // Whenever a creature you control with a legendary Equipment attached to it deals combat damage to a player, draw a card. + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility(new DrawCardSourceControllerEffect(1), + filter, false, SetTargetPointer.NONE, true).setTriggerPhrase( + "Whenever a creature you control with a legendary Equipment attached to it deals combat damage to a player, ")); + } + + private KassandraEagleBearer(final KassandraEagleBearer card) { + super(card); + } + + @Override + public KassandraEagleBearer copy() { + return new KassandraEagleBearer(this); + } +} + +class KassandraEagleBearerEffect extends OneShotEffect { + private static final String spearName = "The Spear of Leonidas"; + + KassandraEagleBearerEffect() { + super(Outcome.PutCardInPlay); + this.staticText = "search your graveyard, hand and/or library for a card named The Spear of Leonidas," + + " put it onto the battlefield, then shuffle."; + } + + private KassandraEagleBearerEffect(final KassandraEagleBearerEffect effect) { + super(effect); + } + + @Override + public KassandraEagleBearerEffect copy() { + return new KassandraEagleBearerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + Card spearCard = null; + FilterCard filter = new FilterCard("card named The Spear of Leonidas"); + filter.add(new NamePredicate(spearName)); + TargetCardInLibrary libraryTarget = new TargetCardInLibrary(filter); + if (controller.searchLibrary(libraryTarget, source, game)) { + for (UUID id : libraryTarget.getTargets()) { + spearCard = game.getCard(id); + } + } + + // Hand is a hidden zone - you may fail to find a Spear of Leonidas that's in your hand. + // 701.19b. If a player is searching a hidden zone for cards with a stated quality, such as a card with a + // certain card type or color, that player isn't required to find some or all of those cards even if they're + // present in that zone. + // This is true even if your hand is revealed: + // 400.2. ... Hidden zones are zones in which not all players can be expected to see the cards' faces. + // Library and hand are hidden zones, even if all the cards in one such zone happen to be revealed. + if (spearCard == null) { + FilterCard filter2 = new FilterCard("card from your hand named The Spear of Leonidas"); + filter2.add(new NamePredicate(spearName)); + TargetCard target = new TargetCard(0, 1, Zone.HAND, filter2); + target.withNotTarget(true); + Cards cards = new CardsImpl(); + cards.addAllCards(controller.getHand().getCards(filter, source.getControllerId(), source, game)); + if (!cards.isEmpty()) { + controller.choose(outcome, cards, target, source, game); + for (UUID id : target.getTargets()) { + spearCard = game.getCard(id); + } + } + } + + // You cannot fail to find a spear if there is one in your graveyard, because the graveyard is not hidden. + if (spearCard == null) { + TargetCard target = new TargetCard(1, 1, Zone.GRAVEYARD, filter); + target.withNotTarget(true); + Cards cards = new CardsImpl(); + cards.addAllCards(controller.getGraveyard().getCards(filter, source.getControllerId(), source, game)); + if (!cards.isEmpty()) { + controller.choose(outcome, cards, target, source, game); + for (UUID id : target.getTargets()) { + spearCard = game.getCard(id); + } + } + } + + if (spearCard != null) { + controller.moveCards(spearCard, Zone.BATTLEFIELD, source, game); + } + controller.shuffleLibrary(source, game); + return true; + } +} + +enum KassandraEagleBearerPredicate implements Predicate { + instance; + + @Override + public boolean apply(Permanent input, Game game) { + return input.getAttachments() + .stream() + .map(game::getPermanentOrLKIBattlefield) + .filter(Objects::nonNull) + .anyMatch(attachment -> attachment.hasSubtype(SubType.EQUIPMENT, game) && attachment.isLegendary()); + } + + @Override + public String toString() { + return "creature you control with a legendary Equipment attached to it"; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/AssassinsCreed.java b/Mage.Sets/src/mage/sets/AssassinsCreed.java index 09f13e42ce4d..4800ca335fed 100644 --- a/Mage.Sets/src/mage/sets/AssassinsCreed.java +++ b/Mage.Sets/src/mage/sets/AssassinsCreed.java @@ -84,6 +84,7 @@ private AssassinsCreed() { cards.add(new SetCardInfo("Hunter's Bow", 41, Rarity.UNCOMMON, mage.cards.h.HuntersBow.class)); cards.add(new SetCardInfo("Island", 103, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Jackdaw", 58, Rarity.RARE, mage.cards.j.Jackdaw.class)); + cards.add(new SetCardInfo("Kassandra, Eagle Bearer", 59, Rarity.MYTHIC, mage.cards.k.KassandraEagleBearer.class)); cards.add(new SetCardInfo("Keen-Eyed Raven", 279, Rarity.UNCOMMON, mage.cards.k.KeenEyedRaven.class)); cards.add(new SetCardInfo("Labyrinth Adversary", 290, Rarity.UNCOMMON, mage.cards.l.LabyrinthAdversary.class)); cards.add(new SetCardInfo("Leonardo da Vinci", 20, Rarity.MYTHIC, mage.cards.l.LeonardoDaVinci.class)); From 9ba12d13d2e84e754faf1ac40da23c61fa705226 Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Mon, 20 Jan 2025 15:52:48 -0500 Subject: [PATCH 2/3] Update Kassandra, Eagle Bearer to reduce player choose dialogues from three to two. --- .../mage/cards/k/KassandraEagleBearer.java | 45 +++++++++---------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/Mage.Sets/src/mage/cards/k/KassandraEagleBearer.java b/Mage.Sets/src/mage/cards/k/KassandraEagleBearer.java index c1769f27084a..fb19218dd2d8 100644 --- a/Mage.Sets/src/mage/cards/k/KassandraEagleBearer.java +++ b/Mage.Sets/src/mage/cards/k/KassandraEagleBearer.java @@ -1,6 +1,7 @@ package mage.cards.k; import java.util.Objects; +import java.util.Set; import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; @@ -98,36 +99,30 @@ public boolean apply(Game game, Ability source) { } } - // Hand is a hidden zone - you may fail to find a Spear of Leonidas that's in your hand. - // 701.19b. If a player is searching a hidden zone for cards with a stated quality, such as a card with a - // certain card type or color, that player isn't required to find some or all of those cards even if they're - // present in that zone. - // This is true even if your hand is revealed: - // 400.2. ... Hidden zones are zones in which not all players can be expected to see the cards' faces. - // Library and hand are hidden zones, even if all the cards in one such zone happen to be revealed. if (spearCard == null) { FilterCard filter2 = new FilterCard("card from your hand named The Spear of Leonidas"); filter2.add(new NamePredicate(spearName)); - TargetCard target = new TargetCard(0, 1, Zone.HAND, filter2); - target.withNotTarget(true); - Cards cards = new CardsImpl(); - cards.addAllCards(controller.getHand().getCards(filter, source.getControllerId(), source, game)); - if (!cards.isEmpty()) { - controller.choose(outcome, cards, target, source, game); - for (UUID id : target.getTargets()) { - spearCard = game.getCard(id); - } + Cards spears = new CardsImpl(); + spears.addAllCards(controller.getHand().getCards(filter, source.getControllerId(), source, game)); + Set spearsInGraveyard = controller.getGraveyard().getCards(filter, source.getControllerId(), source, game); + TargetCard target; + if (spearsInGraveyard.isEmpty()) { + // Hand is a hidden zone - you may fail to find a Spear of Leonidas that's in your hand. + // 701.19b. If a player is searching a hidden zone for cards with a stated quality, such as a card with a + // certain card type or color, that player isn't required to find some or all of those cards even if they're + // present in that zone. + // This is true even if your hand is revealed: + // 400.2. ... Hidden zones are zones in which not all players can be expected to see the cards' faces. + // Library and hand are hidden zones, even if all the cards in one such zone happen to be revealed. + target = new TargetCard(0, 1, Zone.HAND, filter); + } else { + spears.addAllCards(spearsInGraveyard); + // You cannot fail to find a spear if there is one in your graveyard, because the graveyard is not hidden. + target = new TargetCard(1, 1, Zone.HAND, filter); } - } - - // You cannot fail to find a spear if there is one in your graveyard, because the graveyard is not hidden. - if (spearCard == null) { - TargetCard target = new TargetCard(1, 1, Zone.GRAVEYARD, filter); target.withNotTarget(true); - Cards cards = new CardsImpl(); - cards.addAllCards(controller.getGraveyard().getCards(filter, source.getControllerId(), source, game)); - if (!cards.isEmpty()) { - controller.choose(outcome, cards, target, source, game); + if (!spears.isEmpty()) { + controller.choose(outcome, spears, target, source, game); for (UUID id : target.getTargets()) { spearCard = game.getCard(id); } From 29bab5f6140cc5e7132e976f501be0ad76054d42 Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Tue, 21 Jan 2025 15:26:29 -0500 Subject: [PATCH 3/3] Fix Tribute to the World Tree to still draw a card if the creature has died before the ability resolves. --- Mage.Sets/src/mage/cards/t/TributeToTheWorldTree.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/t/TributeToTheWorldTree.java b/Mage.Sets/src/mage/cards/t/TributeToTheWorldTree.java index 42ffe0a780e7..202812dd81e7 100644 --- a/Mage.Sets/src/mage/cards/t/TributeToTheWorldTree.java +++ b/Mage.Sets/src/mage/cards/t/TributeToTheWorldTree.java @@ -63,7 +63,7 @@ public boolean apply(Game game, Ability source) { } // We need to ask the game for the actualized object for the entering permanent. - Permanent permanent = game.getPermanent(permanentEntering.getId()); + Permanent permanent = game.getPermanentOrLKIBattlefield(permanentEntering.getId()); if (permanent == null) { return false; }