From cc330ac3b8d7d38cf2320b54f8778cbc525a0f22 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 09:54:33 -0500 Subject: [PATCH 01/29] [TLA] Implement The Legend of Kurruk / Avatar Kurruk --- Mage.Sets/src/mage/cards/a/AvatarKuruk.java | 50 +++++++++++++++++ .../src/mage/cards/t/TheLegendOfKuruk.java | 54 +++++++++++++++++++ .../src/mage/sets/AvatarTheLastAirbender.java | 4 ++ 3 files changed, 108 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/a/AvatarKuruk.java create mode 100644 Mage.Sets/src/mage/cards/t/TheLegendOfKuruk.java diff --git a/Mage.Sets/src/mage/cards/a/AvatarKuruk.java b/Mage.Sets/src/mage/cards/a/AvatarKuruk.java new file mode 100644 index 00000000000..94c2be30e65 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AvatarKuruk.java @@ -0,0 +1,50 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.costs.common.WaterbendCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.turn.AddExtraTurnControllerEffect; +import mage.abilities.keyword.ExhaustAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.SpiritWorldToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AvatarKuruk extends CardImpl { + + public AvatarKuruk(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.AVATAR); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + this.nightCard = true; + + // Whenever you cast a spell, create a 1/1 colorless Spirit creature token with "This token can't block or be blocked by non-Spirit creatures." + this.addAbility(new SpellCastControllerTriggeredAbility( + new CreateTokenEffect(new SpiritWorldToken()), StaticFilters.FILTER_SPELL_A, false + )); + + // Exhaust -- Waterbend {20}: Take an extra turn after this one. + this.addAbility(new ExhaustAbility(new AddExtraTurnControllerEffect(), new WaterbendCost(20))); + } + + private AvatarKuruk(final AvatarKuruk card) { + super(card); + } + + @Override + public AvatarKuruk copy() { + return new AvatarKuruk(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheLegendOfKuruk.java b/Mage.Sets/src/mage/cards/t/TheLegendOfKuruk.java new file mode 100644 index 00000000000..17e8d452e98 --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheLegendOfKuruk.java @@ -0,0 +1,54 @@ +package mage.cards.t; + +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.Effects; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ExileSagaAndReturnTransformedEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SagaChapter; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheLegendOfKuruk extends CardImpl { + + public TheLegendOfKuruk(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}{U}"); + + this.subtype.add(SubType.SAGA); + this.secondSideCardClazz = mage.cards.a.AvatarKuruk.class; + + // (As this Saga enters and after your draw step, add a lore counter.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I, II -- Scry 2, then draw a card. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_II, + new Effects( + new ScryEffect(2, false), + new DrawCardSourceControllerEffect(1).concatBy(", then") + ) + ); + + // III -- Exile this Saga, then return it to the battlefield transformed under your control. + this.addAbility(new TransformAbility()); + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new ExileSagaAndReturnTransformedEffect()); + this.addAbility(sagaAbility); + } + + private TheLegendOfKuruk(final TheLegendOfKuruk card) { + super(card); + } + + @Override + public TheLegendOfKuruk copy() { + return new TheLegendOfKuruk(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index 09565a8ab8f..99b05e5a654 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -66,6 +66,8 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Avatar Aang", 308, Rarity.MYTHIC, mage.cards.a.AvatarAang.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Avatar Aang", 363, Rarity.MYTHIC, mage.cards.a.AvatarAang.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Avatar Enthusiasts", 11, Rarity.COMMON, mage.cards.a.AvatarEnthusiasts.class)); + cards.add(new SetCardInfo("Avatar Kuruk", 355, Rarity.MYTHIC, mage.cards.a.AvatarKuruk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avatar Kuruk", 61, Rarity.MYTHIC, mage.cards.a.AvatarKuruk.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Avatar's Wrath", 12, Rarity.RARE, mage.cards.a.AvatarsWrath.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Avatar's Wrath", 365, Rarity.RARE, mage.cards.a.AvatarsWrath.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Azula Always Lies", 84, Rarity.COMMON, mage.cards.a.AzulaAlwaysLies.class)); @@ -311,6 +313,8 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Teo, Spirited Glider", 74, Rarity.UNCOMMON, mage.cards.t.TeoSpiritedGlider.class)); cards.add(new SetCardInfo("The Boulder, Ready to Rumble", 168, Rarity.UNCOMMON, mage.cards.t.TheBoulderReadyToRumble.class)); cards.add(new SetCardInfo("The Cave of Two Lovers", 126, Rarity.UNCOMMON, mage.cards.t.TheCaveOfTwoLovers.class)); + cards.add(new SetCardInfo("The Legend of Kuruk", 355, Rarity.MYTHIC, mage.cards.t.TheLegendOfKuruk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Legend of Kuruk", 61, Rarity.MYTHIC, mage.cards.t.TheLegendOfKuruk.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Lion-Turtle", 232, Rarity.RARE, mage.cards.t.TheLionTurtle.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Lion-Turtle", 328, Rarity.RARE, mage.cards.t.TheLionTurtle.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Mechanist, Aerial Artisan", 369, Rarity.RARE, mage.cards.t.TheMechanistAerialArtisan.class, NON_FULL_USE_VARIOUS)); From 7d482d7a4430ff22b4968f1f33440c2cc5385532 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 10:03:02 -0500 Subject: [PATCH 02/29] [TLA] Implement The Legend of Roku / Avatar Roku --- .../sources/ScryfallImageSupportTokens.java | 11 ++-- Mage.Sets/src/mage/cards/a/AvatarRoku.java | 48 ++++++++++++++++ .../src/mage/cards/t/TheLegendOfRoku.java | 55 +++++++++++++++++++ .../src/mage/sets/AvatarTheLastAirbender.java | 4 ++ .../token/DragonFirebendingToken.java | 32 +++++++++++ Mage/src/main/resources/tokens-database.txt | 3 +- 6 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/a/AvatarRoku.java create mode 100644 Mage.Sets/src/mage/cards/t/TheLegendOfRoku.java create mode 100644 Mage/src/main/java/mage/game/permanent/token/DragonFirebendingToken.java diff --git a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java index a70aa38e89b..7c3e4b0a507 100644 --- a/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java +++ b/Mage.Client/src/main/java/org/mage/plugins/card/dl/sources/ScryfallImageSupportTokens.java @@ -1,10 +1,10 @@ package org.mage.plugins.card.dl.sources; +import mage.cards.repository.TokenRepository; + import java.util.HashMap; import java.util.Map; -import mage.cards.repository.TokenRepository; - /** * @author JayDi85 */ @@ -2224,7 +2224,7 @@ public class ScryfallImageSupportTokens { put("WHO/Treasure/2", "https://api.scryfall.com/cards/twho/29?format=image"); put("WHO/Treasure/3", "https://api.scryfall.com/cards/twho/30?format=image"); put("WHO/Treasure/4", "https://api.scryfall.com/cards/twho/31?format=image"); - put("WHO/Warrior", "https://api.scryfall.com/cards/twho/9?format=image"); + put("WHO/Warrior", "https://api.scryfall.com/cards/twho/9?format=image"); // 8ED put("8ED/Bird", "https://api.scryfall.com/cards/p03/7/en?format=image"); @@ -2401,7 +2401,7 @@ public class ScryfallImageSupportTokens { put("OTP/Human Warrior", "https://api.scryfall.com/cards/totp/3/en?format=image"); put("OTP/Pest", "https://api.scryfall.com/cards/totp/4/en?format=image"); - // SCD + // SCD put("SCD/Beast", "https://api.scryfall.com/cards/tscd/19/en?format=image"); put("SCD/Bird", "https://api.scryfall.com/cards/tscd/2/en?format=image"); put("SCD/Cat", "https://api.scryfall.com/cards/tscd/3/en?format=image"); @@ -2743,7 +2743,7 @@ public class ScryfallImageSupportTokens { put("ACR/Treasure", "https://api.scryfall.com/cards/tacr/6?format=image"); // DD2 - put("DD2/Elemental Shaman", "https://api.scryfall.com/cards/tdd2/1?format=image"); + put("DD2/Elemental Shaman", "https://api.scryfall.com/cards/tdd2/1?format=image"); // FIN put("FIN/Hero/1", "https://api.scryfall.com/cards/tfin/2/en?format=image"); @@ -2844,6 +2844,7 @@ public class ScryfallImageSupportTokens { put("TLA/Clue/3", "https://api.scryfall.com/cards/ttla/16/?format=image"); put("TLA/Clue/4", "https://api.scryfall.com/cards/ttla/17/?format=image"); put("TLA/Clue/5", "https://api.scryfall.com/cards/ttla/18/?format=image"); + put("TLA/Dragon", "https://api.scryfall.com/cards/ttla/9/?format=image"); put("TLA/Food/1", "https://api.scryfall.com/cards/ttla/19/?format=image"); put("TLA/Food/2", "https://api.scryfall.com/cards/ttla/20/?format=image"); put("TLA/Food/3", "https://api.scryfall.com/cards/ttla/21/?format=image"); diff --git a/Mage.Sets/src/mage/cards/a/AvatarRoku.java b/Mage.Sets/src/mage/cards/a/AvatarRoku.java new file mode 100644 index 00000000000..c5b970958a7 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AvatarRoku.java @@ -0,0 +1,48 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FirebendingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.game.permanent.token.DragonFirebendingToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AvatarRoku extends CardImpl { + + public AvatarRoku(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.AVATAR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + this.nightCard = true; + + // Firebending 4 + this.addAbility(new FirebendingAbility(4)); + + // {8}: Create a 4/4 red Dragon creature token with flying and firebending 4. + this.addAbility(new SimpleActivatedAbility( + new CreateTokenEffect(new DragonFirebendingToken()), new GenericManaCost(8) + )); + } + + private AvatarRoku(final AvatarRoku card) { + super(card); + } + + @Override + public AvatarRoku copy() { + return new AvatarRoku(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheLegendOfRoku.java b/Mage.Sets/src/mage/cards/t/TheLegendOfRoku.java new file mode 100644 index 00000000000..7b87b91d5ff --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheLegendOfRoku.java @@ -0,0 +1,55 @@ +package mage.cards.t; + +import mage.Mana; +import mage.abilities.common.SagaAbility; +import mage.abilities.effects.common.ExileSagaAndReturnTransformedEffect; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.abilities.effects.mana.BasicManaEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SagaChapter; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheLegendOfRoku extends CardImpl { + + public TheLegendOfRoku(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{R}{R}"); + + this.subtype.add(SubType.SAGA); + this.secondSideCardClazz = mage.cards.a.AvatarRoku.class; + + // (As this Saga enters and after your draw step, add a lore counter.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Exile the top three cards of your library. Until the end of your next turn, you may play those cards. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, + new ExileTopXMayPlayUntilEffect(3, Duration.UntilEndOfYourNextTurn) + ); + + // II -- Add one mana of any color. + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_II, new BasicManaEffect(Mana.AnyMana(1))); + + // III -- Exile this Saga, then return it to the battlefield transformed under your control. + this.addAbility(new TransformAbility()); + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new ExileSagaAndReturnTransformedEffect()); + this.addAbility(sagaAbility); + } + + private TheLegendOfRoku(final TheLegendOfRoku card) { + super(card); + } + + @Override + public TheLegendOfRoku copy() { + return new TheLegendOfRoku(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index 99b05e5a654..08950b1fb44 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -68,6 +68,8 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Avatar Enthusiasts", 11, Rarity.COMMON, mage.cards.a.AvatarEnthusiasts.class)); cards.add(new SetCardInfo("Avatar Kuruk", 355, Rarity.MYTHIC, mage.cards.a.AvatarKuruk.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Avatar Kuruk", 61, Rarity.MYTHIC, mage.cards.a.AvatarKuruk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avatar Roku", 145, Rarity.MYTHIC, mage.cards.a.AvatarRoku.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avatar Roku", 357, Rarity.MYTHIC, mage.cards.a.AvatarRoku.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Avatar's Wrath", 12, Rarity.RARE, mage.cards.a.AvatarsWrath.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Avatar's Wrath", 365, Rarity.RARE, mage.cards.a.AvatarsWrath.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Azula Always Lies", 84, Rarity.COMMON, mage.cards.a.AzulaAlwaysLies.class)); @@ -315,6 +317,8 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("The Cave of Two Lovers", 126, Rarity.UNCOMMON, mage.cards.t.TheCaveOfTwoLovers.class)); cards.add(new SetCardInfo("The Legend of Kuruk", 355, Rarity.MYTHIC, mage.cards.t.TheLegendOfKuruk.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Legend of Kuruk", 61, Rarity.MYTHIC, mage.cards.t.TheLegendOfKuruk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Legend of Roku", 145, Rarity.MYTHIC, mage.cards.t.TheLegendOfRoku.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Legend of Roku", 357, Rarity.MYTHIC, mage.cards.t.TheLegendOfRoku.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Lion-Turtle", 232, Rarity.RARE, mage.cards.t.TheLionTurtle.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Lion-Turtle", 328, Rarity.RARE, mage.cards.t.TheLionTurtle.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Mechanist, Aerial Artisan", 369, Rarity.RARE, mage.cards.t.TheMechanistAerialArtisan.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage/src/main/java/mage/game/permanent/token/DragonFirebendingToken.java b/Mage/src/main/java/mage/game/permanent/token/DragonFirebendingToken.java new file mode 100644 index 00000000000..c6fd93d29cb --- /dev/null +++ b/Mage/src/main/java/mage/game/permanent/token/DragonFirebendingToken.java @@ -0,0 +1,32 @@ +package mage.game.permanent.token; + +import mage.MageInt; +import mage.abilities.keyword.FirebendingAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.constants.CardType; +import mage.constants.SubType; + +/** + * @author TheElk801 + */ +public final class DragonFirebendingToken extends TokenImpl { + + public DragonFirebendingToken() { + super("Dragon Token", "4/4 red Dragon creature token with flying and firebending 4"); + cardType.add(CardType.CREATURE); + color.setRed(true); + subtype.add(SubType.DRAGON); + power = new MageInt(4); + toughness = new MageInt(4); + addAbility(FlyingAbility.getInstance()); + addAbility(new FirebendingAbility(4)); + } + + private DragonFirebendingToken(final DragonFirebendingToken token) { + super(token); + } + + public DragonFirebendingToken copy() { + return new DragonFirebendingToken(this); + } +} diff --git a/Mage/src/main/resources/tokens-database.txt b/Mage/src/main/resources/tokens-database.txt index ce8aa97b8f8..dd6c10c31d5 100644 --- a/Mage/src/main/resources/tokens-database.txt +++ b/Mage/src/main/resources/tokens-database.txt @@ -2897,6 +2897,7 @@ |Generate|TOK:TLA|Clue|3||ClueArtifactToken| |Generate|TOK:TLA|Clue|4||ClueArtifactToken| |Generate|TOK:TLA|Clue|5||ClueArtifactToken| +|Generate|TOK:TLA|Dragon|||DragonFirebendingToken| |Generate|TOK:TLA|Food|1||FoodToken| |Generate|TOK:TLA|Food|2||FoodToken| |Generate|TOK:TLA|Food|3||FoodToken| @@ -2989,4 +2990,4 @@ |Generate|TOK:PL24|Dragon|||DragonToken| # PL25 -|Generate|TOK:PL25|Snake|||SnakeToken| \ No newline at end of file +|Generate|TOK:PL25|Snake|||SnakeToken| From 70ffc77a5b03a8bd4235303a7e4fb6648c89ff13 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 10:27:59 -0500 Subject: [PATCH 03/29] [TLA] Implement Elemental Teachings --- .../src/mage/cards/e/ElementalTeachings.java | 37 ++++++++ Mage.Sets/src/mage/cards/g/GiftsUngiven.java | 73 ++------------- .../src/mage/cards/r/RealmsUncharted.java | 79 ++-------------- .../src/mage/sets/AvatarTheLastAirbender.java | 2 + ...rchLibraryForFourDifferentCardsEffect.java | 92 +++++++++++++++++++ 5 files changed, 143 insertions(+), 140 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/e/ElementalTeachings.java create mode 100644 Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryForFourDifferentCardsEffect.java diff --git a/Mage.Sets/src/mage/cards/e/ElementalTeachings.java b/Mage.Sets/src/mage/cards/e/ElementalTeachings.java new file mode 100644 index 00000000000..b4c1fb32884 --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElementalTeachings.java @@ -0,0 +1,37 @@ +package mage.cards.e; + +import mage.abilities.effects.common.search.SearchLibraryForFourDifferentCardsEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.PutCards; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ElementalTeachings extends CardImpl { + + public ElementalTeachings(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{G}"); + + this.subtype.add(SubType.LESSON); + + // Search your library for up to four land cards with different names and reveal them. An opponent chooses two of those cards. Put the chosen cards into your graveyard and the rest onto the battlefield tapped, then shuffle. + this.getSpellAbility().addEffect(new SearchLibraryForFourDifferentCardsEffect( + StaticFilters.FILTER_CARD_LANDS, PutCards.BATTLEFIELD_TAPPED, false + )); + } + + private ElementalTeachings(final ElementalTeachings card) { + super(card); + } + + @Override + public ElementalTeachings copy() { + return new ElementalTeachings(this); + } +} diff --git a/Mage.Sets/src/mage/cards/g/GiftsUngiven.java b/Mage.Sets/src/mage/cards/g/GiftsUngiven.java index 88b3ff747e2..a50cad504ac 100644 --- a/Mage.Sets/src/mage/cards/g/GiftsUngiven.java +++ b/Mage.Sets/src/mage/cards/g/GiftsUngiven.java @@ -1,20 +1,11 @@ package mage.cards.g; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryForFourDifferentCardsEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.filter.FilterCard; -import mage.game.Game; -import mage.players.Player; -import mage.target.TargetCard; -import mage.target.common.TargetCardInLibrary; -import mage.target.common.TargetCardWithDifferentNameInLibrary; +import mage.constants.PutCards; +import mage.filter.StaticFilters; import mage.target.common.TargetOpponent; import java.util.UUID; @@ -28,7 +19,9 @@ public final class GiftsUngiven extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}"); // Search your library for up to four cards with different names and reveal them. Target opponent chooses two of those cards. Put the chosen cards into your graveyard and the rest into your hand. Then shuffle your library. - this.getSpellAbility().addEffect(new GiftsUngivenEffect()); + this.getSpellAbility().addEffect(new SearchLibraryForFourDifferentCardsEffect( + StaticFilters.FILTER_CARD_CARDS, PutCards.HAND, true + )); this.getSpellAbility().addTarget(new TargetOpponent()); } @@ -41,57 +34,3 @@ public final class GiftsUngiven extends CardImpl { return new GiftsUngiven(this); } } - -class GiftsUngivenEffect extends OneShotEffect { - - private static final FilterCard filter = new FilterCard("cards with different names"); - private static final FilterCard filter2 = new FilterCard("cards to put in graveyard"); - - public GiftsUngivenEffect() { - super(Outcome.DrawCard); - this.staticText = "Search your library for up to four cards with different names and reveal them. " + - "Target opponent chooses two of those cards. Put the chosen cards into your graveyard " + - "and the rest into your hand. Then shuffle"; - } - - private GiftsUngivenEffect(final GiftsUngivenEffect effect) { - super(effect); - } - - @Override - public GiftsUngivenEffect copy() { - return new GiftsUngivenEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - Player opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); - if (player == null || opponent == null) { - return false; - } - TargetCardInLibrary target = new TargetCardWithDifferentNameInLibrary(0, 4, filter); - player.searchLibrary(target, source, game); - Cards cards = new CardsImpl(target.getTargets()); - cards.retainZone(Zone.LIBRARY, game); - if (cards.isEmpty()) { - player.shuffleLibrary(source, game); - } - player.revealCards(source, cards, game); - - if (cards.size() > 2) { - Cards cardsToKeep = new CardsImpl(); - cardsToKeep.addAll(cards); - TargetCard targetDiscard = new TargetCard(2, Zone.LIBRARY, filter2); - if (opponent.choose(Outcome.Discard, cards, targetDiscard, source, game)) { - cardsToKeep.removeIf(targetDiscard.getTargets()::contains); - cards.removeAll(cardsToKeep); - } - player.moveCards(cardsToKeep, Zone.HAND, source, game); - } - - player.moveCards(cards, Zone.GRAVEYARD, source, game); - player.shuffleLibrary(source, game); - return true; - } -} diff --git a/Mage.Sets/src/mage/cards/r/RealmsUncharted.java b/Mage.Sets/src/mage/cards/r/RealmsUncharted.java index 2b7e09fafd6..309c2e9b903 100644 --- a/Mage.Sets/src/mage/cards/r/RealmsUncharted.java +++ b/Mage.Sets/src/mage/cards/r/RealmsUncharted.java @@ -1,22 +1,11 @@ package mage.cards.r; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.search.SearchLibraryForFourDifferentCardsEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.cards.Cards; -import mage.cards.CardsImpl; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.Zone; -import mage.filter.FilterCard; -import mage.filter.common.FilterLandCard; -import mage.game.Game; -import mage.players.Player; -import mage.target.TargetCard; -import mage.target.common.TargetCardInLibrary; -import mage.target.common.TargetCardWithDifferentNameInLibrary; -import mage.target.common.TargetOpponent; +import mage.constants.PutCards; +import mage.filter.StaticFilters; import java.util.UUID; @@ -29,7 +18,9 @@ public final class RealmsUncharted extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{G}"); // Search your library for four land cards with different names and reveal them. An opponent chooses two of those cards. Put the chosen cards into your graveyard and the rest into your hand. Then shuffle your library. - this.getSpellAbility().addEffect(new RealmsUnchartedEffect()); + this.getSpellAbility().addEffect(new SearchLibraryForFourDifferentCardsEffect( + StaticFilters.FILTER_CARD_LANDS, PutCards.HAND, false + )); } private RealmsUncharted(final RealmsUncharted card) { @@ -41,61 +32,3 @@ public final class RealmsUncharted extends CardImpl { return new RealmsUncharted(this); } } - -class RealmsUnchartedEffect extends OneShotEffect { - - private static final FilterCard filter = new FilterLandCard("land cards with different names"); - private static final FilterCard filter2 = new FilterCard("cards to put in graveyard"); - - public RealmsUnchartedEffect() { - super(Outcome.DrawCard); - this.staticText = "Search your library for up to four land cards with different names and reveal them. " + - "An opponent chooses two of those cards. Put the chosen cards into your graveyard " + - "and the rest into your hand. Then shuffle"; - } - - private RealmsUnchartedEffect(final RealmsUnchartedEffect effect) { - super(effect); - } - - @Override - public RealmsUnchartedEffect copy() { - return new RealmsUnchartedEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return false; - } - TargetCardInLibrary targetCards = new TargetCardWithDifferentNameInLibrary(0, 4, filter); - player.searchLibrary(targetCards, source, game); - Cards cards = new CardsImpl(targetCards.getTargets()); - cards.retainZone(Zone.LIBRARY, game); - if (cards.isEmpty()) { - player.shuffleLibrary(source, game); - } - player.revealCards(source, cards, game); - - if (cards.size() > 2) { - TargetOpponent targetOpponent = new TargetOpponent(); - targetOpponent.withNotTarget(true); - player.choose(outcome, targetOpponent, source, game); - Player opponent = game.getPlayer(targetOpponent.getFirstTarget()); - if (opponent != null) { - Cards cardsToKeep = new CardsImpl(cards); - TargetCard targetDiscard = new TargetCard(2, Zone.LIBRARY, filter2); - if (opponent.choose(Outcome.Discard, cards, targetDiscard, source, game)) { - cardsToKeep.removeIf(targetDiscard.getTargets()::contains); - cards.removeAll(cardsToKeep); - } - player.moveCards(cardsToKeep, Zone.HAND, source, game); - } - } - - player.moveCards(cards, Zone.GRAVEYARD, source, game); - player.shuffleLibrary(source, game); - return true; - } -} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index 08950b1fb44..129d6eb50a2 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -132,6 +132,8 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Earthbending Lesson", 176, Rarity.COMMON, mage.cards.e.EarthbendingLesson.class)); cards.add(new SetCardInfo("Earthen Ally", 177, Rarity.RARE, mage.cards.e.EarthenAlly.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Earthen Ally", 377, Rarity.RARE, mage.cards.e.EarthenAlly.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Elemental Teachings", 178, Rarity.RARE, mage.cards.e.ElementalTeachings.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Elemental Teachings", 378, Rarity.RARE, mage.cards.e.ElementalTeachings.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ember Island Production", 48, Rarity.UNCOMMON, mage.cards.e.EmberIslandProduction.class)); cards.add(new SetCardInfo("Energybending", 2, Rarity.UNCOMMON, mage.cards.e.Energybending.class)); cards.add(new SetCardInfo("Enter the Avatar State", 18, Rarity.UNCOMMON, mage.cards.e.EnterTheAvatarState.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryForFourDifferentCardsEffect.java b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryForFourDifferentCardsEffect.java new file mode 100644 index 00000000000..c49f7ff6225 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/effects/common/search/SearchLibraryForFourDifferentCardsEffect.java @@ -0,0 +1,92 @@ +package mage.abilities.effects.common.search; + +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.Cards; +import mage.cards.CardsImpl; +import mage.constants.Outcome; +import mage.constants.PutCards; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.TargetPlayer; +import mage.target.common.TargetCardInLibrary; +import mage.target.common.TargetCardWithDifferentNameInLibrary; +import mage.target.common.TargetOpponent; + +/** + * @author TheElk801 + */ +public class SearchLibraryForFourDifferentCardsEffect extends OneShotEffect { + + private final FilterCard filter; + private final PutCards putCards; + private final boolean useTargetPointer; + private static final FilterCard filter2 = new FilterCard("cards to put in graveyard"); + + public SearchLibraryForFourDifferentCardsEffect(FilterCard filter, PutCards putCards, boolean useTargetPointer) { + super(Outcome.Benefit); + this.filter = filter; + this.putCards = putCards; + this.useTargetPointer = useTargetPointer; + staticText = "search your library for up to four " + filter + + " with different names and reveal them. " + (useTargetPointer ? "Target" : "An") + + " opponent chooses two of those cards. Put the chosen cards into your graveyard and the rest " + + putCards.getMessage(false, false) + ", then shuffle"; + } + + private SearchLibraryForFourDifferentCardsEffect(final SearchLibraryForFourDifferentCardsEffect effect) { + super(effect); + this.filter = effect.filter; + this.putCards = effect.putCards; + this.useTargetPointer = effect.useTargetPointer; + } + + @Override + public SearchLibraryForFourDifferentCardsEffect copy() { + return new SearchLibraryForFourDifferentCardsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + TargetCardInLibrary targetCards = new TargetCardWithDifferentNameInLibrary(0, 4, filter); + player.searchLibrary(targetCards, source, game); + Cards cards = new CardsImpl(targetCards.getTargets()); + cards.retainZone(Zone.LIBRARY, game); + player.revealCards(source, cards, game); + if (cards.isEmpty()) { + player.shuffleLibrary(source, game); + return true; + } + + Cards toGrave = new CardsImpl(); + if (cards.size() > 2) { + Player opponent; + if (useTargetPointer) { + opponent = game.getPlayer(getTargetPointer().getFirst(game, source)); + } else { + TargetPlayer target = new TargetOpponent(true); + player.choose(outcome, target, source, game); + opponent = game.getPlayer(target.getFirstTarget()); + } + if (opponent != null) { + TargetCard targetDiscard = new TargetCard(2, Zone.LIBRARY, filter2); + opponent.choose(Outcome.Discard, cards, targetDiscard, source, game); + toGrave.addAll(targetDiscard.getTargets()); + } + } else { + toGrave.addAll(cards); + } + cards.removeAll(toGrave); + player.moveCards(toGrave, Zone.GRAVEYARD, source, game); + putCards.moveCards(player, cards, source, game); + player.shuffleLibrary(source, game); + return true; + } +} From 31b023a0030e2405a760c2b48139184d2a0c6e31 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 10:32:19 -0500 Subject: [PATCH 04/29] [TLA] Implement Raven Eagle --- Mage.Sets/src/mage/cards/r/RavenEagle.java | 94 +++++++++++++++++++ .../src/mage/sets/AvatarTheLastAirbender.java | 2 + 2 files changed, 96 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/r/RavenEagle.java diff --git a/Mage.Sets/src/mage/cards/r/RavenEagle.java b/Mage.Sets/src/mage/cards/r/RavenEagle.java new file mode 100644 index 00000000000..86a184ae755 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RavenEagle.java @@ -0,0 +1,94 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.DrawNthCardTriggeredAbility; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.game.Game; +import mage.game.permanent.token.ClueArtifactToken; +import mage.players.Player; +import mage.target.common.TargetCardInGraveyard; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RavenEagle extends CardImpl { + + public RavenEagle(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.BIRD); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(2); + this.toughness = new MageInt(3); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever this creature enters or attacks, exile up to one target card from a graveyard. If a creature card is exiled this way, create a Clue token. + Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new RavenEagleEffect()); + ability.addTarget(new TargetCardInGraveyard(0, 1)); + this.addAbility(ability); + + // Whenever you draw your second card each turn, each opponent loses 1 life and you gain 1 life. + ability = new DrawNthCardTriggeredAbility(new LoseLifeOpponentsEffect(1)); + ability.addEffect(new GainLifeEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private RavenEagle(final RavenEagle card) { + super(card); + } + + @Override + public RavenEagle copy() { + return new RavenEagle(this); + } +} + +class RavenEagleEffect extends OneShotEffect { + + RavenEagleEffect() { + super(Outcome.Benefit); + staticText = "exile up to one target card from a graveyard. " + + "If a creature card is exiled this way, create a Clue token"; + } + + private RavenEagleEffect(final RavenEagleEffect effect) { + super(effect); + } + + @Override + public RavenEagleEffect copy() { + return new RavenEagleEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (player == null || card == null) { + return false; + } + boolean isCreature = card.isCreature(game); + player.moveCards(card, Zone.EXILED, source, game); + if (isCreature) { + game.processAction(); + new ClueArtifactToken().putOntoBattlefield(1, game, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index 129d6eb50a2..f4cf823018a 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -272,6 +272,8 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Ran and Shaw", 150, Rarity.RARE, mage.cards.r.RanAndShaw.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ran and Shaw", 325, Rarity.RARE, mage.cards.r.RanAndShaw.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Raucous Audience", 190, Rarity.COMMON, mage.cards.r.RaucousAudience.class)); + cards.add(new SetCardInfo("Raven Eagle", 116, Rarity.RARE, mage.cards.r.RavenEagle.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Raven Eagle", 324, Rarity.RARE, mage.cards.r.RavenEagle.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Razor Rings", 33, Rarity.COMMON, mage.cards.r.RazorRings.class)); cards.add(new SetCardInfo("Realm of Koh", 276, Rarity.RARE, mage.cards.r.RealmOfKoh.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Realm of Koh", 391, Rarity.RARE, mage.cards.r.RealmOfKoh.class, NON_FULL_USE_VARIOUS)); From b5954c90a569826559fe6a1885145a488fcde5dd Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 10:54:19 -0500 Subject: [PATCH 05/29] [TLE] Implement Aang and Katara --- Mage.Sets/src/mage/cards/a/AangAndKatara.java | 64 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 1 + 2 files changed, 65 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/a/AangAndKatara.java diff --git a/Mage.Sets/src/mage/cards/a/AangAndKatara.java b/Mage.Sets/src/mage/cards/a/AangAndKatara.java new file mode 100644 index 00000000000..6b2c1c3f7c2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AangAndKatara.java @@ -0,0 +1,64 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.game.permanent.token.AllyToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AangAndKatara extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent("tapped artifacts and/or creatures you control"); + + static { + filter.add(TappedPredicate.TAPPED); + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate() + )); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter, null); + private static final Hint hint = new ValueHint("Tapped artifacts and creatures you control", xValue); + + public AangAndKatara(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{W}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.AVATAR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Whenever Aang and Katara enter or attack, create X 1/1 white Ally creature tokens, where X is the number of tapped artifacts and/or creatures you control. + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new CreateTokenEffect(new AllyToken(), xValue)) + .setTriggerPhrase("Whenever {this} enter or attack, ").addHint(hint)); + } + + private AangAndKatara(final AangAndKatara card) { + super(card); + } + + @Override + public AangAndKatara copy() { + return new AangAndKatara(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index 7184b565771..e2c1c624d7d 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -26,6 +26,7 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { this.rotationSet = true; this.hasBasicLands = true; + cards.add(new SetCardInfo("Aang and Katara", 69, Rarity.RARE, mage.cards.a.AangAndKatara.class)); cards.add(new SetCardInfo("Aang's Defense", 211, Rarity.COMMON, mage.cards.a.AangsDefense.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aang's Defense", 266, Rarity.COMMON, mage.cards.a.AangsDefense.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Aang, A Lot to Learn", 146, Rarity.UNCOMMON, mage.cards.a.AangALotToLearn.class)); From 88fc3b9045f3b90504443bf1339a82ba3261a0c3 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 10:53:46 -0500 Subject: [PATCH 06/29] [TLE] Implement Appa the Vigilant --- .../src/mage/cards/a/AppaTheVigilant.java | 68 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 1 + 2 files changed, 69 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/a/AppaTheVigilant.java diff --git a/Mage.Sets/src/mage/cards/a/AppaTheVigilant.java b/Mage.Sets/src/mage/cards/a/AppaTheVigilant.java new file mode 100644 index 00000000000..517110654e9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AppaTheVigilant.java @@ -0,0 +1,68 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldThisOrAnotherTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.VigilanceAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AppaTheVigilant extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent(SubType.ALLY, "Ally"); + + public AppaTheVigilant(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{W}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.BISON); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Vigilance + this.addAbility(VigilanceAbility.getInstance()); + + // Whenever Appa or another Ally you control enters, creatures you control get +1/+1 and gain flying and vigilance until end of turn. + Ability ability = new EntersBattlefieldThisOrAnotherTriggeredAbility( + new BoostControlledEffect(1, 1, Duration.EndOfTurn) + .setText("creatures you control get +1/+1"), + filter, false, true + ); + ability.addEffect(new GainAbilityControlledEffect( + FlyingAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE + ).setText("and gain flying")); + ability.addEffect(new GainAbilityControlledEffect( + VigilanceAbility.getInstance(), Duration.EndOfTurn, + StaticFilters.FILTER_PERMANENT_CREATURE + ).setText("and vigilance until end of turn")); + this.addAbility(ability); + } + + private AppaTheVigilant(final AppaTheVigilant card) { + super(card); + } + + @Override + public AppaTheVigilant copy() { + return new AppaTheVigilant(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index e2c1c624d7d..21fd23654c4 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -42,6 +42,7 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Allied Teamwork", 213, Rarity.RARE, mage.cards.a.AlliedTeamwork.class)); cards.add(new SetCardInfo("Appa, Aang's Companion", 214, Rarity.UNCOMMON, mage.cards.a.AppaAangsCompanion.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Appa, Aang's Companion", 268, Rarity.UNCOMMON, mage.cards.a.AppaAangsCompanion.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Appa, the Vigilant", 62, Rarity.RARE, mage.cards.a.AppaTheVigilant.class)); cards.add(new SetCardInfo("Arcane Signet", 315, Rarity.RARE, mage.cards.a.ArcaneSignet.class)); cards.add(new SetCardInfo("Avatar Kyoshi, Earthbender", 130, Rarity.MYTHIC, mage.cards.a.AvatarKyoshiEarthbender.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Avatar Kyoshi, Earthbender", 201, Rarity.MYTHIC, mage.cards.a.AvatarKyoshiEarthbender.class, NON_FULL_USE_VARIOUS)); From d10863b84453c1df7d014d7844cca86316d48e51 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 10:53:55 -0500 Subject: [PATCH 07/29] [TLE] Implement Bumi's Feast Lecture --- .../src/mage/cards/b/BumisFeastLecture.java | 51 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 1 + 2 files changed, 52 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/b/BumisFeastLecture.java diff --git a/Mage.Sets/src/mage/cards/b/BumisFeastLecture.java b/Mage.Sets/src/mage/cards/b/BumisFeastLecture.java new file mode 100644 index 00000000000..f3850984bee --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BumisFeastLecture.java @@ -0,0 +1,51 @@ +package mage.cards.b; + +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.common.FilterControlledPermanent; +import mage.game.permanent.token.FoodToken; +import mage.target.common.TargetControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BumisFeastLecture extends CardImpl { + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount( + new FilterControlledPermanent(SubType.FOOD, "twice the number of Foods you control"), 2 + ); + private static final Hint hint = new ValueHint( + "Foods you control", new PermanentsOnBattlefieldCount(new FilterControlledPermanent(SubType.FOOD)) + ); + + public BumisFeastLecture(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}"); + + this.subtype.add(SubType.LESSON); + + // Create a Food token. Then earthbend X, where X is twice the number of Foods you control. + this.getSpellAbility().addEffect(new CreateTokenEffect(new FoodToken())); + this.getSpellAbility().addEffect(new EarthbendTargetEffect(xValue).concatBy("Then")); + this.getSpellAbility().addTarget(new TargetControlledLandPermanent()); + this.getSpellAbility().addHint(hint); + } + + private BumisFeastLecture(final BumisFeastLecture card) { + super(card); + } + + @Override + public BumisFeastLecture copy() { + return new BumisFeastLecture(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index 21fd23654c4..53067807db6 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -64,6 +64,7 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Brainstorm", 155, Rarity.COMMON, mage.cards.b.Brainstorm.class)); cards.add(new SetCardInfo("Bribery", 10, Rarity.MYTHIC, mage.cards.b.Bribery.class)); cards.add(new SetCardInfo("Brought Back", 1, Rarity.MYTHIC, mage.cards.b.BroughtBack.class)); + cards.add(new SetCardInfo("Bumi's Feast Lecture", 133, Rarity.UNCOMMON, mage.cards.b.BumisFeastLecture.class)); cards.add(new SetCardInfo("Bumi, Eclectic Earthbender", 248, Rarity.RARE, mage.cards.b.BumiEclecticEarthbender.class)); cards.add(new SetCardInfo("Capital Guard", 234, Rarity.COMMON, mage.cards.c.CapitalGuard.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Capital Guard", 277, Rarity.COMMON, mage.cards.c.CapitalGuard.class, NON_FULL_USE_VARIOUS)); From 80f604943d6810e65112278ef29900b2831323e3 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 10:54:08 -0500 Subject: [PATCH 08/29] [TLE] Implement Elephant-Mandrill --- .../src/mage/cards/e/ElephantMandrill.java | 69 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 1 + 2 files changed, 70 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/e/ElephantMandrill.java diff --git a/Mage.Sets/src/mage/cards/e/ElephantMandrill.java b/Mage.Sets/src/mage/cards/e/ElephantMandrill.java new file mode 100644 index 00000000000..1096727695e --- /dev/null +++ b/Mage.Sets/src/mage/cards/e/ElephantMandrill.java @@ -0,0 +1,69 @@ +package mage.cards.e; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.CreateTokenAllEffect; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.keyword.ReachAbility; +import mage.abilities.triggers.BeginningOfCombatTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterArtifactPermanent; +import mage.game.permanent.token.FoodToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ElephantMandrill extends CardImpl { + + private static final FilterPermanent filter = new FilterArtifactPermanent("artifact your opponents control"); + + static { + filter.add(TargetController.OPPONENT.getOwnerPredicate()); + } + + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter); + private static final Hint hint = new ValueHint("Artifacts your opponents control", xValue); + + public ElephantMandrill(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}"); + + this.subtype.add(SubType.ELEPHANT); + this.subtype.add(SubType.MONKEY); + this.power = new MageInt(3); + this.toughness = new MageInt(2); + + // Reach + this.addAbility(ReachAbility.getInstance()); + + // When this creature enters, each player creates a Food token. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new CreateTokenAllEffect(new FoodToken(), TargetController.EACH_PLAYER) + )); + + // At the beginning of combat on your turn, this creature gets +1/+1 until end of turn for each artifact your opponents control. + this.addAbility(new BeginningOfCombatTriggeredAbility( + new BoostSourceEffect(xValue, xValue, Duration.EndOfTurn) + ).addHint(hint)); + } + + private ElephantMandrill(final ElephantMandrill card) { + super(card); + } + + @Override + public ElephantMandrill copy() { + return new ElephantMandrill(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index 53067807db6..56887aa2857 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -104,6 +104,7 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Eel-Hounds", 250, Rarity.UNCOMMON, mage.cards.e.EelHounds.class)); cards.add(new SetCardInfo("Eladamri's Call", 48, Rarity.MYTHIC, mage.cards.e.EladamrisCall.class)); cards.add(new SetCardInfo("Elemental Bond", 40, Rarity.MYTHIC, mage.cards.e.ElementalBond.class)); + cards.add(new SetCardInfo("Elephant-Mandrill", 138, Rarity.UNCOMMON, mage.cards.e.ElephantMandrill.class)); cards.add(new SetCardInfo("Elephant-Rat", 228, Rarity.COMMON, mage.cards.e.ElephantRat.class)); cards.add(new SetCardInfo("Empty City Ruse", 3, Rarity.MYTHIC, mage.cards.e.EmptyCityRuse.class)); cards.add(new SetCardInfo("Enlightened Tutor", 305, Rarity.RARE, mage.cards.e.EnlightenedTutor.class)); From 76605e2a9dbe432234d308629077ae4c86ade336 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 10:56:03 -0500 Subject: [PATCH 09/29] [TLE] Implement Freedom Fighter Recruit --- .../mage/cards/f/FreedomFighterRecruit.java | 41 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 1 + 2 files changed, 42 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/FreedomFighterRecruit.java diff --git a/Mage.Sets/src/mage/cards/f/FreedomFighterRecruit.java b/Mage.Sets/src/mage/cards/f/FreedomFighterRecruit.java new file mode 100644 index 00000000000..9ea0d9c8c2c --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FreedomFighterRecruit.java @@ -0,0 +1,41 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.CreaturesYouControlCount; +import mage.abilities.effects.common.continuous.SetBasePowerSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FreedomFighterRecruit extends CardImpl { + + public FreedomFighterRecruit(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.REBEL); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(0); + this.toughness = new MageInt(2); + + // Freedom Fighter Recruit's power is equal to the number of creatures you control. + this.addAbility(new SimpleStaticAbility(Zone.ALL, new SetBasePowerSourceEffect(CreaturesYouControlCount.PLURAL))); + } + + private FreedomFighterRecruit(final FreedomFighterRecruit card) { + super(card); + } + + @Override + public FreedomFighterRecruit copy() { + return new FreedomFighterRecruit(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index 56887aa2857..e73f892bb6c 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -135,6 +135,7 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Founding of Omashu", 116, Rarity.UNCOMMON, mage.cards.f.FoundingOfOmashu.class)); cards.add(new SetCardInfo("Frantic Confrontation", 117, Rarity.UNCOMMON, mage.cards.f.FranticConfrontation.class)); cards.add(new SetCardInfo("Frantic Search", 159, Rarity.COMMON, mage.cards.f.FranticSearch.class)); + cards.add(new SetCardInfo("Freedom Fighter Recruit", 118, Rarity.COMMON, mage.cards.f.FreedomFighterRecruit.class)); cards.add(new SetCardInfo("Frog-Squirrels", 251, Rarity.COMMON, mage.cards.f.FrogSquirrels.class)); cards.add(new SetCardInfo("Gamble", 312, Rarity.RARE, mage.cards.g.Gamble.class)); cards.add(new SetCardInfo("Gilacorn", 231, Rarity.COMMON, mage.cards.g.Gilacorn.class)); From 67dc57122434780c9cf375cec1a31ebc86f87c07 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 10:56:51 -0500 Subject: [PATCH 10/29] [TLE] Implement Giant Fly --- Mage.Sets/src/mage/cards/g/GiantFly.java | 45 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 1 + 2 files changed, 46 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/GiantFly.java diff --git a/Mage.Sets/src/mage/cards/g/GiantFly.java b/Mage.Sets/src/mage/cards/g/GiantFly.java new file mode 100644 index 00000000000..2ef7ffb7fa2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/GiantFly.java @@ -0,0 +1,45 @@ +package mage.cards.g; + +import mage.MageInt; +import mage.abilities.common.SacrificePermanentTriggeredAbility; +import mage.abilities.effects.common.continuous.BoostSourceEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class GiantFly extends CardImpl { + + public GiantFly(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}"); + + this.subtype.add(SubType.INSECT); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever you sacrifice another permanent, this creature gets +1/+0 until end of turn. + this.addAbility(new SacrificePermanentTriggeredAbility( + new BoostSourceEffect(1, 0, Duration.EndOfTurn), StaticFilters.FILTER_ANOTHER_PERMANENT + )); + } + + private GiantFly(final GiantFly card) { + super(card); + } + + @Override + public GiantFly copy() { + return new GiantFly(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index e73f892bb6c..5205ede610b 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -138,6 +138,7 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Freedom Fighter Recruit", 118, Rarity.COMMON, mage.cards.f.FreedomFighterRecruit.class)); cards.add(new SetCardInfo("Frog-Squirrels", 251, Rarity.COMMON, mage.cards.f.FrogSquirrels.class)); cards.add(new SetCardInfo("Gamble", 312, Rarity.RARE, mage.cards.g.Gamble.class)); + cards.add(new SetCardInfo("Giant Fly", 107, Rarity.COMMON, mage.cards.g.GiantFly.class)); cards.add(new SetCardInfo("Gilacorn", 231, Rarity.COMMON, mage.cards.g.Gilacorn.class)); cards.add(new SetCardInfo("Heartbeat of Spring", 42, Rarity.MYTHIC, mage.cards.h.HeartbeatOfSpring.class)); cards.add(new SetCardInfo("Heroic Intervention", 43, Rarity.MYTHIC, mage.cards.h.HeroicIntervention.class)); From 79afaa0fbbe99c76a91afdbb9deab0708251d3f8 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 10:59:44 -0500 Subject: [PATCH 11/29] [TLE] Implement Hook Swords --- Mage.Sets/src/mage/cards/h/HookSwords.java | 58 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 1 + 2 files changed, 59 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/h/HookSwords.java diff --git a/Mage.Sets/src/mage/cards/h/HookSwords.java b/Mage.Sets/src/mage/cards/h/HookSwords.java new file mode 100644 index 00000000000..18ad91d00d8 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HookSwords.java @@ -0,0 +1,58 @@ +package mage.cards.h; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.common.MyTurnCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.effects.common.CreateTokenAttachSourceEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.game.permanent.token.AllyToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HookSwords extends CardImpl { + + public HookSwords(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{R/W}"); + + this.subtype.add(SubType.EQUIPMENT); + + // When this Equipment enters, create a 1/1 white Ally creature token, then attach this Equipment to it. + this.addAbility(new EntersBattlefieldTriggeredAbility(new CreateTokenAttachSourceEffect(new AllyToken()))); + + // During your turn, equipped creature gets +1/+1 and has first strike. + Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( + new BoostEquippedEffect(1, 1), MyTurnCondition.instance, + "during your turn, equipped creature gets +1/+1" + )); + ability.addEffect(new ConditionalContinuousEffect( + new GainAbilityAttachedEffect(FirstStrikeAbility.getInstance(), AttachmentType.EQUIPMENT), + MyTurnCondition.instance, "and has first strike" + )); + this.addAbility(ability); + + // Equip {3} + this.addAbility(new EquipAbility(3)); + } + + private HookSwords(final HookSwords card) { + super(card); + } + + @Override + public HookSwords copy() { + return new HookSwords(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index 5205ede610b..74c8b7e8c47 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -143,6 +143,7 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Heartbeat of Spring", 42, Rarity.MYTHIC, mage.cards.h.HeartbeatOfSpring.class)); cards.add(new SetCardInfo("Heroic Intervention", 43, Rarity.MYTHIC, mage.cards.h.HeroicIntervention.class)); cards.add(new SetCardInfo("Hippo-Cows", 252, Rarity.COMMON, mage.cards.h.HippoCows.class)); + cards.add(new SetCardInfo("Hook Swords", 147, Rarity.UNCOMMON, mage.cards.h.HookSwords.class)); cards.add(new SetCardInfo("Humble Defector", 30, Rarity.MYTHIC, mage.cards.h.HumbleDefector.class)); cards.add(new SetCardInfo("Imprisoned in the Moon", 14, Rarity.MYTHIC, mage.cards.i.ImprisonedInTheMoon.class)); cards.add(new SetCardInfo("Inspired Insurgent", 77, Rarity.COMMON, mage.cards.i.InspiredInsurgent.class)); From 4e2c4ec1f0f0dce54076fd8f680134a5c7b59e15 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 11:09:22 -0500 Subject: [PATCH 12/29] [TLE] Implement Lo and Li, Royal Advisors --- .../mage/cards/l/LoAndLiRoyalAdvisors.java | 86 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 2 + 2 files changed, 88 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/l/LoAndLiRoyalAdvisors.java diff --git a/Mage.Sets/src/mage/cards/l/LoAndLiRoyalAdvisors.java b/Mage.Sets/src/mage/cards/l/LoAndLiRoyalAdvisors.java new file mode 100644 index 00000000000..e81990cf725 --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LoAndLiRoyalAdvisors.java @@ -0,0 +1,86 @@ +package mage.cards.l; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.abilities.effects.common.counter.AddCountersAllEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LoAndLiRoyalAdvisors extends CardImpl { + + public LoAndLiRoyalAdvisors(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{B}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ADVISOR); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever an opponent discards a card or mills one or more cards, put a +1/+1 counter on each Advisor you control. + this.addAbility(new LoAndLiRoyalAdvisorsTriggeredAbility()); + + // {2}{U/B}: Target player mills four cards. + Ability ability = new SimpleActivatedAbility(new MillCardsTargetEffect(4), new ManaCostsImpl<>("{2}{U/B}")); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + } + + private LoAndLiRoyalAdvisors(final LoAndLiRoyalAdvisors card) { + super(card); + } + + @Override + public LoAndLiRoyalAdvisors copy() { + return new LoAndLiRoyalAdvisors(this); + } +} + +class LoAndLiRoyalAdvisorsTriggeredAbility extends TriggeredAbilityImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.ADVISOR); + + LoAndLiRoyalAdvisorsTriggeredAbility() { + super(Zone.BATTLEFIELD, new AddCountersAllEffect(CounterType.P1P1.createInstance(), filter)); + setTriggerPhrase("Whenever an opponent discards a card or mills one or more cards, "); + } + + private LoAndLiRoyalAdvisorsTriggeredAbility(final LoAndLiRoyalAdvisorsTriggeredAbility ability) { + super(ability); + } + + @Override + public LoAndLiRoyalAdvisorsTriggeredAbility copy() { + return new LoAndLiRoyalAdvisorsTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DISCARDED_CARD + || event.getType() == GameEvent.EventType.MILLED_CARDS_BATCH_FOR_ONE_PLAYER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + return game.getOpponents(getControllerId()).contains(event.getPlayerId()); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index 74c8b7e8c47..872207d94ea 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -170,6 +170,8 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Lightning Bolt", 32, Rarity.MYTHIC, mage.cards.l.LightningBolt.class)); cards.add(new SetCardInfo("Lion Vulture", 232, Rarity.RARE, mage.cards.l.LionVulture.class)); cards.add(new SetCardInfo("Lita, Mechanical Engineer", 4, Rarity.MYTHIC, mage.cards.l.LitaMechanicalEngineer.class)); + cards.add(new SetCardInfo("Lo and Li, Royal Advisors", 108, Rarity.RARE, mage.cards.l.LoAndLiRoyalAdvisors.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lo and Li, Royal Advisors", 189, Rarity.RARE, mage.cards.l.LoAndLiRoyalAdvisors.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Longshot, Rebel Bowman", 120, Rarity.UNCOMMON, mage.cards.l.LongshotRebelBowman.class)); cards.add(new SetCardInfo("Lost in the Spirit World", 224, Rarity.UNCOMMON, mage.cards.l.LostInTheSpiritWorld.class)); cards.add(new SetCardInfo("Loyal Fire Sage", 242, Rarity.UNCOMMON, mage.cards.l.LoyalFireSage.class)); From 6b5fb2d4c28c9caf5b0de2d860b9476976479fae Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 14:56:57 -0500 Subject: [PATCH 13/29] [TLA] Implement Destined Confrontation --- .../mage/cards/d/DestinedConfrontation.java | 122 ++++++++++++++++++ .../src/mage/sets/AvatarTheLastAirbender.java | 1 + 2 files changed, 123 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/d/DestinedConfrontation.java diff --git a/Mage.Sets/src/mage/cards/d/DestinedConfrontation.java b/Mage.Sets/src/mage/cards/d/DestinedConfrontation.java new file mode 100644 index 00000000000..2bf1bb8d80d --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DestinedConfrontation.java @@ -0,0 +1,122 @@ +package mage.cards.d; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.filter.FilterPermanent; +import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DestinedConfrontation extends CardImpl { + + public DestinedConfrontation(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{W}{W}"); + + // Each player chooses any number of creatures they control with total power 4 or less, then sacrifices all other creatures they control. + this.getSpellAbility().addEffect(new DestinedConfrontationEffect()); + } + + private DestinedConfrontation(final DestinedConfrontation card) { + super(card); + } + + @Override + public DestinedConfrontation copy() { + return new DestinedConfrontation(this); + } +} + +class DestinedConfrontationEffect extends OneShotEffect { + + DestinedConfrontationEffect() { + super(Outcome.Benefit); + staticText = "each player chooses any number of creatures they control " + + "with total power 4 or less, then sacrifices all other creatures they control"; + } + + private DestinedConfrontationEffect(final DestinedConfrontationEffect effect) { + super(effect); + } + + @Override + public DestinedConfrontationEffect copy() { + return new DestinedConfrontationEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Set permanents = new HashSet<>(); + for (UUID playerId : game.getState().getPlayersInRange(source.getControllerId(), game)) { + Player player = game.getPlayer(playerId); + if (player == null) { + continue; + } + TargetPermanent target = new DestinedConfrontationTarget(); + player.choose(outcome, target, source, game); + permanents.addAll(target.getTargets()); + } + for (Permanent permanent : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_PERMANENT_CREATURE, source.getSourceId(), source, game + )) { + if (!permanents.contains(permanent.getId())) { + permanent.sacrifice(source, game); + } + } + return true; + } +} + +class DestinedConfrontationTarget extends TargetPermanent { + + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("creatures you control with total power 4 or less"); + + DestinedConfrontationTarget() { + super(0, Integer.MAX_VALUE, filter, true); + } + + private DestinedConfrontationTarget(final DestinedConfrontationTarget target) { + super(target); + } + + @Override + public DestinedConfrontationTarget copy() { + return new DestinedConfrontationTarget(this); + } + + @Override + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { + return false; + } + Permanent permanent = game.getPermanent(id); + if (permanent == null) { + return false; + } + return this + .getTargets() + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .map(MageObject::getPower) + .mapToInt(MageInt::getValue) + .sum() + permanent.getPower().getValue() <= 4; + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index f4cf823018a..2ae5606866e 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -115,6 +115,7 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Day of Black Sun", 94, Rarity.RARE, mage.cards.d.DayOfBlackSun.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Deadly Precision", 95, Rarity.COMMON, mage.cards.d.DeadlyPrecision.class)); cards.add(new SetCardInfo("Deserter's Disciple", 131, Rarity.COMMON, mage.cards.d.DesertersDisciple.class)); + cards.add(new SetCardInfo("Destined Confrontation", 15, Rarity.UNCOMMON, mage.cards.d.DestinedConfrontation.class)); cards.add(new SetCardInfo("Diligent Zookeeper", 171, Rarity.RARE, mage.cards.d.DiligentZookeeper.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Diligent Zookeeper", 327, Rarity.RARE, mage.cards.d.DiligentZookeeper.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Dragonfly Swarm", 215, Rarity.UNCOMMON, mage.cards.d.DragonflySwarm.class)); From a59d57ae931dc8254fc377901c0b77aa93873e81 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 15:04:56 -0500 Subject: [PATCH 14/29] [TLA] Implement Phoenix Fleet Airship --- .../src/mage/cards/p/PhoenixFleetAirship.java | 97 +++++++++++++++++++ .../src/mage/sets/AvatarTheLastAirbender.java | 2 + 2 files changed, 99 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/p/PhoenixFleetAirship.java diff --git a/Mage.Sets/src/mage/cards/p/PhoenixFleetAirship.java b/Mage.Sets/src/mage/cards/p/PhoenixFleetAirship.java new file mode 100644 index 00000000000..564c6c5556c --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PhoenixFleetAirship.java @@ -0,0 +1,97 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalContinuousEffect; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.dynamicvalue.common.PermanentsSacrificedThisTurnCount; +import mage.abilities.effects.CreateTokenCopySourceEffect; +import mage.abilities.effects.common.continuous.AddCardTypeSourceEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.abilities.hint.common.PermanentsSacrificedThisTurnHint; +import mage.abilities.keyword.CrewAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.game.Game; +import mage.watchers.common.PermanentsSacrificedWatcher; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PhoenixFleetAirship extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(); + + static { + filter.add(new NamePredicate("Phoenix Fleet Airship")); + } + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition(filter, ComparisonType.MORE_THAN, 7); + private static final Hint hint = new ValueHint( + "Permanents you control named Phoenix Fleet Airship", new PermanentsOnBattlefieldCount(filter) + ); + + public PhoenixFleetAirship(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}{B}{B}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // At the beginning of your end step, if you sacrificed a permanent this turn, create a token that's a copy of this Vehicle. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new CreateTokenCopySourceEffect()) + .withInterveningIf(PhoenixFleetAirshipCondition.instance) + .addHint(PermanentsSacrificedThisTurnHint.instance), new PermanentsSacrificedWatcher()); + + // As long as you control eight or more permanents named Phoenix Fleet Airship, this Vehicle is an artifact creature. + this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( + new AddCardTypeSourceEffect(Duration.WhileOnBattlefield, CardType.ARTIFACT, CardType.CREATURE), + condition, "as long as you control eight or more permanents " + + "named Phoenix Fleet Airship, this Vehicle is an artifact creature" + )).addHint(hint)); + + // Crew 1 + this.addAbility(new CrewAbility(1)); + } + + private PhoenixFleetAirship(final PhoenixFleetAirship card) { + super(card); + } + + @Override + public PhoenixFleetAirship copy() { + return new PhoenixFleetAirship(this); + } +} + +enum PhoenixFleetAirshipCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return PermanentsSacrificedThisTurnCount.instance.calculate(game, source, null) > 0; + } + + @Override + public String toString() { + return "you sacrificed a permanent this turn"; + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index 2ae5606866e..59db5af2be7 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -259,6 +259,8 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Ozai, the Phoenix King", 311, Rarity.MYTHIC, mage.cards.o.OzaiThePhoenixKing.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ozai, the Phoenix King", 335, Rarity.MYTHIC, mage.cards.o.OzaiThePhoenixKing.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Path to Redemption", 31, Rarity.COMMON, mage.cards.p.PathToRedemption.class)); + cards.add(new SetCardInfo("Phoenix Fleet Airship", 114, Rarity.MYTHIC, mage.cards.p.PhoenixFleetAirship.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Phoenix Fleet Airship", 323, Rarity.MYTHIC, mage.cards.p.PhoenixFleetAirship.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Pillar Launch", 189, Rarity.COMMON, mage.cards.p.PillarLaunch.class)); cards.add(new SetCardInfo("Pirate Peddlers", 115, Rarity.COMMON, mage.cards.p.PiratePeddlers.class)); cards.add(new SetCardInfo("Plains", 282, Rarity.LAND, mage.cards.basiclands.Plains.class, NON_FULL_USE_VARIOUS)); From 2fd0065e7eeaf1b10af2bd293a361b4edf2129cb Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 15:07:44 -0500 Subject: [PATCH 15/29] [TLA] Implement Price of Freedom --- .../src/mage/cards/p/PriceOfFreedom.java | 54 +++++++++++++++++++ .../src/mage/sets/AvatarTheLastAirbender.java | 1 + 2 files changed, 55 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/p/PriceOfFreedom.java diff --git a/Mage.Sets/src/mage/cards/p/PriceOfFreedom.java b/Mage.Sets/src/mage/cards/p/PriceOfFreedom.java new file mode 100644 index 00000000000..a1fcab1bed4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PriceOfFreedom.java @@ -0,0 +1,54 @@ +package mage.cards.p; + +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.search.SearchLibraryPutInPlayTargetControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PriceOfFreedom extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("artifact or land an opponent controls"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.LAND.getPredicate() + )); + filter.add(TargetController.OPPONENT.getOwnerPredicate()); + } + + public PriceOfFreedom(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{R}"); + + this.subtype.add(SubType.LESSON); + + // Destroy target artifact or land an opponent controls. Its controller may search their library for a basic land card, put it onto the battlefield tapped, then shuffle. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addEffect(new SearchLibraryPutInPlayTargetControllerEffect(true)); + + // Draw a card. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1).concatBy("
")); + } + + private PriceOfFreedom(final PriceOfFreedom card) { + super(card); + } + + @Override + public PriceOfFreedom copy() { + return new PriceOfFreedom(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index 59db5af2be7..835b6cd76cc 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -270,6 +270,7 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Planetarium of Wan Shi Tong", 385, Rarity.MYTHIC, mage.cards.p.PlanetariumOfWanShiTong.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Platypus-Bear", 236, Rarity.COMMON, mage.cards.p.PlatypusBear.class)); cards.add(new SetCardInfo("Pretending Poxbearers", 237, Rarity.COMMON, mage.cards.p.PretendingPoxbearers.class)); + cards.add(new SetCardInfo("Price of Freedom", 149, Rarity.UNCOMMON, mage.cards.p.PriceOfFreedom.class)); cards.add(new SetCardInfo("Professor Zei, Anthropologist", 238, Rarity.UNCOMMON, mage.cards.p.ProfessorZeiAnthropologist.class)); cards.add(new SetCardInfo("Rabaroo Troop", 32, Rarity.COMMON, mage.cards.r.RabarooTroop.class)); cards.add(new SetCardInfo("Ran and Shaw", 150, Rarity.RARE, mage.cards.r.RanAndShaw.class, NON_FULL_USE_VARIOUS)); From 7322bf784f273adde564edd6f1f0bb6654e76e80 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 15:13:55 -0500 Subject: [PATCH 16/29] [TLA] Implement Sparring Dummy --- Mage.Sets/src/mage/cards/s/SparringDummy.java | 92 +++++++++++++++++++ .../src/mage/sets/AvatarTheLastAirbender.java | 1 + 2 files changed, 93 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/SparringDummy.java diff --git a/Mage.Sets/src/mage/cards/s/SparringDummy.java b/Mage.Sets/src/mage/cards/s/SparringDummy.java new file mode 100644 index 00000000000..f1aea71cad2 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SparringDummy.java @@ -0,0 +1,92 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.keyword.DefenderAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.cards.Cards; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.TargetImpl; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SparringDummy extends CardImpl { + + public SparringDummy(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{G}"); + + this.subtype.add(SubType.SCARECROW); + this.power = new MageInt(1); + this.toughness = new MageInt(3); + + // Defender + this.addAbility(DefenderAbility.getInstance()); + + // {T}: Mill a card. You may put a land card milled this way into your hand. You gain 2 life if a Lesson card is milled this way. + this.addAbility(new SimpleActivatedAbility(new SparringDummyEffect(), new TapSourceCost())); + } + + private SparringDummy(final SparringDummy card) { + super(card); + } + + @Override + public SparringDummy copy() { + return new SparringDummy(this); + } +} + +class SparringDummyEffect extends OneShotEffect { + + private static final FilterCard filter = new FilterCard(SubType.LESSON); + + SparringDummyEffect() { + super(Outcome.Benefit); + staticText = "mill a card. You may put a land card milled this way into your hand. " + + "You gain 2 life if a Lesson card is milled this way"; + } + + private SparringDummyEffect(final SparringDummyEffect effect) { + super(effect); + } + + @Override + public SparringDummyEffect copy() { + return new SparringDummyEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + Cards cards = player.millCards(1, source, game); + TargetCard target = new TargetCard(0, 1, Zone.ALL, StaticFilters.FILTER_CARD_LAND); + player.choose(Outcome.DrawCard, cards, target, source, game); + Optional.ofNullable(target) + .map(TargetImpl::getFirstTarget) + .map(game::getCard) + .ifPresent(card -> player.moveCards(card, Zone.HAND, source, game)); + if (cards.count(filter, game) > 0) { + player.gainLife(2, game, source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index 835b6cd76cc..fcb7c58b3c6 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -311,6 +311,7 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Sozin's Comet", 154, Rarity.MYTHIC, mage.cards.s.SozinsComet.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sozin's Comet", 309, Rarity.MYTHIC, mage.cards.s.SozinsComet.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sozin's Comet", 332, Rarity.MYTHIC, mage.cards.s.SozinsComet.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Sparring Dummy", 197, Rarity.UNCOMMON, mage.cards.s.SparringDummy.class)); cards.add(new SetCardInfo("Suki, Courageous Rescuer", 368, Rarity.RARE, mage.cards.s.SukiCourageousRescuer.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Suki, Courageous Rescuer", 37, Rarity.RARE, mage.cards.s.SukiCourageousRescuer.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Suki, Kyoshi Warrior", 243, Rarity.UNCOMMON, mage.cards.s.SukiKyoshiWarrior.class)); From 6c45ec6004aebde72d7a8080a4ef2217a5dab83d Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 15:30:39 -0500 Subject: [PATCH 17/29] [TLE] Implement Chong and Lily, Nomads --- .../src/mage/cards/c/ChongAndLilyNomads.java | 107 ++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 2 + 2 files changed, 109 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/c/ChongAndLilyNomads.java diff --git a/Mage.Sets/src/mage/cards/c/ChongAndLilyNomads.java b/Mage.Sets/src/mage/cards/c/ChongAndLilyNomads.java new file mode 100644 index 00000000000..817564189f5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ChongAndLilyNomads.java @@ -0,0 +1,107 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.StaticValue; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.game.Game; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ChongAndLilyNomads extends CardImpl { + + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.BARD, "Bards you control"); + + public ChongAndLilyNomads(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.BARD); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever one or more Bards you control attack, choose one -- + // * Put a lore counter on each of any number of target Sagas you control. + Ability ability = new AttacksWithCreaturesTriggeredAbility( + new AddCountersTargetEffect(CounterType.LORE.createInstance()), 1, filter + ); + ability.addTarget(new TargetPermanent(0, Integer.MAX_VALUE, ChongAndLilyNomadsValue.getFilter())); + + // * Creatures you control get +1/+0 until end of turn for each lore counter among Sagas you control. + ability.addMode(new Mode(new BoostControlledEffect( + ChongAndLilyNomadsValue.instance, StaticValue.get(0), Duration.WhileOnBattlefield + ))); + this.addAbility(ability.addHint(ChongAndLilyNomadsValue.getHint())); + } + + private ChongAndLilyNomads(final ChongAndLilyNomads card) { + super(card); + } + + @Override + public ChongAndLilyNomads copy() { + return new ChongAndLilyNomads(this); + } +} + +enum ChongAndLilyNomadsValue implements DynamicValue { + instance; + private static final FilterPermanent filter = new FilterControlledPermanent(SubType.SAGA, "Sagas you control"); + + public static FilterPermanent getFilter() { + return filter; + } + + private static final Hint hint = new ValueHint("Lore counters among Sagas you control", instance); + + public static Hint getHint() { + return hint; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return game + .getBattlefield() + .getActivePermanents(filter, sourceAbility.getControllerId(), sourceAbility, game) + .stream() + .mapToInt(permanent -> permanent.getCounters(game).getCount(CounterType.LORE)) + .sum(); + } + + @Override + public ChongAndLilyNomadsValue copy() { + return this; + } + + @Override + public String getMessage() { + return "lore counter among Sagas you control"; + } + + @Override + public String toString() { + return "1"; + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index 872207d94ea..7c9fc5fab01 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -72,6 +72,8 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Cathartic Reunion", 164, Rarity.COMMON, mage.cards.c.CatharticReunion.class)); cards.add(new SetCardInfo("Chakra Meditation", 179, Rarity.RARE, mage.cards.c.ChakraMeditation.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Chakra Meditation", 91, Rarity.RARE, mage.cards.c.ChakraMeditation.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chong and Lily, Nomads", 113, Rarity.RARE, mage.cards.c.ChongAndLilyNomads.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Chong and Lily, Nomads", 192, Rarity.RARE, mage.cards.c.ChongAndLilyNomads.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Cityscape Leveler", 53, Rarity.MYTHIC, mage.cards.c.CityscapeLeveler.class)); cards.add(new SetCardInfo("Clone Legion", 12, Rarity.MYTHIC, mage.cards.c.CloneLegion.class)); cards.add(new SetCardInfo("Clone", 11, Rarity.MYTHIC, mage.cards.c.Clone.class)); From ad61edd559f9c78f44943da41bd542903b438a7a Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 15:30:52 -0500 Subject: [PATCH 18/29] [TLE] Implement Kyoshi Warrior Exemplars --- .../mage/cards/k/KyoshiWarriorExemplars.java | 51 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 1 + 2 files changed, 52 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/k/KyoshiWarriorExemplars.java diff --git a/Mage.Sets/src/mage/cards/k/KyoshiWarriorExemplars.java b/Mage.Sets/src/mage/cards/k/KyoshiWarriorExemplars.java new file mode 100644 index 00000000000..f6c4da33ceb --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KyoshiWarriorExemplars.java @@ -0,0 +1,51 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.hint.common.LandsYouControlHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.filter.common.FilterControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class KyoshiWarriorExemplars extends CardImpl { + + private static final Condition condition = new PermanentsOnTheBattlefieldCondition( + new FilterControlledLandPermanent("you control eight or more lands"), ComparisonType.MORE_THAN, 7 + ); + + public KyoshiWarriorExemplars(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(4); + this.toughness = new MageInt(3); + + // Whenever this creature attacks, if you control eight or more lands, creatures you control get +2/+2 until end of turn. + this.addAbility(new AttacksTriggeredAbility( + new BoostControlledEffect(2, 2, Duration.EndOfTurn) + ).withInterveningIf(condition).addHint(LandsYouControlHint.instance)); + } + + private KyoshiWarriorExemplars(final KyoshiWarriorExemplars card) { + super(card); + } + + @Override + public KyoshiWarriorExemplars copy() { + return new KyoshiWarriorExemplars(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index 7c9fc5fab01..2973462005b 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -168,6 +168,7 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Koma, Cosmos Serpent", 51, Rarity.MYTHIC, mage.cards.k.KomaCosmosSerpent.class)); cards.add(new SetCardInfo("Komodo Rhino", 241, Rarity.COMMON, mage.cards.k.KomodoRhino.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Komodo Rhino", 283, Rarity.COMMON, mage.cards.k.KomodoRhino.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Kyoshi Warrior Exemplars", 140, Rarity.UNCOMMON, mage.cards.k.KyoshiWarriorExemplars.class)); cards.add(new SetCardInfo("Kyoshi Warrior Guard", 216, Rarity.COMMON, mage.cards.k.KyoshiWarriorGuard.class)); cards.add(new SetCardInfo("Lightning Bolt", 32, Rarity.MYTHIC, mage.cards.l.LightningBolt.class)); cards.add(new SetCardInfo("Lion Vulture", 232, Rarity.RARE, mage.cards.l.LionVulture.class)); From dc458681cc14a1350e8332a9e1aa1d78bb5c2a87 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 15:35:54 -0500 Subject: [PATCH 19/29] [TLE] Implement Lost in Memories --- .../src/mage/cards/l/LostInMemories.java | 95 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 2 + 2 files changed, 97 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/l/LostInMemories.java diff --git a/Mage.Sets/src/mage/cards/l/LostInMemories.java b/Mage.Sets/src/mage/cards/l/LostInMemories.java new file mode 100644 index 00000000000..cf363c9638a --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LostInMemories.java @@ -0,0 +1,95 @@ +package mage.cards.l; + +import mage.abilities.Ability; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.FlashAbility; +import mage.abilities.keyword.FlashbackAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LostInMemories extends CardImpl { + + public LostInMemories(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{R}"); + + this.subtype.add(SubType.AURA); + + // Flash + this.addAbility(FlashAbility.getInstance()); + + // Enchant creature you control + TargetPermanent auraTarget = new TargetControlledCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // Enchanted creature gets +1/+1 and has "Whenever this creature deals combat damage to a player, target instant or sorcery card in your graveyard gains flashback until end of turn. The flashback cost is equal to its mana cost." + TriggeredAbility triggeredAbility = new DealsCombatDamageToAPlayerTriggeredAbility(new LostInMemoriesEffect()); + triggeredAbility.addTarget(new TargetCardInYourGraveyard(StaticFilters.FILTER_CARD_INSTANT_OR_SORCERY)); + Ability ability = new SimpleStaticAbility(new BoostEnchantedEffect(1, 1)); + ability.addEffect(new GainAbilityAttachedEffect(triggeredAbility, AttachmentType.AURA) + .setText("and has \"Whenever this creature deals combat damage to a player, " + + "target instant or sorcery card in your graveyard gains flashback until end of turn. " + + "The flashback cost is equal to its mana cost.\"")); + this.addAbility(ability); + } + + private LostInMemories(final LostInMemories card) { + super(card); + } + + @Override + public LostInMemories copy() { + return new LostInMemories(this); + } +} + +class LostInMemoriesEffect extends ContinuousEffectImpl { + + LostInMemoriesEffect() { + super(Duration.EndOfTurn, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + this.staticText = "target instant or sorcery card in your graveyard gains flashback until end of turn. " + + "The flashback cost is equal to its mana cost"; + } + + private LostInMemoriesEffect(final LostInMemoriesEffect effect) { + super(effect); + } + + @Override + public LostInMemoriesEffect copy() { + return new LostInMemoriesEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Card card = game.getCard(getTargetPointer().getFirst(game, source)); + if (card == null) { + return false; + } + FlashbackAbility ability = new FlashbackAbility(card, card.getManaCost()); + ability.setSourceId(card.getId()); + ability.setControllerId(card.getOwnerId()); + game.getState().addOtherAbility(card, ability); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index 2973462005b..2dfdcfa2a4a 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -176,6 +176,8 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Lo and Li, Royal Advisors", 108, Rarity.RARE, mage.cards.l.LoAndLiRoyalAdvisors.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lo and Li, Royal Advisors", 189, Rarity.RARE, mage.cards.l.LoAndLiRoyalAdvisors.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Longshot, Rebel Bowman", 120, Rarity.UNCOMMON, mage.cards.l.LongshotRebelBowman.class)); + cards.add(new SetCardInfo("Lost in Memories", 121, Rarity.RARE, mage.cards.l.LostInMemories.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Lost in Memories", 195, Rarity.RARE, mage.cards.l.LostInMemories.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Lost in the Spirit World", 224, Rarity.UNCOMMON, mage.cards.l.LostInTheSpiritWorld.class)); cards.add(new SetCardInfo("Loyal Fire Sage", 242, Rarity.UNCOMMON, mage.cards.l.LoyalFireSage.class)); cards.add(new SetCardInfo("Mai and Zuko", 68, Rarity.RARE, mage.cards.m.MaiAndZuko.class)); From 1447f467c305b0137269e803fd326339f7ff24dc Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 15:39:22 -0500 Subject: [PATCH 20/29] [TLE] Implement Master's Guidance --- .../src/mage/cards/m/MastersGuidance.java | 49 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 2 + 2 files changed, 51 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MastersGuidance.java diff --git a/Mage.Sets/src/mage/cards/m/MastersGuidance.java b/Mage.Sets/src/mage/cards/m/MastersGuidance.java new file mode 100644 index 00000000000..bab0517611a --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MastersGuidance.java @@ -0,0 +1,49 @@ +package mage.cards.m; + +import mage.abilities.Ability; +import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; +import mage.abilities.condition.common.FerociousCondition; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.common.FerociousHint; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.filter.StaticFilters; +import mage.target.common.TargetAttackingCreature; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MastersGuidance extends CardImpl { + + public MastersGuidance(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{G}"); + + // Whenever you attack with two or more legendary creatures, put a +1/+1 counter on each of up to two target attacking creatures. + Ability ability = new AttacksWithCreaturesTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance()), + 2, StaticFilters.FILTER_CREATURES_LEGENDARY + ); + ability.addTarget(new TargetAttackingCreature(0, 2)); + this.addAbility(ability); + + // At the beginning of your end step, if you control a creature with power 4 or greater, draw a card. + this.addAbility(new BeginningOfEndStepTriggeredAbility(new DrawCardSourceControllerEffect(1)) + .withInterveningIf(FerociousCondition.instance) + .addHint(FerociousHint.instance)); + } + + private MastersGuidance(final MastersGuidance card) { + super(card); + } + + @Override + public MastersGuidance copy() { + return new MastersGuidance(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index 2dfdcfa2a4a..6d2d58128e0 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -182,6 +182,8 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Loyal Fire Sage", 242, Rarity.UNCOMMON, mage.cards.l.LoyalFireSage.class)); cards.add(new SetCardInfo("Mai and Zuko", 68, Rarity.RARE, mage.cards.m.MaiAndZuko.class)); cards.add(new SetCardInfo("Many Partings", 169, Rarity.COMMON, mage.cards.m.ManyPartings.class)); + cards.add(new SetCardInfo("Master's Guidance", 141, Rarity.RARE, mage.cards.m.MastersGuidance.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Master's Guidance", 206, Rarity.RARE, mage.cards.m.MastersGuidance.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Match the Odds", 253, Rarity.UNCOMMON, mage.cards.m.MatchTheOdds.class)); cards.add(new SetCardInfo("Mechanical Glider", 256, Rarity.COMMON, mage.cards.m.MechanicalGlider.class)); cards.add(new SetCardInfo("Meteorite", 54, Rarity.MYTHIC, mage.cards.m.Meteorite.class)); From c193ff994b215951260400acf2ed31ad7c790df4 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 15:41:37 -0500 Subject: [PATCH 21/29] [TLE] Implement Monk Gyatso --- Mage.Sets/src/mage/cards/m/MonkGyatso.java | 44 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 2 + 2 files changed, 46 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MonkGyatso.java diff --git a/Mage.Sets/src/mage/cards/m/MonkGyatso.java b/Mage.Sets/src/mage/cards/m/MonkGyatso.java new file mode 100644 index 00000000000..40358b3ad4d --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MonkGyatso.java @@ -0,0 +1,44 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.common.BecomesTargetAnyTriggeredAbility; +import mage.abilities.effects.keyword.AirbendTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class MonkGyatso extends CardImpl { + + public MonkGyatso(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.MONK); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Whenever another creature you control becomes the target of a spell or ability, you may airbend that creature. + this.addAbility(new BecomesTargetAnyTriggeredAbility( + new AirbendTargetEffect().setText("airbend that creature"), + StaticFilters.FILTER_ANOTHER_CREATURE_YOU_CONTROL + ).setOptional(true)); + } + + private MonkGyatso(final MonkGyatso card) { + super(card); + } + + @Override + public MonkGyatso copy() { + return new MonkGyatso(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index 6d2d58128e0..ce656693480 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -191,6 +191,8 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Moku, Meandering Drummer", 122, Rarity.UNCOMMON, mage.cards.m.MokuMeanderingDrummer.class)); cards.add(new SetCardInfo("Momo, Rambunctious Rascal", 217, Rarity.UNCOMMON, mage.cards.m.MomoRambunctiousRascal.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Momo, Rambunctious Rascal", 270, Rarity.UNCOMMON, mage.cards.m.MomoRambunctiousRascal.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Monk Gyatso", 173, Rarity.RARE, mage.cards.m.MonkGyatso.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Monk Gyatso", 81, Rarity.RARE, mage.cards.m.MonkGyatso.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 289, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 290, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mountain", 291, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); From a4adf6f844faacd19f13475b144e01bb5e433eeb Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 15:51:15 -0500 Subject: [PATCH 22/29] [TLE] Implement Nightmares and Daydreams --- .../mage/cards/n/NightmaresAndDaydreams.java | 115 ++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 1 + 2 files changed, 116 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/n/NightmaresAndDaydreams.java diff --git a/Mage.Sets/src/mage/cards/n/NightmaresAndDaydreams.java b/Mage.Sets/src/mage/cards/n/NightmaresAndDaydreams.java new file mode 100644 index 00000000000..b314cfa05d0 --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NightmaresAndDaydreams.java @@ -0,0 +1,115 @@ +package mage.cards.n; + +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.common.SagaAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.MillCardsTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.stack.Spell; +import mage.target.TargetPlayer; + +import java.util.Objects; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NightmaresAndDaydreams extends CardImpl { + + public NightmaresAndDaydreams(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{U}"); + + this.subtype.add(SubType.SAGA); + + // (As this Saga enters and after your draw step, add a lore counter. Sacrifice after IV.) + SagaAbility sagaAbility = new SagaAbility(this, SagaChapter.CHAPTER_IV); + + // I, II, III -- Until your next turn, whenever you cast an instant or sorcery spell, target player mills cards equal to that spell's mana value. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, SagaChapter.CHAPTER_III, + new CreateDelayedTriggeredAbilityEffect(new NightmaresAndDaydreamsTriggeredAbility()) + ); + + // IV -- Draw a card. If a graveyard has twenty or more cards in it, draw three cards instead. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_IV, + new ConditionalOneShotEffect( + new DrawCardSourceControllerEffect(3), new DrawCardSourceControllerEffect(1), + NightmaresAndDaydreamsCondition.instance, "draw a card. " + + "If a graveyard has twenty or more cards in it, draw three cards instead" + ) + ); + this.addAbility(sagaAbility); + } + + private NightmaresAndDaydreams(final NightmaresAndDaydreams card) { + super(card); + } + + @Override + public NightmaresAndDaydreams copy() { + return new NightmaresAndDaydreams(this); + } +} + +enum NightmaresAndDaydreamsCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return game + .getState() + .getPlayersInRange(source.getControllerId(), game) + .stream() + .map(game::getPlayer) + .filter(Objects::nonNull) + .anyMatch(player -> player.getGraveyard().size() >= 20); + } +} + +class NightmaresAndDaydreamsTriggeredAbility extends DelayedTriggeredAbility { + + NightmaresAndDaydreamsTriggeredAbility() { + super(new MillCardsTargetEffect(SavedDamageValue.MANY) + .setText("target player mills cards equal to that spell's mana value"), + Duration.UntilYourNextTurn, false, false); + this.setTriggerPhrase("Until your next turn, whenever you cast an instant or sorcery spell, "); + this.addTarget(new TargetPlayer()); + } + + private NightmaresAndDaydreamsTriggeredAbility(final NightmaresAndDaydreamsTriggeredAbility ability) { + super(ability); + } + + @Override + public NightmaresAndDaydreamsTriggeredAbility copy() { + return new NightmaresAndDaydreamsTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.SPELL_CAST; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Spell spell = game.getSpell(event.getTargetId()); + if (spell == null || !spell.isControlledBy(getControllerId()) || !spell.isInstantOrSorcery(game)) { + return false; + } + this.getEffects().setValue("damage", spell.getManaValue()); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index ce656693480..e86448a8434 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -203,6 +203,7 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Mountain", 296, Rarity.LAND, mage.cards.basiclands.Mountain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mystic Remora", 16, Rarity.MYTHIC, mage.cards.m.MysticRemora.class)); cards.add(new SetCardInfo("Mystical Tutor", 308, Rarity.RARE, mage.cards.m.MysticalTutor.class)); + cards.add(new SetCardInfo("Nightmares and Daydreams", 94, Rarity.RARE, mage.cards.n.NightmaresAndDaydreams.class)); cards.add(new SetCardInfo("Noxious Gearhulk", 25, Rarity.MYTHIC, mage.cards.n.NoxiousGearhulk.class)); cards.add(new SetCardInfo("Obscuring Haze", 313, Rarity.RARE, mage.cards.o.ObscuringHaze.class)); cards.add(new SetCardInfo("Overwhelming Victory", 123, Rarity.RARE, mage.cards.o.OverwhelmingVictory.class, NON_FULL_USE_VARIOUS)); From be04d9b28ff19e922bc3271e1b6ce39487cd1b84 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 15:55:22 -0500 Subject: [PATCH 23/29] [TLE] Implement Suki, Kyoshi Captain --- .../src/mage/cards/s/SukiKyoshiCaptain.java | 63 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 2 + 2 files changed, 65 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/SukiKyoshiCaptain.java diff --git a/Mage.Sets/src/mage/cards/s/SukiKyoshiCaptain.java b/Mage.Sets/src/mage/cards/s/SukiKyoshiCaptain.java new file mode 100644 index 00000000000..f9c1b602a33 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SukiKyoshiCaptain.java @@ -0,0 +1,63 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.common.continuous.BoostControlledEffect; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.AttackingPredicate; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SukiKyoshiCaptain extends CardImpl { + + private static final FilterCreaturePermanent filter = new FilterCreaturePermanent(SubType.WARRIOR, "Warriors"); + private static final FilterPermanent filter2 = new FilterPermanent(SubType.WARRIOR, "attacking Warriors"); + + static { + filter.add(AttackingPredicate.instance); + } + + public SukiKyoshiCaptain(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // Other Warriors you control get +1/+1. + this.addAbility(new SimpleStaticAbility(new BoostControlledEffect( + 1, 1, Duration.WhileOnBattlefield, filter, true + ))); + + // {3}{W}: Attacking Warriors you control gain double strike until end of turn. + this.addAbility(new SimpleActivatedAbility(new GainAbilityControlledEffect( + DoubleStrikeAbility.getInstance(), Duration.EndOfTurn, filter2 + ), new ManaCostsImpl<>("{3}{W}"))); + } + + private SukiKyoshiCaptain(final SukiKyoshiCaptain card) { + super(card); + } + + @Override + public SukiKyoshiCaptain copy() { + return new SukiKyoshiCaptain(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index e86448a8434..7be8be943be 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -247,6 +247,8 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Sol Ring", 316, Rarity.RARE, mage.cards.s.SolRing.class)); cards.add(new SetCardInfo("Solid Ground", 142, Rarity.UNCOMMON, mage.cards.s.SolidGround.class)); cards.add(new SetCardInfo("Standstill", 19, Rarity.MYTHIC, mage.cards.s.Standstill.class)); + cards.add(new SetCardInfo("Suki, Kyoshi Captain", 175, Rarity.RARE, mage.cards.s.SukiKyoshiCaptain.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Suki, Kyoshi Captain", 85, Rarity.RARE, mage.cards.s.SukiKyoshiCaptain.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Sunbaked Canyon", 58, Rarity.MYTHIC, mage.cards.s.SunbakedCanyon.class)); cards.add(new SetCardInfo("Sundial of the Infinite", 55, Rarity.MYTHIC, mage.cards.s.SundialOfTheInfinite.class)); cards.add(new SetCardInfo("Suspicious Bookcase", 170, Rarity.UNCOMMON, mage.cards.s.SuspiciousBookcase.class)); From 25cd45c8b857898e24dc48bb8b37b93b09441e31 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 14 Nov 2025 15:58:20 -0500 Subject: [PATCH 24/29] [TLE] Implement Toph, Greatest Earthbender --- .../mage/cards/t/TophGreatestEarthbender.java | 63 +++++++++++++++++++ .../sets/AvatarTheLastAirbenderEternal.java | 1 + 2 files changed, 64 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TophGreatestEarthbender.java diff --git a/Mage.Sets/src/mage/cards/t/TophGreatestEarthbender.java b/Mage.Sets/src/mage/cards/t/TophGreatestEarthbender.java new file mode 100644 index 00000000000..f40f25e154b --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TophGreatestEarthbender.java @@ -0,0 +1,63 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.dynamicvalue.common.ManaSpentToCastCount; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.abilities.keyword.DoubleStrikeAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.FilterPermanent; +import mage.target.common.TargetControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TophGreatestEarthbender extends CardImpl { + + private static final FilterPermanent filter = new FilterPermanent("land creatures"); + + static { + filter.add(CardType.LAND.getPredicate()); + filter.add(CardType.CREATURE.getPredicate()); + } + + public TophGreatestEarthbender(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{R}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARRIOR); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(3); + this.toughness = new MageInt(3); + + // When Toph enters, earthbend X, where X is the amount of mana spent to cast her. + Ability ability = new EntersBattlefieldTriggeredAbility(new EarthbendTargetEffect(ManaSpentToCastCount.instance)); + ability.addTarget(new TargetControlledLandPermanent()); + this.addAbility(ability); + + // Land creatures you control have double strike. + this.addAbility(new SimpleStaticAbility(new GainAbilityControlledEffect( + DoubleStrikeAbility.getInstance(), Duration.WhileOnBattlefield, filter + ))); + } + + private TophGreatestEarthbender(final TophGreatestEarthbender card) { + super(card); + } + + @Override + public TophGreatestEarthbender copy() { + return new TophGreatestEarthbender(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java index 7be8be943be..b5788a8cf8e 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbenderEternal.java @@ -269,6 +269,7 @@ public final class AvatarTheLastAirbenderEternal extends ExpansionSet { cards.add(new SetCardInfo("Thriving Moor", 264, Rarity.COMMON, mage.cards.t.ThrivingMoor.class)); cards.add(new SetCardInfo("Toph, Earthbending Master", 145, Rarity.MYTHIC, mage.cards.t.TophEarthbendingMaster.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Toph, Earthbending Master", 209, Rarity.MYTHIC, mage.cards.t.TophEarthbendingMaster.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Toph, Greatest Earthbender", 70, Rarity.RARE, mage.cards.t.TophGreatestEarthbender.class)); cards.add(new SetCardInfo("Toucan-Puffin", 88, Rarity.COMMON, mage.cards.t.ToucanPuffin.class)); cards.add(new SetCardInfo("Training Grounds", 20, Rarity.MYTHIC, mage.cards.t.TrainingGrounds.class)); cards.add(new SetCardInfo("Treetop Village", 60, Rarity.MYTHIC, mage.cards.t.TreetopVillage.class)); From 49442b7caff22524a5562463339e6778bf8680ee Mon Sep 17 00:00:00 2001 From: ReSech Date: Sat, 15 Nov 2025 20:10:31 +1100 Subject: [PATCH 25/29] Slimefoot And Squee - fixed that it can't be activated without another target (#14089, #14088) --- Mage.Sets/src/mage/cards/s/SlimefootAndSquee.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/s/SlimefootAndSquee.java b/Mage.Sets/src/mage/cards/s/SlimefootAndSquee.java index 4a8be95b0bc..4b8f9549464 100644 --- a/Mage.Sets/src/mage/cards/s/SlimefootAndSquee.java +++ b/Mage.Sets/src/mage/cards/s/SlimefootAndSquee.java @@ -58,7 +58,7 @@ public final class SlimefootAndSquee extends CardImpl { ability.addCost(new SacrificeTargetCost(filter)); ability.addEffect(new ReturnFromGraveyardToBattlefieldTargetEffect() .setText("and up to one other target creature card from your graveyard to the battlefield")); - ability.addTarget(new TargetCardInYourGraveyard(filter2)); + ability.addTarget(new TargetCardInYourGraveyard(0, 1, filter2)); this.addAbility(ability); } From 6b4b9387139b636a668a561747442ae6eb52f8ca Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 15 Nov 2025 10:03:06 -0500 Subject: [PATCH 26/29] [TLA] Implement Bumi, King of Three Trials --- .../mage/cards/b/BumiKingOfThreeTrials.java | 88 +++++++++++++++++++ .../src/mage/sets/AvatarTheLastAirbender.java | 1 + 2 files changed, 89 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/b/BumiKingOfThreeTrials.java diff --git a/Mage.Sets/src/mage/cards/b/BumiKingOfThreeTrials.java b/Mage.Sets/src/mage/cards/b/BumiKingOfThreeTrials.java new file mode 100644 index 00000000000..17b18557907 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BumiKingOfThreeTrials.java @@ -0,0 +1,88 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Mode; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.LessonsInGraveCondition; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.abilities.effects.keyword.ScryTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.target.TargetPlayer; +import mage.target.common.TargetControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BumiKingOfThreeTrials extends CardImpl { + + public BumiKingOfThreeTrials(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{5}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.NOBLE); + this.subtype.add(SubType.ALLY); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Bumi enters, choose up to X, where X is the number of Lesson cards in your graveyard -- + // * Put three +1/+1 counters on Bumi. + // * Target player scries 3. + // * Earthbend 3. + this.addAbility(new BumiKingOfThreeTrialsTriggeredAbility()); + } + + private BumiKingOfThreeTrials(final BumiKingOfThreeTrials card) { + super(card); + } + + @Override + public BumiKingOfThreeTrials copy() { + return new BumiKingOfThreeTrials(this); + } +} + +class BumiKingOfThreeTrialsTriggeredAbility extends EntersBattlefieldTriggeredAbility { + + private static final DynamicValue xValue = new CardsInControllerGraveyardCount(new FilterCard(SubType.LESSON)); + + BumiKingOfThreeTrialsTriggeredAbility() { + super(new AddCountersSourceEffect(CounterType.P1P1.createInstance())); + this.getModes().setChooseText("choose up to X, where X is the number of Lesson cards in your graveyard —"); + this.getModes().setMinModes(0); + this.addMode(new Mode(new ScryTargetEffect(3)).addTarget(new TargetPlayer())); + this.addMode(new Mode(new EarthbendTargetEffect(3)).addTarget(new TargetControlledLandPermanent())); + this.addHint(LessonsInGraveCondition.getHint()); + } + + private BumiKingOfThreeTrialsTriggeredAbility(final BumiKingOfThreeTrialsTriggeredAbility ability) { + super(ability); + } + + @Override + public BumiKingOfThreeTrialsTriggeredAbility copy() { + return new BumiKingOfThreeTrialsTriggeredAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (!super.checkTrigger(event, game)) { + return false; + } + this.getModes().setMaxModes(xValue.calculate(game, this, null)); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index fcb7c58b3c6..65c6add2c29 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -92,6 +92,7 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Boiling Rock Rioter", 87, Rarity.RARE, mage.cards.b.BoilingRockRioter.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Boomerang Basics", 46, Rarity.UNCOMMON, mage.cards.b.BoomerangBasics.class)); cards.add(new SetCardInfo("Bumi Bash", 125, Rarity.COMMON, mage.cards.b.BumiBash.class)); + cards.add(new SetCardInfo("Bumi, King of Three Trials", 169, Rarity.UNCOMMON, mage.cards.b.BumiKingOfThreeTrials.class)); cards.add(new SetCardInfo("Bumi, Unleashed", 211, Rarity.MYTHIC, mage.cards.b.BumiUnleashed.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Bumi, Unleashed", 348, Rarity.MYTHIC, mage.cards.b.BumiUnleashed.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Buzzard-Wasp Colony", 88, Rarity.UNCOMMON, mage.cards.b.BuzzardWaspColony.class)); From 7f85e6ef3f3cca210d0db0c9f2349b6520f310e7 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 15 Nov 2025 10:08:26 -0500 Subject: [PATCH 27/29] [TLA] Implement Combustion Man --- Mage.Sets/src/mage/cards/c/CombustionMan.java | 92 +++++++++++++++++++ .../src/mage/sets/AvatarTheLastAirbender.java | 1 + 2 files changed, 93 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/c/CombustionMan.java diff --git a/Mage.Sets/src/mage/cards/c/CombustionMan.java b/Mage.Sets/src/mage/cards/c/CombustionMan.java new file mode 100644 index 00000000000..946f62ded84 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CombustionMan.java @@ -0,0 +1,92 @@ +package mage.cards.c; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.util.CardUtil; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CombustionMan extends CardImpl { + + public CombustionMan(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(4); + this.toughness = new MageInt(6); + + // Whenever Combustion Man attacks, destroy target permanent unless its controller has Combustion Man deal damage to them equal to his power. + Ability ability = new AttacksTriggeredAbility(new CombustionManEffect()); + ability.addTarget(new TargetPermanent()); + this.addAbility(ability); + } + + private CombustionMan(final CombustionMan card) { + super(card); + } + + @Override + public CombustionMan copy() { + return new CombustionMan(this); + } +} + +class CombustionManEffect extends OneShotEffect { + + CombustionManEffect() { + super(Outcome.Benefit); + staticText = "destroy target permanent unless its controller has {this} deal damage to them equal to his power"; + } + + private CombustionManEffect(final CombustionManEffect effect) { + super(effect); + } + + @Override + public CombustionManEffect copy() { + return new CombustionManEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + Player player = game.getPlayer(permanent.getControllerId()); + if (player == null || !player.chooseUse( + Outcome.Damage, "Have " + CardUtil.getSourceIdName(game, source) + + " deal damage to you equal to its power?", "If you don't, " + + permanent.getIdName() + " will be destroyed", + "Take damage", "Destroy permanent", source, game + )) { + return permanent.destroy(source, game); + } + return Optional + .ofNullable(source.getSourcePermanentOrLKI(game)) + .map(MageObject::getPower) + .map(MageInt::getValue) + .filter(x -> x > 0) + .filter(x -> player.damage(x, source, game) > 0) + .isPresent(); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index 65c6add2c29..bbeb3385f06 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -100,6 +100,7 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Canyon Crawler", 90, Rarity.COMMON, mage.cards.c.CanyonCrawler.class)); cards.add(new SetCardInfo("Cat-Gator", 91, Rarity.UNCOMMON, mage.cards.c.CatGator.class)); cards.add(new SetCardInfo("Cat-Owl", 212, Rarity.COMMON, mage.cards.c.CatOwl.class)); + cards.add(new SetCardInfo("Combustion Man", 127, Rarity.UNCOMMON, mage.cards.c.CombustionMan.class)); cards.add(new SetCardInfo("Combustion Technique", 128, Rarity.UNCOMMON, mage.cards.c.CombustionTechnique.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Combustion Technique", 301, Rarity.UNCOMMON, mage.cards.c.CombustionTechnique.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Compassionate Healer", 13, Rarity.COMMON, mage.cards.c.CompassionateHealer.class)); From 886dd1f0b2ae05549dbe6e1e0027a56f5943d56f Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 15 Nov 2025 10:24:28 -0500 Subject: [PATCH 28/29] [TLA] Implement Lost Days --- Mage.Sets/src/mage/cards/d/DeemInferior.java | 45 +------------------ Mage.Sets/src/mage/cards/l/LostDays.java | 39 ++++++++++++++++ .../src/mage/cards/t/TemporalCleansing.java | 45 +------------------ .../src/mage/sets/AvatarTheLastAirbender.java | 1 + .../PutOnTopOrBottomLibraryTargetEffect.java | 33 ++++++++++++-- 5 files changed, 74 insertions(+), 89 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/l/LostDays.java diff --git a/Mage.Sets/src/mage/cards/d/DeemInferior.java b/Mage.Sets/src/mage/cards/d/DeemInferior.java index 2745bc88338..dc4a84da763 100644 --- a/Mage.Sets/src/mage/cards/d/DeemInferior.java +++ b/Mage.Sets/src/mage/cards/d/DeemInferior.java @@ -3,16 +3,12 @@ package mage.cards.d; import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.dynamicvalue.common.CardsDrawnThisTurnDynamicValue; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.Zone; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.common.TargetNonlandPermanent; import java.util.UUID; @@ -33,7 +29,7 @@ public final class DeemInferior extends CardImpl { this.addAbility(ability.addHint(CardsDrawnThisTurnDynamicValue.getHint())); // The owner of target nonland permanent puts it into their library second from the top or on the bottom. - this.getSpellAbility().addEffect(new DeemInferiorEffect()); + this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect(2, true)); this.getSpellAbility().addTarget(new TargetNonlandPermanent()); } @@ -46,40 +42,3 @@ public final class DeemInferior extends CardImpl { return new DeemInferior(this); } } - -// Same as Temporal Cleansing. -class DeemInferiorEffect extends OneShotEffect { - - DeemInferiorEffect() { - super(Outcome.Benefit); - staticText = "the owner of target nonland permanent puts it into their library second from the top or on the bottom"; - } - - private DeemInferiorEffect(final DeemInferiorEffect effect) { - super(effect); - } - - @Override - public DeemInferiorEffect copy() { - return new DeemInferiorEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - if (permanent == null) { - return false; - } - Player player = game.getPlayer(permanent.getOwnerId()); - if (player == null) { - return false; - } - if (player.chooseUse( - outcome, "Put " + permanent.getIdName() + " second from the top or on the bottom?", - null, "Second from top", "Bottom", source, game - )) { - return player.putCardOnTopXOfLibrary(permanent, game, source, 2, true); - } - return player.putCardsOnBottomOfLibrary(permanent, game, source); - } -} diff --git a/Mage.Sets/src/mage/cards/l/LostDays.java b/Mage.Sets/src/mage/cards/l/LostDays.java new file mode 100644 index 00000000000..92d8967e83d --- /dev/null +++ b/Mage.Sets/src/mage/cards/l/LostDays.java @@ -0,0 +1,39 @@ +package mage.cards.l; + +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.game.permanent.token.ClueArtifactToken; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class LostDays extends CardImpl { + + public LostDays(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{U}"); + + this.subtype.add(SubType.LESSON); + + // The owner of target creature or enchantment puts it into their library second from the top or on the bottom. You create a Clue token. + this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect(2, true)); + this.getSpellAbility().addEffect(new CreateTokenEffect(new ClueArtifactToken()).concatBy("You")); + this.getSpellAbility().addTarget(new TargetPermanent(StaticFilters.FILTER_PERMANENT_CREATURE_OR_ENCHANTMENT)); + } + + private LostDays(final LostDays card) { + super(card); + } + + @Override + public LostDays copy() { + return new LostDays(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TemporalCleansing.java b/Mage.Sets/src/mage/cards/t/TemporalCleansing.java index 250e304cc9b..32002794171 100644 --- a/Mage.Sets/src/mage/cards/t/TemporalCleansing.java +++ b/Mage.Sets/src/mage/cards/t/TemporalCleansing.java @@ -1,15 +1,10 @@ package mage.cards.t; -import mage.abilities.Ability; -import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.PutOnTopOrBottomLibraryTargetEffect; import mage.abilities.keyword.ConvokeAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; import mage.target.common.TargetNonlandPermanent; import java.util.UUID; @@ -26,7 +21,7 @@ public final class TemporalCleansing extends CardImpl { this.addAbility(new ConvokeAbility()); // The owner of target nonland permanent puts it into their library second from the top or on the bottom. - this.getSpellAbility().addEffect(new TemporalCleansingEffect()); + this.getSpellAbility().addEffect(new PutOnTopOrBottomLibraryTargetEffect(2, true)); this.getSpellAbility().addTarget(new TargetNonlandPermanent()); } @@ -39,39 +34,3 @@ public final class TemporalCleansing extends CardImpl { return new TemporalCleansing(this); } } - -class TemporalCleansingEffect extends OneShotEffect { - - TemporalCleansingEffect() { - super(Outcome.Benefit); - staticText = "the owner of target nonland permanent puts it into their library second from the top or on the bottom"; - } - - private TemporalCleansingEffect(final TemporalCleansingEffect effect) { - super(effect); - } - - @Override - public TemporalCleansingEffect copy() { - return new TemporalCleansingEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); - if (permanent == null) { - return false; - } - Player player = game.getPlayer(permanent.getOwnerId()); - if (player == null) { - return false; - } - if (player.chooseUse( - outcome, "Put " + permanent.getIdName() + " second from the top or on the bottom?", - null, "Second from top", "Bottom", source, game - )) { - return player.putCardOnTopXOfLibrary(permanent, game, source, 2, true); - } - return player.putCardsOnBottomOfLibrary(permanent, game, source); - } -} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index bbeb3385f06..c8b4db17dfe 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -230,6 +230,7 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Lightning Strike", 146, Rarity.COMMON, mage.cards.l.LightningStrike.class)); cards.add(new SetCardInfo("Lo and Li, Twin Tutors", 108, Rarity.UNCOMMON, mage.cards.l.LoAndLiTwinTutors.class)); cards.add(new SetCardInfo("Long Feng, Grand Secretariat", 233, Rarity.UNCOMMON, mage.cards.l.LongFengGrandSecretariat.class)); + cards.add(new SetCardInfo("Lost Days", 62, Rarity.COMMON, mage.cards.l.LostDays.class)); cards.add(new SetCardInfo("Mai, Jaded Edge", 147, Rarity.UNCOMMON, mage.cards.m.MaiJadedEdge.class)); cards.add(new SetCardInfo("Mai, Scornful Striker", 109, Rarity.RARE, mage.cards.m.MaiScornfulStriker.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Mai, Scornful Striker", 374, Rarity.RARE, mage.cards.m.MaiScornfulStriker.class, NON_FULL_USE_VARIOUS)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java index 4a99ba55bd2..45dfa70ed00 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/PutOnTopOrBottomLibraryTargetEffect.java @@ -6,21 +6,29 @@ import mage.abilities.effects.OneShotEffect; import mage.constants.Outcome; import mage.game.Game; import mage.players.Player; +import mage.util.CardUtil; /** * @author TheElk801 */ public class PutOnTopOrBottomLibraryTargetEffect extends OneShotEffect { + private final int position; private final boolean textOwnerOf; public PutOnTopOrBottomLibraryTargetEffect(boolean textOwnerOf) { + this(1, textOwnerOf); + } + + public PutOnTopOrBottomLibraryTargetEffect(int position, boolean textOwnerOf) { super(Outcome.ReturnToHand); + this.position = position; this.textOwnerOf = textOwnerOf; } private PutOnTopOrBottomLibraryTargetEffect(final PutOnTopOrBottomLibraryTargetEffect effect) { super(effect); + this.position = effect.position; this.textOwnerOf = effect.textOwnerOf; } @@ -35,10 +43,14 @@ public class PutOnTopOrBottomLibraryTargetEffect extends OneShotEffect { if (player == null) { return false; } + String message = position > 1 ? "into your library " + CardUtil.numberToOrdinalText(position) + " from the top or on the" : "on the top or"; boolean onTop = player.chooseUse( - Outcome.Detriment, "Put the targeted object on the top or bottom of your library?", + Outcome.Detriment, "Put the targeted object " + message + " bottom of your library?", null, "Top", "Bottom", source, game ); + if (onTop && position > 1) { + return new PutIntoLibraryNFromTopTargetEffect(position).apply(game, source); + } return new PutOnLibraryTargetEffect(onTop).apply(game, source); } @@ -47,8 +59,23 @@ public class PutOnTopOrBottomLibraryTargetEffect extends OneShotEffect { if (staticText != null && !staticText.isEmpty()) { return staticText; } + StringBuilder sb = new StringBuilder(); String targetText = getTargetPointer().describeTargets(mode.getTargets(), "that permanent"); - return (textOwnerOf ? "the owner of " + targetText : targetText + "'s owner") + - " puts it on their choice of the top or bottom of their library"; + if (textOwnerOf) { + sb.append("the owner of "); + sb.append(targetText); + } else { + sb.append(targetText); + sb.append("'s owner"); + } + sb.append(" puts it"); + if (position > 1) { + sb.append("into their library "); + sb.append(CardUtil.numberToOrdinalText(position)); + sb.append(" from the top or on the bottom"); + } else { + sb.append("on their choice of the top or bottom of their library"); + } + return sb.toString(); } } From b16fba2ceb9da1b1bea08b02a86eb01176dc1ebc Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 15 Nov 2025 10:38:02 -0500 Subject: [PATCH 29/29] [TLA] Implement The Legend of Kyoshi / Avatar Kyoshi --- Mage.Sets/src/mage/cards/a/AvatarKyoshi.java | 62 +++++++++++++++++ .../src/mage/cards/t/TheLegendOfKyoshi.java | 66 +++++++++++++++++++ .../src/mage/sets/AvatarTheLastAirbender.java | 4 ++ 3 files changed, 132 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/a/AvatarKyoshi.java create mode 100644 Mage.Sets/src/mage/cards/t/TheLegendOfKyoshi.java diff --git a/Mage.Sets/src/mage/cards/a/AvatarKyoshi.java b/Mage.Sets/src/mage/cards/a/AvatarKyoshi.java new file mode 100644 index 00000000000..c4c67099081 --- /dev/null +++ b/Mage.Sets/src/mage/cards/a/AvatarKyoshi.java @@ -0,0 +1,62 @@ +package mage.cards.a; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue; +import mage.abilities.effects.common.continuous.GainAbilityControlledEffect; +import mage.abilities.keyword.HexproofAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.abilities.mana.DynamicManaAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.filter.StaticFilters; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class AvatarKyoshi extends CardImpl { + + public AvatarKyoshi(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, ""); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.AVATAR); + this.power = new MageInt(5); + this.toughness = new MageInt(4); + this.nightCard = true; + + // Lands you control have trample and hexproof. + Ability ability = new SimpleStaticAbility(new GainAbilityControlledEffect( + TrampleAbility.getInstance(), Duration.WhileOnBattlefield, StaticFilters.FILTER_LANDS + )); + ability.addEffect(new GainAbilityControlledEffect( + HexproofAbility.getInstance(), Duration.WhileOnBattlefield, StaticFilters.FILTER_LANDS + ).setText("and hexproof")); + this.addAbility(ability); + + // {T}: Add X mana of any one color, where X is the greatest power among creatures you control. + this.addAbility(new DynamicManaAbility( + Mana.AnyMana(1), GreatestAmongPermanentsValue.POWER_CONTROLLED_CREATURES, + new TapSourceCost(), "add X mana of any one color, " + + "where X is the greatest power among creatures you control", true + ).addHint(GreatestAmongPermanentsValue.POWER_CONTROLLED_CREATURES.getHint())); + } + + private AvatarKyoshi(final AvatarKyoshi card) { + super(card); + } + + @Override + public AvatarKyoshi copy() { + return new AvatarKyoshi(this); + } +} diff --git a/Mage.Sets/src/mage/cards/t/TheLegendOfKyoshi.java b/Mage.Sets/src/mage/cards/t/TheLegendOfKyoshi.java new file mode 100644 index 00000000000..1ba57a2371b --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheLegendOfKyoshi.java @@ -0,0 +1,66 @@ +package mage.cards.t; + +import mage.abilities.common.SagaAbility; +import mage.abilities.dynamicvalue.common.CardsInControllerHandCount; +import mage.abilities.dynamicvalue.common.GreatestAmongPermanentsValue; +import mage.abilities.effects.Effects; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ExileSagaAndReturnTransformedEffect; +import mage.abilities.effects.common.continuous.AddCardSubTypeTargetEffect; +import mage.abilities.effects.keyword.EarthbendTargetEffect; +import mage.abilities.keyword.TransformAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SagaChapter; +import mage.constants.SubType; +import mage.target.common.TargetControlledLandPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheLegendOfKyoshi extends CardImpl { + + public TheLegendOfKyoshi(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{4}{G}{G}"); + + this.subtype.add(SubType.SAGA); + this.secondSideCardClazz = mage.cards.a.AvatarKyoshi.class; + + // (As this Saga enters and after your draw step, add a lore counter.) + SagaAbility sagaAbility = new SagaAbility(this); + + // I -- Draw cards equal to the greatest power among creatures you control. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_I, + new DrawCardSourceControllerEffect(GreatestAmongPermanentsValue.POWER_CONTROLLED_CREATURES) + ); + + // II -- Earthbend X, where X is the number of cards in your hand. That land becomes an Island in addition to its other types. + sagaAbility.addChapterEffect( + this, SagaChapter.CHAPTER_II, + new Effects( + new EarthbendTargetEffect(CardsInControllerHandCount.ANY), + new AddCardSubTypeTargetEffect(SubType.ISLAND, Duration.Custom) + .setText("That land becomes an Island in addition to its other types") + ), new TargetControlledLandPermanent() + ); + + // III -- Exile this Saga, then return it to the battlefield transformed under your control. + this.addAbility(new TransformAbility()); + sagaAbility.addChapterEffect(this, SagaChapter.CHAPTER_III, new ExileSagaAndReturnTransformedEffect()); + this.addAbility(sagaAbility.addHint(GreatestAmongPermanentsValue.POWER_CONTROLLED_CREATURES.getHint())); + } + + private TheLegendOfKyoshi(final TheLegendOfKyoshi card) { + super(card); + } + + @Override + public TheLegendOfKyoshi copy() { + return new TheLegendOfKyoshi(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java index c8b4db17dfe..8e59ffff27a 100644 --- a/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java +++ b/Mage.Sets/src/mage/sets/AvatarTheLastAirbender.java @@ -68,6 +68,8 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("Avatar Enthusiasts", 11, Rarity.COMMON, mage.cards.a.AvatarEnthusiasts.class)); cards.add(new SetCardInfo("Avatar Kuruk", 355, Rarity.MYTHIC, mage.cards.a.AvatarKuruk.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Avatar Kuruk", 61, Rarity.MYTHIC, mage.cards.a.AvatarKuruk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avatar Kyoshi", 186, Rarity.MYTHIC, mage.cards.a.AvatarKyoshi.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Avatar Kyoshi", 358, Rarity.MYTHIC, mage.cards.a.AvatarKyoshi.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Avatar Roku", 145, Rarity.MYTHIC, mage.cards.a.AvatarRoku.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Avatar Roku", 357, Rarity.MYTHIC, mage.cards.a.AvatarRoku.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Avatar's Wrath", 12, Rarity.RARE, mage.cards.a.AvatarsWrath.class, NON_FULL_USE_VARIOUS)); @@ -329,6 +331,8 @@ public final class AvatarTheLastAirbender extends ExpansionSet { cards.add(new SetCardInfo("The Cave of Two Lovers", 126, Rarity.UNCOMMON, mage.cards.t.TheCaveOfTwoLovers.class)); cards.add(new SetCardInfo("The Legend of Kuruk", 355, Rarity.MYTHIC, mage.cards.t.TheLegendOfKuruk.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Legend of Kuruk", 61, Rarity.MYTHIC, mage.cards.t.TheLegendOfKuruk.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Legend of Kyoshi", 186, Rarity.MYTHIC, mage.cards.t.TheLegendOfKyoshi.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("The Legend of Kyoshi", 358, Rarity.MYTHIC, mage.cards.t.TheLegendOfKyoshi.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Legend of Roku", 145, Rarity.MYTHIC, mage.cards.t.TheLegendOfRoku.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Legend of Roku", 357, Rarity.MYTHIC, mage.cards.t.TheLegendOfRoku.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("The Lion-Turtle", 232, Rarity.RARE, mage.cards.t.TheLionTurtle.class, NON_FULL_USE_VARIOUS));