From c543f9a3b82a84ba9ea6e198a6bbdf524ce9f112 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 13 Sep 2024 19:49:13 -0400 Subject: [PATCH 01/62] [DSK] update spoiler --- Utils/mtg-cards-data.txt | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 55ca242188c..95af5781d46 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -53982,6 +53982,7 @@ Forceful Cultivator|Alchemy: Kamigawa|29|M|{2}{G}{G}|Creature - Snake Shaman|2|3 Imperial Blademaster|Alchemy: Kamigawa|30|R|{1}{R}{W}|Creature - Human Samurai|2|3|Double strike$Whenever a Samurai or Warrior you control attacks alone, draft a card from Imperial Blademaster's spellbook.| Acrobatic Cheerleader|Duskmourn: House of Horror|1|C|{1}{W}|Creature - Human Survivor|2|2|Survival -- At the beginning of your second main phase, if Acrobatic Cheerleader is tapped, put a flying counter on it. This ability triggers only once.| Cult Healer|Duskmourn: House of Horror|2|C|{2}{W}|Creature - Human Doctor|3|3|Eerie -- Whenever an enchantment you control enters and whenever you fully unlock a Room, Cult Healer gains lifelink until end of turn.| +Dazzling Theater // Prop Room|Duskmourn: House of Horror|3|R|{3}{W}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Creature spells you cast have convoke.$Prop Room${2}{W}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Untap each creature you control during each other player's untap step.| Dollmaker's Shop // Porcelain Gallery|Duskmourn: House of Horror|4|M|{1}{W}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Whenever one or more non-Toy creatures you control attack a player, create a 1/1 white Toy artifact creature token.$Porcelain Gallery${4}{W}{W}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Creatures you control have base power and toughness each equal to the number of creatures you control.| Emerge from the Cocoon|Duskmourn: House of Horror|5|C|{4}{W}|Sorcery|||Return target creature card from your graveyard to the battlefield. You gain 3 life.| Enduring Innocence|Duskmourn: House of Horror|6|R|{1}{W}{W}|Enchantment Creature - Sheep Glimmer|2|1|Lifelink$Whenever one or more other creatures you control with power 2 or less enter, draw a card. This ability triggers only once each turn.$When Enduring Innocence dies, if it was a creature, return it to the battlefield under its owner's control. It's an enchantment.| @@ -54003,8 +54004,10 @@ Optimistic Scavenger|Duskmourn: House of Horror|21|U|{W}|Creature - Human Scout| Orphans of the Wheat|Duskmourn: House of Horror|22|U|{1}{W}|Creature - Human|2|1|Whenever Orphans of the Wheat attacks, tap any number of untapped creatures you control. Orphans of the Wheat gets +1/+1 until end of turn for each creature tapped this way.| Overlord of the Mistmoors|Duskmourn: House of Horror|23|M|{5}{W}{W}|Enchantment Creature - Avatar Horror|6|6|Impending 4--{2}{W}{W}$Whenever Overlord of the Mistmoors enters or attacks, create two 2/1 white Insect creature tokens with flying.| Patched Plaything|Duskmourn: House of Horror|24|U|{2}{W}|Artifact Creature - Toy|4|3|Double strike$Patched Plaything enters with two -1/-1 counters on it if you cast it from your hand.| +Possessed Goat|Duskmourn: House of Horror|25|C|{W}|Creature - Goat|1|1|{3}, Discard a card: Put three +1/+1 counters on Possessed Goat and it becomes a black Demon in addition to its other colors and types. Activate only once.| Reluctant Role Model|Duskmourn: House of Horror|26|R|{1}{W}|Creature - Human Survivor|2|2|Survival -- At the beginning of your second main phase, if Reluctant Role Model is tapped, put a flying, lifelink, or +1/+1 counter on it.$Whenever Reluctant Role Model or another creature you control dies, if it had counters on it, put those counters on up to one target creature.| Savior of the Small|Duskmourn: House of Horror|27|U|{3}{W}|Creature - Kor Survivor|3|4|Survival -- At the beginning of your second main phase, if Savior of the Small is tapped, return target creature card with mana value 3 or less from your graveyard to your hand.| +Seized from Slumber|Duskmourn: House of Horror|28|C|{4}{W}|Instant|||This spell costs {3} less to cast if it targets a tapped creature.$Destroy target creature.| Shardmage's Rescue|Duskmourn: House of Horror|29|U|{W}|Enchantment - Aura|||Flash$Enchant creature you control$As long as Shardmage's Rescue entered this turn, enchanted creature has hexproof.$Enchanted creature gets +1/+1.| Sheltered by Ghosts|Duskmourn: House of Horror|30|U|{1}{W}|Enchantment - Aura|||Enchant creature you control$When Sheltered by Ghosts enters, exile target nonland permanent an opponent controls until Sheltered by Ghosts leaves the battlefield.$Enchanted creature gets +1/+0 and has lifelink and ward {2}.| Shepherding Spirits|Duskmourn: House of Horror|31|C|{4}{W}{W}|Creature - Spirit|4|5|Flying$Plainscycling {2}| @@ -54025,6 +54028,7 @@ Clammy Prowler|Duskmourn: House of Horror|45|C|{3}{U}|Enchantment Creature - Hor Creeping Peeper|Duskmourn: House of Horror|46|C|{1}{U}|Creature - Eye|2|1|{T}: Add {U}. Spend this mana only to cast an enchantment spell, unlock a door, or turn a permanent face up.| Cursed Windbreaker|Duskmourn: House of Horror|47|U|{2}{U}|Artifact - Equipment|||When Cursed Windbreaker enters, manifest dread, then attach Cursed Windbreaker to that creature.$Equipped creature has flying.$Equip {3}| Daggermaw Megalodon|Duskmourn: House of Horror|48|C|{4}{U}{U}|Creature - Shark|5|7|Vigilance$Islandcycling {2}| +Don't Make a Sound|Duskmourn: House of Horror|49|C|{1}{U}|Instant|||Counter target spell unless its controller pays {2}. If they do, surveil 2.| Duskmourn's Domination|Duskmourn: House of Horror|50|U|{4}{U}{U}|Enchantment - Aura|||Enchant creature$You control enchanted creature.$Enchanted creature gets -3/-0 and loses all abilities.| Enduring Curiosity|Duskmourn: House of Horror|51|R|{2}{U}{U}|Enchantment Creature - Cat Glimmer|4|3|Flash$Whenever a creature you control deals combat damage to a player, draw a card.$When Enduring Curiosity dies, if it was a creature, return it to the battlefield under its owner's control. It's an enchantment.| Enter the Enigma|Duskmourn: House of Horror|52|C|{U}|Sorcery|||Target creature can't be blocked this turn.$Draw a card.| @@ -54039,6 +54043,7 @@ Get Out|Duskmourn: House of Horror|60|U|{U}{U}|Instant|||Choose one --$* Counter Ghostly Keybearer|Duskmourn: House of Horror|61|U|{3}{U}|Creature - Spirit|3|3|Flying$Whenever Ghostly Keybearer deals combat damage to a player, unlock a locked door of up to one target Room you control.| Leyline of Transformation|Duskmourn: House of Horror|63|R|{2}{U}{U}|Enchantment|||If Leyline of Transformation is in your opening hand, you may begin the game with it on the battlefield.$As Leyline of Transformation enters, choose a creature type.$Creatures you control are the chosen type in addition to their other types. The same is true for creature spells you control and creature cards you own that aren't on the battlefield.| Marina Vendrell's Grimoire|Duskmourn: House of Horror|64|R|{5}{U}|Legendary Artifact|||When Marina Vendrell's Grimoire enters, if you cast it, draw five cards.$You have no maximum hand size and don't lose the game for having 0 or less life.$Whenever you gain life, draw that many cards.$Whenever you lose life, discard that many cards. Then if you have no cards in hand, you lose the game.| +Meat Locker // Drowned Diner|Duskmourn: House of Horror|65|C|{2}{U}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, tap up to one target creature and put two stun counters on it.$Drowned Diner${3}{U}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, draw three cards, then discard a card.| The Mindskinner|Duskmourn: House of Horror|66|R|{U}{U}{U}|Legendary Enchantment Creature - Nightmare|10|1|The Mindskinner can't be blocked.$If a source you control would deal damage to an opponent, prevent that damage and each opponent mills that many cards.| Mirror Room // Fractured Realm|Duskmourn: House of Horror|67|M|{2}{U}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, create a token that's a copy of target creature you control, except it's a Reflection in addition to its other creature types.$Fractured Realm${5}{U}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$If a triggered ability of a permanent you control triggers, that ability triggers an additional time.| Overlord of the Floodpits|Duskmourn: House of Horror|68|M|{3}{U}{U}|Enchantment Creature - Avatar Horror|5|3|Impending 4--{1}{U}{U}$Flying$Whenever Overlord of the Floodpits enters or attacks, draw two cards, then discard a card.| @@ -54052,6 +54057,7 @@ The Tale of Tamiyo|Duskmourn: House of Horror|75|R|{2}{U}|Legendary Enchantment Tunnel Surveyor|Duskmourn: House of Horror|76|C|{2}{U}|Creature - Human Detective|2|2|When Tunnel Surveyor enters, create a 1/1 white Glimmer enchantment creature token.| Twist Reality|Duskmourn: House of Horror|77|C|{1}{U}{U}|Instant|||Choose one --$* Counter target spell.$* Manifest dread.| Unable to Scream|Duskmourn: House of Horror|78|C|{U}|Enchantment - Aura|||Enchant creature$Enchanted creature loses all abilities and is a Toy artifact creature with base power and toughness 0/2 in addition to its other types.$As long as enchanted creature is face down, it can't be turned face up.| +Underwater Tunnel // Slimy Aquarium|Duskmourn: House of Horror|79|C|{U}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, surveil 2.$Slimy Aquarium${3}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, manifest dread, then put a +1/+1 counter on that creature.| Unnerving Grasp|Duskmourn: House of Horror|80|U|{2}{U}|Sorcery|||Return up to one target nonland permanent to its owner's hand. Manifest dread.| Unwilling Vessel|Duskmourn: House of Horror|81|U|{2}{U}|Creature - Human Wizard|3|2|Vigilance$Eerie -- Whenever an enchantment you control enters and whenever you fully unlock a Room, put a possession counter on Unwilling Vessel.$When Unwilling Vessel dies, create an X/X blue Spirit creature token with flying, where X is the number of counters on Unwilling Vessel.| Vanish from Sight|Duskmourn: House of Horror|82|C|{3}{U}|Instant|||Target nonland permanent's owner puts it on the top or bottom of their library. Surveil 1.| @@ -54060,11 +54066,12 @@ Balemurk Leech|Duskmourn: House of Horror|84|C|{1}{B}|Creature - Leech|2|2|Eerie Cackling Slasher|Duskmourn: House of Horror|85|C|{3}{B}|Creature - Human Assassin|3|3|Deathtouch$Cackling Slasher enters with a +1/+1 counter on it if a creature died this turn.| Come Back Wrong|Duskmourn: House of Horror|86|R|{2}{B}|Sorcery|||Destroy target creature. If a creature card is put into a graveyard this way, return it to the battlefield under your control. Sacrifice it at the beginning of your next end step.| Commune with Evil|Duskmourn: House of Horror|87|U|{2}{B}|Sorcery|||Look at the top four cards of your library. Put one of them into your hand and the rest into your graveyard. You gain 3 life.| -Cracked Skull|Duskmourn: House of Horror|88|C|{2}{B}|Enchantment|||Enchant creature$When Cracked Skull enters, look at target player's hand. You may choose a nonland card from it. That player discards that card.$When enchanted creature is dealt damage, destroy it.| +Cracked Skull|Duskmourn: House of Horror|88|C|{2}{B}|Enchantment - Aura|||Enchant creature$When Cracked Skull enters, look at target player's hand. You may choose a nonland card from it. That player discards that card.$When enchanted creature is dealt damage, destroy it.| Cynical Loner|Duskmourn: House of Horror|89|U|{1}{B}|Creature - Human Survivor|3|1|Cynical Loner can't be blocked by Glimmers.$Survival -- At the beginning of your second main phase, if Cynical Loner is tapped, you may search your library for a card, put it into your graveyard, then shuffle.| Dashing Bloodsucker|Duskmourn: House of Horror|90|U|{3}{B}|Creature - Vampire Warrior|2|5|Eerie -- Whenever an enchantment you control enters and whenever you fully unlock a Room, Dashing Bloodsucker gets +2/+0 and gains lifelink until end of turn.| Defiled Crypt // Cadaver Lab|Duskmourn: House of Horror|91|U|{3}{B}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Whenever one or more cards leave your graveyard, create a 2/2 black Horror enchantment creature token. This ability triggers only once each turn.$Cadaver Lab${B}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, return target creature card from your graveyard to your hand.| Demonic Counsel|Duskmourn: House of Horror|92|R|{1}{B}|Sorcery|||Search your library for a Demon card, reveal it, put it into your hand, then shuffle.$Delirium -- If there are four or more card types among cards in your graveyard, instead search your library for any card, put it into your hand, then shuffle.| +Derelict Attic // Widow's Walk|Duskmourn: House of Horror|93|C|{2}{B}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, you draw two cards and you lose 2 life.$Widow's Walk${3}{B}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Whenever a creature you control attacks alone, it gets +1/+0 and gains deathtouch until end of turn.| Doomsday Excruciator|Duskmourn: House of Horror|94|R|{B}{B}{B}{B}{B}{B}|Creature - Demon|6|6|Flying$When Doomsday Excruciator enters, if it was cast, each player exiles all but the bottom six cards of their library face down.$At the beginning of your upkeep, draw a card.| Enduring Tenacity|Duskmourn: House of Horror|95|R|{2}{B}{B}|Enchantment Creature - Snake Glimmer|4|3|Whenever you gain life, target opponent loses that much life.$When Enduring Tenacity dies, if it was a creature, return it to the battlefield under its owner's control. It's an enchantment.| Fanatic of the Harrowing|Duskmourn: House of Horror|96|C|{3}{B}|Creature - Human Cleric|2|2|When Fanatic of the Harrowing enters, each player discards a card. If you discarded a card this way, draw a card.| @@ -54098,6 +54105,7 @@ Winter's Intervention|Duskmourn: House of Horror|123|C|{1}{B}|Instant|||Winter's Withering Torment|Duskmourn: House of Horror|124|U|{2}{B}|Instant|||Destroy target creature or enchantment. You lose 2 life.| Bedhead Beastie|Duskmourn: House of Horror|125|C|{4}{R}{R}|Creature - Beast|5|6|Menace$Mountaincycling {2}| Betrayer's Bargain|Duskmourn: House of Horror|126|U|{1}{R}|Instant|||As an additioinal cost to cast this spell, sacrifice a creature or enchantment or pay {2}.$Betrayer's Bargain deals 5 damage to target creature. If that creature would die this turn, exile it instead.| +Boilerbilges Ripper|Duskmourn: House of Horror|127|C|{4}{R}|Creature - Human Assassin|4|4|When Boilerbilges Ripper enters, you may sacrifice another creature or enchantment. When you do, Boilerbilges Ripper deals 2 damage to any target.| Chainsaw|Duskmourn: House of Horror|128|R|{1}{R}|Artifact - Equipment|||When Chainsaw enters, it deals 3 damage to up to one target creature.$Whenever one or more creatures die, put a rev counter on Chainsaw.$Equipped creature gets +X/+0, where X is the number of rev counters on Chainsaw.$Equip {3}| Charred Foyer // Warped Space|Duskmourn: House of Horror|129|M|{3}{R}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$At the beginning of your upkeep, exile the top card of your library. You may play that card this turn.$Warped Space${4}{R}{R}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Once each turn, you may pay {0} rather than pay the mana cost for a spell you cast from exile.| Clockwork Percussionist|Duskmourn: House of Horror|130|C|{R}|Artifact Creature - Monkey Toy|1|1|Haste$When Clockwork Percussionist dies, exile the top card of your library. You may play it until the end of your next turn.| @@ -54128,6 +54136,7 @@ Ripchain Razorkin|Duskmourn: House of Horror|154|C|{3}{R}|Creature - Human Berse The Rollercrusher Ride|Duskmourn: House of Horror|155|M|{X}{2}{R}|Legendary Enchantment|||Delirium -- If a source you control would deal noncombat damage to a permanent or player while there are four or more card types among cards in your graveyard, it deals double that damage instead.$When The Rollercrusher Ride enters, it deals X damage to each of up to X target creatures.| Scorching Dragonfire|Duskmourn: House of Horror|156|C|{1}{R}|Instant|||Scorching Dragonfire deals 3 damage to target creature or planeswalker. If that creature or planeswalker would die this turn, exile it instead.| Screaming Nemesis|Duskmourn: House of Horror|157|M|{2}{R}|Creature - Spirit|3|3|Haste$Whenever Screaming Nemesis is dealt damage, it deals that much damage to any other target. If a player is dealt damage this way, they can't gain life for the rest of the game.| +Ticket Booth // Tunnel of Hate|Duskmourn: House of Horror|158|C|{2}{R}|Enchantment|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, manifest dread.$Tunnel of Hate${4}{R}{R}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Whenever you attack, target attacking creature gains double strike until end of turn.| Trial of Agony|Duskmourn: House of Horror|159|U|{R}|Sorcery|||Choose two target creatures controlled by the same opponent. That player chooses one of those creatures. Trial of Agony deals 5 damage to that creature, and the other can't block this turn.| Turn Inside Out|Duskmourn: House of Horror|160|C|{R}|Instant|||Target creature gets +3/+0 until end of turn. When it dies this turn, manifest dread.| Untimely Malfunction|Duskmourn: House of Horror|161|U|{1}{R}|Instant|||Choose one --$* Destroy target artifact.$* Change the target of target spell or ability with a single target.$* One or two target creatures can't block this turn.| @@ -54160,6 +54169,7 @@ Kona, Rescue Beastie|Duskmourn: House of Horror|187|R|{3}{G}|Legendary Creature Leyline of Mutation|Duskmourn: House of Horror|188|R|{2}{G}{G}|Enchantment|||If Leyline of Mutation is in your opening hand, you may begin the game with it on the battlefield.$You may pay {W}{U}{B}{R}{G} rather than pay the mana cost for spells that you cast.| Manifest Dread|Duskmourn: House of Horror|189|C|{1}{G}|Sorcery|||Manifest dread.| Moldering Gym // Weight Room|Duskmourn: House of Horror|190|C|{2}{G}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, search your library for a basic land card, put it onto the battlefield tapped, then shuffle.$Weight Room${5}{G}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, manifest dread, then put three +1/+1 counters on that creature.| +Monstrous Emergence|Duskmourn: House of Horror|191|C|{1}{G}|Sorcery|||As an additional cost to cast this spell, choose a creature you control or reveal a creature card from your hand.$Monstrous Emergence deals damage equal to the power of the creature you chose or the card you revealed to target creature.| Omnivorous Flytrap|Duskmourn: House of Horror|192|R|{2}{G}|Creature - Plant|2|4|Delirium -- Whenever Omnivorous Flytrap enters or attacks, if there are four or more card types among cards in your graveyard, distribute two +1/+1 counters among one or two target creatures. Then if there are six or more card types among cards in your graveyard, double the number of +1/+1 counters on those creatures.| Overgrown Zealot|Duskmourn: House of Horror|193|U|{1}{G}|Creature - Elf Druid|0|4|{T}: Add one mana of any color.${T}: Add two mana of any one color. Spend this mana only to turn permanents face up.| Overlord of the Hauntwoods|Duskmourn: House of Horror|194|M|{3}{G}{G}|Enchantment Creature - Avatar Horror|6|5|Impending 4--{1}{G}{G}$Whenever Overlord of the Hauntwoods enters or attacks, create a tapped colorless land token named Everywhere that is every basic land type.| @@ -54173,7 +54183,7 @@ Twitching Doll|Duskmourn: House of Horror|201|R|{1}{G}|Artifact Creature - Spide Tyvar, the Pummeler|Duskmourn: House of Horror|202|M|{1}{G}{G}|Legendary Creature - Elf Warrior|3|3|Tap another untapped creature you control: Tyvar, the Pummeler gains indestructible until end of turn. Tap it.${3}{G}{G}: Creatures you control get +X/+X until end of turn, where X is the greatest power among creatures you control.| Under the Skin|Duskmourn: House of Horror|203|U|{2}{G}|Sorcery|||Manifest dread.$You may return a permanent card from your graveyard to your hand.| Valgavoth's Onslaught|Duskmourn: House of Horror|204|R|{X}{X}{G}|Sorcery|||Manifest dread X times, then put X +1/+1 counters on each of those creatures.| -Walk-in Closet // Forgotten Cellar|Duskmourn: House of Horror|205|M|{2}{G}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$You may play land cards from your graveyard.$Forgotten Cellar${3}{G}{G}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, you may cast spells from your graveyard this turn, and if a card would be put into your graveyard from anywhere this turn, exile it instead.| +Walk-in Closet // Forgotten Cellar|Duskmourn: House of Horror|205|M|{2}{G}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$You may play lands from your graveyard.$Forgotten Cellar${3}{G}{G}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, you may cast spells from your graveyard this turn, and if a card would be put into your graveyard from anywhere this turn, exile it instead.| Wary Watchdog|Duskmourn: House of Horror|206|C|{1}{G}|Creature - Dog|3|1|When Wary Watchdog enters or dies, surveil 1.| Wickerfolk Thresher|Duskmourn: House of Horror|207|U|{3}{G}|Artifact Creature - Scarecrow|5|4|Delirium -- Whenever Wickerfolk Thresher attacks, if there are four or more card types among cards in your graveyard, look at the top card of your library. If it's a land card, you may put it onto the battlefield. If you don't put the card onto the battlefield, put it into your hand.| Arabella, Abandoned Doll|Duskmourn: House of Horror|208|U|{R}{W}|Legendary Artifact Creature - Toy|1|3|Whenever Arabella, Abandoned Doll attacks, it deals X damage to each opponent and you gain X life, where X is the number of creatures you control with power 2 or less.| @@ -54197,10 +54207,12 @@ Peer Past the Veil|Duskmourn: House of Horror|226|R|{2}{R}{G}|Instant|||Discard Restricted Office // Lecture Hall|Duskmourn: House of Horror|227|R|{2}{W}{W}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this room, destroy all creatures with power 3 or greater.$Lecture Hall${5}{U}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Other permanents you control have hexproof.| Rip, Spawn Hunter|Duskmourn: House of Horror|228|R|{2}{G}{W}|Legendary Creature - Human Survivor|4|4|Survival -- At the beginning of your second main phase, if Rip, Spawn Hunter is tapped, reveal the top X cards of your library, where X is its power. Put any number of creature and/or Vehicle cards with different powers from among them into your hand. Put the rest on the bottom of your library in a random order.| Rite of the Moth|Duskmourn: House of Horror|229|U|{1}{W}{B}{B}|Sorcery|||Return target creature card from your graveyard to the battlefield with a finality counter on it.$Flashback {3}{W}{W}{B}| +Roaring Furnace // Steaming Sauna|Duskmourn: House of Horror|230|R|{1}{R}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, this Room deals damage equal to the number of cards in your hand to target creature an opponent controls.$Steaming Sauna${3}{U}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$You have no maximum hand size. At the beginning of your end step, draw a card.| Sawblade Skinripper|Duskmourn: House of Horror|231|U|{1}{B}{R}|Creature - Human Assassin|3|2|Menace${2}, Sacrifice another creature or enchantment: Put a +1/+1 counter on Sawblade Skinripper.$At the beginning of your end step, if you sacrificed one or more permanents this turn, Sawblade Skinripper deals that much damage to any target.| Shrewd Storyteller|Duskmourn: House of Horror|232|U|{1}{G}{W}|Creature - Human Survivor|3|3|Survival -- At the beginning of your second main phase, if Shrewd Storyteller is tapped, put a +1/+1 counter on target creature.| +Shroudstomper|Duskmourn: House of Horror|233|U|{3}{W}{W}{B}{B}|Creature - Elemental|5|5|Deathtouch Whenever Shroudstomper enters or attacks, each opponent loses 2 life. You gain 2 life and draw a card.| Skullsnap Nuisance|Duskmourn: House of Horror|234|U|{U}{B}|Creature - Insect Skeleton|1|4|Flying$Eerie -- Whenever an enchantment you control enters and whenever you fully unlock a Room, surveil 1.| -Smoky Lounge // Misty Salon|Duskmourn: House of Horror|235|U|{2}{R}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$At the beginning of your first main phase, add {R}{R}. Spend this mana only to cast Room spells and unlock doors.$Misty Salon${3}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, you create an X/X blue Spirit creature token with flying, where X is the number of unlocked doors among Rooms you control.| +Smoky Lounge // Misty Salon|Duskmourn: House of Horror|235|U|{2}{R}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$At the beginning of your first main phase, add {R}{R}. Spend this mana only to cast Room spells and unlock doors.$Misty Salon${3}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, create an X/X blue Spirit creature token with flying, where X is the number of unlocked doors among Rooms you control.| The Swarmweaver|Duskmourn: House of Horror|236|R|{2}{B}{G}|Legendary Artifact Creature - Scarecrow|2|3|When The Swarmweaver enters, create two 1/1 black and green Insect creature tokens with flying.$Delirium -- As long as there are four or more card types among cards in your graveyard, Insects and Spiders you control get +1/+1 and have deathtouch.| Undead Sprinter|Duskmourn: House of Horror|237|R|{B}{R}|Creature - Zombie|2|2|Trample, haste$You may cast Undead Sprinter from your graveyard if a non-Zombie creature died this turn. If you do, Undead Sprinter enters with a +1/+1 counter on it.| Victor, Valgavoth's Seneschal|Duskmourn: House of Horror|238|R|{1}{W}{B}|Legendary Creature - Human Warlock|3|3|Eerie -- Whenever an enchantment you control enters and whenever you fully unlock a Room, surveil 2 if this is the first time this ability has resolved this turn. If it's the second time, each opponent discards a card. If it's the third time, put a creature card from a graveyard onto the battlefield under your control.| @@ -54242,7 +54254,6 @@ Island|Duskmourn: House of Horror|279|C||Basic Land - Island|||({T}: Add {U}.)| Swamp|Duskmourn: House of Horror|281|C||Basic Land - Swamp|||({T}: Add {B}.)| Mountain|Duskmourn: House of Horror|283|C||Basic Land - Mountain|||({T}: Add {R}.)| Forest|Duskmourn: House of Horror|285|C||Basic Land - Forest|||({T}: Add {G}.)| -Roaring Furnace // Steaming Sauna|Duskmourn: House of Horror|343|R|{1}{R}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, this Room deals damage equal to the number of cards in your hand to target creature an opponent controls.$Steaming Sauna${3}{U}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$You have no maximum hand size. At the beginning of your end step, draw a card.| Felidar Savior|Foundations|12|C|{3}{W}|Creature - Cat Beast|2|3|Lifelink$When this creature enters, put a +1/+1 counter on each of up to two other target creatures you control.| Helpful Hunter|Foundations|16|C|{1}{W}|Creature - Cat|1|1|When this creature enters, draw a card.| Prideful Parent|Foundations|21|C|{2}{W}|Creature - Cat|2|2|Vigilance$When this creature enters, create a 1/1 white Cat creature token.| From 4f3e831fba480ee88706815255a00f890b7242fa Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 13 Sep 2024 19:51:39 -0400 Subject: [PATCH 02/62] [DSK] Implement Shroudstomper --- Mage.Sets/src/mage/cards/s/Shroudstomper.java | 47 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 48 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/Shroudstomper.java diff --git a/Mage.Sets/src/mage/cards/s/Shroudstomper.java b/Mage.Sets/src/mage/cards/s/Shroudstomper.java new file mode 100644 index 00000000000..a1404030401 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/Shroudstomper.java @@ -0,0 +1,47 @@ +package mage.cards.s; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.GainLifeEffect; +import mage.abilities.effects.common.LoseLifeOpponentsEffect; +import mage.abilities.keyword.DeathtouchAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Shroudstomper extends CardImpl { + + public Shroudstomper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{W}{W}{B}{B}"); + + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Whenever Shroudstomper enters or attacks each opponent loses 2 life. You gain 2 life and draw a card. + Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new LoseLifeOpponentsEffect(2)); + ability.addEffect(new GainLifeEffect(2)); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy("and")); + this.addAbility(ability); + } + + private Shroudstomper(final Shroudstomper card) { + super(card); + } + + @Override + public Shroudstomper copy() { + return new Shroudstomper(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index db9ccba4914..800822940f7 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -171,6 +171,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Shardmage's Rescue", 29, Rarity.UNCOMMON, mage.cards.s.ShardmagesRescue.class)); cards.add(new SetCardInfo("Shepherding Spirits", 31, Rarity.COMMON, mage.cards.s.ShepherdingSpirits.class)); cards.add(new SetCardInfo("Shrewd Storyteller", 232, Rarity.UNCOMMON, mage.cards.s.ShrewdStoryteller.class)); + cards.add(new SetCardInfo("Shroudstomper", 233, Rarity.UNCOMMON, mage.cards.s.Shroudstomper.class)); cards.add(new SetCardInfo("Silent Hallcreeper", 72, Rarity.RARE, mage.cards.s.SilentHallcreeper.class)); cards.add(new SetCardInfo("Skullsnap Nuisance", 234, Rarity.UNCOMMON, mage.cards.s.SkullsnapNuisance.class)); cards.add(new SetCardInfo("Slavering Branchsnapper", 198, Rarity.COMMON, mage.cards.s.SlaveringBranchsnapper.class)); From 665d5a8b63c5c928231ffcf0158075184b0098b7 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 13 Sep 2024 19:54:40 -0400 Subject: [PATCH 03/62] [DSK] Implement Boilerbilges Ripper --- .../src/mage/cards/b/BoilerbilgesRipper.java | 48 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 49 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/b/BoilerbilgesRipper.java diff --git a/Mage.Sets/src/mage/cards/b/BoilerbilgesRipper.java b/Mage.Sets/src/mage/cards/b/BoilerbilgesRipper.java new file mode 100644 index 00000000000..e8058a95dfe --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BoilerbilgesRipper.java @@ -0,0 +1,48 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.delayed.ReflexiveTriggeredAbility; +import mage.abilities.costs.common.SacrificeTargetCost; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.effects.common.DoWhenCostPaid; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.common.TargetAnyTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BoilerbilgesRipper extends CardImpl { + + public BoilerbilgesRipper(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{4}{R}"); + + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.ASSASSIN); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Boilerbilges Ripper enters, you may sacrifice another creature or enchantment. When you do, Boilerbilges Ripper deals 2 damage to any target. + ReflexiveTriggeredAbility ability = new ReflexiveTriggeredAbility(new DamageTargetEffect(2), false); + ability.addTarget(new TargetAnyTarget()); + this.addAbility(new EntersBattlefieldTriggeredAbility(new DoWhenCostPaid( + ability, new SacrificeTargetCost(StaticFilters.FILTER_PERMANENT_ANOTHER_CREATURE_OR_ENCHANTMENT), + "Sacrifice another creature or enchantment?" + ))); + } + + private BoilerbilgesRipper(final BoilerbilgesRipper card) { + super(card); + } + + @Override + public BoilerbilgesRipper copy() { + return new BoilerbilgesRipper(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 800822940f7..fc4f53ea7ba 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -37,6 +37,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Betrayer's Bargain", 126, Rarity.UNCOMMON, mage.cards.b.BetrayersBargain.class)); cards.add(new SetCardInfo("Blazemire Verge", 256, Rarity.RARE, mage.cards.b.BlazemireVerge.class)); cards.add(new SetCardInfo("Bleeding Woods", 257, Rarity.COMMON, mage.cards.b.BleedingWoods.class)); + cards.add(new SetCardInfo("Boilerbilges Ripper", 127, Rarity.COMMON, mage.cards.b.BoilerbilgesRipper.class)); cards.add(new SetCardInfo("Break Down the Door", 170, Rarity.UNCOMMON, mage.cards.b.BreakDownTheDoor.class)); cards.add(new SetCardInfo("Broodspinner", 211, Rarity.UNCOMMON, mage.cards.b.Broodspinner.class)); cards.add(new SetCardInfo("Cackling Slasher", 85, Rarity.COMMON, mage.cards.c.CacklingSlasher.class)); From e675328d76373a3de79451fd9faf60e72b486cf5 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 13 Sep 2024 19:57:12 -0400 Subject: [PATCH 04/62] [DSK] Implement Seized from Slumber --- .../src/mage/cards/s/SeizedFromSlumber.java | 53 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 54 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/SeizedFromSlumber.java diff --git a/Mage.Sets/src/mage/cards/s/SeizedFromSlumber.java b/Mage.Sets/src/mage/cards/s/SeizedFromSlumber.java new file mode 100644 index 00000000000..7acb0401eb5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SeizedFromSlumber.java @@ -0,0 +1,53 @@ +package mage.cards.s; + +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceTargetsPermanentCondition; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.cost.SpellCostReductionSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Zone; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.TappedPredicate; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SeizedFromSlumber extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("a tapped creature"); + + static { + filter.add(TappedPredicate.TAPPED); + } + + private static final Condition condition = new SourceTargetsPermanentCondition(filter); + + public SeizedFromSlumber(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{4}{W}"); + + // This spell costs {3} less to cast if it targets a tapped creature. + this.addAbility(new SimpleStaticAbility( + Zone.ALL, new SpellCostReductionSourceEffect(3, condition).setCanWorksOnStackOnly(true) + ).setRuleAtTheTop(true)); + + // Destroy target creature. + this.getSpellAbility().addEffect(new DestroyTargetEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private SeizedFromSlumber(final SeizedFromSlumber card) { + super(card); + } + + @Override + public SeizedFromSlumber copy() { + return new SeizedFromSlumber(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index fc4f53ea7ba..99f89f58678 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -169,6 +169,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Scorching Dragonfire", 156, Rarity.COMMON, mage.cards.s.ScorchingDragonfire.class)); cards.add(new SetCardInfo("Scrabbling Skullcrab", 71, Rarity.UNCOMMON, mage.cards.s.ScrabblingSkullcrab.class)); cards.add(new SetCardInfo("Screaming Nemesis", 157, Rarity.MYTHIC, mage.cards.s.ScreamingNemesis.class)); + cards.add(new SetCardInfo("Seized from Slumber", 28, Rarity.COMMON, mage.cards.s.SeizedFromSlumber.class)); cards.add(new SetCardInfo("Shardmage's Rescue", 29, Rarity.UNCOMMON, mage.cards.s.ShardmagesRescue.class)); cards.add(new SetCardInfo("Shepherding Spirits", 31, Rarity.COMMON, mage.cards.s.ShepherdingSpirits.class)); cards.add(new SetCardInfo("Shrewd Storyteller", 232, Rarity.UNCOMMON, mage.cards.s.ShrewdStoryteller.class)); From 5809b7ab7ebb9d79df29d01e3e57578ac7275fc6 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 13 Sep 2024 20:12:56 -0400 Subject: [PATCH 05/62] [DSK] Implement The Mindskinner --- .../src/mage/cards/t/TheMindskinner.java | 83 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 84 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/t/TheMindskinner.java diff --git a/Mage.Sets/src/mage/cards/t/TheMindskinner.java b/Mage.Sets/src/mage/cards/t/TheMindskinner.java new file mode 100644 index 00000000000..98580d540aa --- /dev/null +++ b/Mage.Sets/src/mage/cards/t/TheMindskinner.java @@ -0,0 +1,83 @@ +package mage.cards.t; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.PreventionEffectImpl; +import mage.abilities.keyword.CantBeBlockedSourceAbility; +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.game.Game; +import mage.game.events.GameEvent; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class TheMindskinner extends CardImpl { + + public TheMindskinner(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{U}{U}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.NIGHTMARE); + this.power = new MageInt(10); + this.toughness = new MageInt(1); + + // The Mindskinner can't be blocked. + this.addAbility(new CantBeBlockedSourceAbility()); + + // If a source you control would deal damage to an opponent, prevent that damage and each opponent mills that many cards. + this.addAbility(new SimpleStaticAbility(new TheMindskinnerEffect())); + } + + private TheMindskinner(final TheMindskinner card) { + super(card); + } + + @Override + public TheMindskinner copy() { + return new TheMindskinner(this); + } +} + +class TheMindskinnerEffect extends PreventionEffectImpl { + + TheMindskinnerEffect() { + super(Duration.WhileOnBattlefield); + staticText = "if a source you control would deal damage to an opponent, " + + "prevent that damage and each opponent mills that many cards."; + } + + private TheMindskinnerEffect(final TheMindskinnerEffect effect) { + super(effect); + } + + @Override + public TheMindskinnerEffect copy() { + return new TheMindskinnerEffect(this); + } + + @Override + public boolean replaceEvent(GameEvent event, Ability source, Game game) { + preventDamageAction(event, source, game); + for (UUID playerId : game.getOpponents(source.getControllerId())) { + Player player = game.getPlayer(playerId); + if (player != null) { + player.millCards(event.getAmount(), source, game); + } + } + return true; + } + + @Override + public boolean applies(GameEvent event, Ability source, Game game) { + return super.applies(event, source, game) && source.isControlledBy(game.getControllerId(event.getSourceId())); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 99f89f58678..6df2736c70c 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -187,6 +187,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Swamp", 274, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Terramorphic Expanse", 269, Rarity.COMMON, mage.cards.t.TerramorphicExpanse.class)); cards.add(new SetCardInfo("The Jolly Balloon Man", 219, Rarity.RARE, mage.cards.t.TheJollyBalloonMan.class)); + cards.add(new SetCardInfo("The Mindskinner", 66, Rarity.RARE, mage.cards.t.TheMindskinner.class)); cards.add(new SetCardInfo("The Swarmweaver", 236, Rarity.RARE, mage.cards.t.TheSwarmweaver.class)); cards.add(new SetCardInfo("The Wandering Rescuer", 41, Rarity.MYTHIC, mage.cards.t.TheWanderingRescuer.class)); cards.add(new SetCardInfo("Thornspire Verge", 270, Rarity.RARE, mage.cards.t.ThornspireVerge.class)); From e382951e163c1e1f48eb22029aafe066da9b4581 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 13 Sep 2024 20:22:24 -0400 Subject: [PATCH 06/62] [DSK] Implement Sheltered by Ghosts --- .../src/mage/cards/s/ShelteredByGhosts.java | 66 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 67 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/ShelteredByGhosts.java diff --git a/Mage.Sets/src/mage/cards/s/ShelteredByGhosts.java b/Mage.Sets/src/mage/cards/s/ShelteredByGhosts.java new file mode 100644 index 00000000000..376a6085e88 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/ShelteredByGhosts.java @@ -0,0 +1,66 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.ExileUntilSourceLeavesEffect; +import mage.abilities.effects.common.continuous.BoostEnchantedEffect; +import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.abilities.keyword.LifelinkAbility; +import mage.abilities.keyword.WardAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AttachmentType; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ShelteredByGhosts extends CardImpl { + + public ShelteredByGhosts(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{W}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature you control + TargetPermanent auraTarget = new TargetControlledCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When Sheltered by Ghosts enters, exile target nonland permanent an opponent controls until Sheltered by Ghosts leaves the battlefield. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileUntilSourceLeavesEffect()); + ability.addTarget(new TargetPermanent(StaticFilters.FILTER_OPPONENTS_PERMANENT_NON_LAND)); + this.addAbility(ability); + + // Enchanted creature gets +1/+0 and has lifelink and ward {2}. + ability = new SimpleStaticAbility(new BoostEnchantedEffect(1, 0)); + ability.addEffect(new GainAbilityAttachedEffect( + LifelinkAbility.getInstance(), AttachmentType.AURA + ).setText("and has lifelink")); + ability.addEffect(new GainAbilityAttachedEffect( + new WardAbility(new GenericManaCost(2)), AttachmentType.AURA + ).setText("and ward {2}")); + this.addAbility(ability); + } + + private ShelteredByGhosts(final ShelteredByGhosts card) { + super(card); + } + + @Override + public ShelteredByGhosts copy() { + return new ShelteredByGhosts(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 6df2736c70c..c80719c8940 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -171,6 +171,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Screaming Nemesis", 157, Rarity.MYTHIC, mage.cards.s.ScreamingNemesis.class)); cards.add(new SetCardInfo("Seized from Slumber", 28, Rarity.COMMON, mage.cards.s.SeizedFromSlumber.class)); cards.add(new SetCardInfo("Shardmage's Rescue", 29, Rarity.UNCOMMON, mage.cards.s.ShardmagesRescue.class)); + cards.add(new SetCardInfo("Sheltered by Ghosts", 30, Rarity.UNCOMMON, mage.cards.s.ShelteredByGhosts.class)); cards.add(new SetCardInfo("Shepherding Spirits", 31, Rarity.COMMON, mage.cards.s.ShepherdingSpirits.class)); cards.add(new SetCardInfo("Shrewd Storyteller", 232, Rarity.UNCOMMON, mage.cards.s.ShrewdStoryteller.class)); cards.add(new SetCardInfo("Shroudstomper", 233, Rarity.UNCOMMON, mage.cards.s.Shroudstomper.class)); From 22df21d15d8469836a343d57c7843bb07a9c16e2 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 13 Sep 2024 20:35:49 -0400 Subject: [PATCH 07/62] [DSK] Implement Found Footage --- Mage.Sets/src/mage/cards/f/FoundFootage.java | 50 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + ...pponentFaceDownCreaturesAnyTimeEffect.java | 34 ++++++++++--- 3 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/f/FoundFootage.java diff --git a/Mage.Sets/src/mage/cards/f/FoundFootage.java b/Mage.Sets/src/mage/cards/f/FoundFootage.java new file mode 100644 index 00000000000..299600caf3e --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FoundFootage.java @@ -0,0 +1,50 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.costs.common.SacrificeSourceCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.LookAtOpponentFaceDownCreaturesAnyTimeEffect; +import mage.abilities.effects.keyword.SurveilEffect; +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 java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FoundFootage extends CardImpl { + + public FoundFootage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + this.subtype.add(SubType.CLUE); + + // You may look at face-down creatures your opponents control any time. + this.addAbility(new SimpleStaticAbility( + new LookAtOpponentFaceDownCreaturesAnyTimeEffect(Duration.WhileOnBattlefield, TargetController.OPPONENT) + )); + + // {2}, Sacrifice Found Footage: Surveil 2, then draw a card. + Ability ability = new SimpleActivatedAbility(new SurveilEffect(2), new GenericManaCost(2)); + ability.addCost(new SacrificeSourceCost()); + ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy(", then")); + this.addAbility(ability); + } + + private FoundFootage(final FoundFootage card) { + super(card); + } + + @Override + public FoundFootage copy() { + return new FoundFootage(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index c80719c8940..40833ea6990 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -91,6 +91,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Floodfarm Verge", 259, Rarity.RARE, mage.cards.f.FloodfarmVerge.class)); cards.add(new SetCardInfo("Floodpits Drowner", 59, Rarity.UNCOMMON, mage.cards.f.FloodpitsDrowner.class)); cards.add(new SetCardInfo("Forest", 276, Rarity.LAND, mage.cards.basiclands.Forest.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Found Footage", 246, Rarity.COMMON, mage.cards.f.FoundFootage.class)); cards.add(new SetCardInfo("Frantic Strength", 179, Rarity.COMMON, mage.cards.f.FranticStrength.class)); cards.add(new SetCardInfo("Friendly Ghost", 12, Rarity.COMMON, mage.cards.f.FriendlyGhost.class)); cards.add(new SetCardInfo("Friendly Teddy", 247, Rarity.COMMON, mage.cards.f.FriendlyTeddy.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtOpponentFaceDownCreaturesAnyTimeEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtOpponentFaceDownCreaturesAnyTimeEffect.java index ebc7eb65b35..e766b3ca395 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtOpponentFaceDownCreaturesAnyTimeEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/LookAtOpponentFaceDownCreaturesAnyTimeEffect.java @@ -14,24 +14,28 @@ import mage.players.Player; * @author notgreat */ public class LookAtOpponentFaceDownCreaturesAnyTimeEffect extends ContinuousEffectImpl { - private static final FilterCreaturePermanent filter = new FilterCreaturePermanent("face-down creatures you don't control"); - static { - filter.add(FaceDownPredicate.instance); - filter.add(TargetController.NOT_YOU.getControllerPredicate()); - } + private final FilterCreaturePermanent filter; public LookAtOpponentFaceDownCreaturesAnyTimeEffect() { this(Duration.WhileOnBattlefield); } public LookAtOpponentFaceDownCreaturesAnyTimeEffect(Duration duration) { + this(duration, TargetController.NOT_YOU); + } + + public LookAtOpponentFaceDownCreaturesAnyTimeEffect(Duration duration, TargetController targetController) { super(duration, Layer.PlayerEffects, SubLayer.NA, Outcome.Benefit); - staticText = (duration.toString().isEmpty() ? "" : duration.toString() + ", ") + "you may look at face-down creatures you don't control any time"; + staticText = makeText(duration, targetController); + filter = new FilterCreaturePermanent(); + filter.add(FaceDownPredicate.instance); + filter.add(targetController.getControllerPredicate()); } protected LookAtOpponentFaceDownCreaturesAnyTimeEffect(final LookAtOpponentFaceDownCreaturesAnyTimeEffect effect) { super(effect); + this.filter = effect.filter.copy(); } //Based on LookAtTopCardOfLibraryAnyTimeEffect @@ -56,4 +60,22 @@ public class LookAtOpponentFaceDownCreaturesAnyTimeEffect extends ContinuousEffe public LookAtOpponentFaceDownCreaturesAnyTimeEffect copy() { return new LookAtOpponentFaceDownCreaturesAnyTimeEffect(this); } + + private static String makeText(Duration duration, TargetController targetController) { + StringBuilder sb = new StringBuilder(); + if (!duration.toString().isEmpty()) { + sb.append(duration); + sb.append(", "); + } + sb.append("you may look at face-down creatures "); + switch (targetController) { + case NOT_YOU: + sb.append("you don't control "); + break; + case OPPONENT: + sb.append("your opponents control "); + } + sb.append("any time"); + return sb.toString(); + } } From 50f892b93584099d797c15425a46bd616740857b Mon Sep 17 00:00:00 2001 From: theelk801 Date: Fri, 13 Sep 2024 20:39:37 -0400 Subject: [PATCH 08/62] small change to conditional mode choice --- Mage/src/main/java/mage/abilities/Modes.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java index bff1f93530a..f4c1ffc56bf 100644 --- a/Mage/src/main/java/mage/abilities/Modes.java +++ b/Mage/src/main/java/mage/abilities/Modes.java @@ -245,7 +245,7 @@ public class Modes extends LinkedHashMap implements Copyable // use case: make more modes chooseable if (moreCondition != null && moreCondition.apply(game, source)) { - realMaxModes = this.moreLimit; + realMaxModes = Math.min(this.moreLimit, this.size()); } // use case: limit max modes by opponents (example: choose one or more... each mode must target a different player) From d923f2941d0b2a733d6a5db32533410a93cfe2fa Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Fri, 13 Sep 2024 20:44:38 -0400 Subject: [PATCH 09/62] Implement Impending mechanic (#12865) * implement Impending mechanic * add initial test * add more tests * small fix --- .../mage/cards/o/OverlordOfTheBalemurk.java | 2 +- .../cards/o/OverlordOfTheBoilerbilges.java | 2 +- .../mage/cards/o/OverlordOfTheFloodpits.java | 2 +- .../mage/cards/o/OverlordOfTheHauntwoods.java | 2 +- .../mage/cards/o/OverlordOfTheMistmoors.java | 2 +- .../src/mage/sets/DuskmournHouseOfHorror.java | 2 - .../abilities/keywords/ImpendingTest.java | 226 ++++++++++++++++++ .../costs/AlternativeSourceCostsImpl.java | 6 +- .../abilities/keyword/ImpendingAbility.java | 124 +++++++++- 9 files changed, 348 insertions(+), 20 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ImpendingTest.java diff --git a/Mage.Sets/src/mage/cards/o/OverlordOfTheBalemurk.java b/Mage.Sets/src/mage/cards/o/OverlordOfTheBalemurk.java index 7c8cff9fcf5..607a656af69 100644 --- a/Mage.Sets/src/mage/cards/o/OverlordOfTheBalemurk.java +++ b/Mage.Sets/src/mage/cards/o/OverlordOfTheBalemurk.java @@ -36,7 +36,7 @@ public final class OverlordOfTheBalemurk extends CardImpl { this.toughness = new MageInt(5); // Impending 5--{1}{B} - this.addAbility(new ImpendingAbility("{1}{B}", 5)); + this.addAbility(new ImpendingAbility(5, "{1}{B}")); // Whenever Overlord of the Balemurk enters or attacks, mill four cards, then you may return a non-Avatar creature card or a planeswalker card from your graveyard to your hand. Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new MillCardsControllerEffect(4)); diff --git a/Mage.Sets/src/mage/cards/o/OverlordOfTheBoilerbilges.java b/Mage.Sets/src/mage/cards/o/OverlordOfTheBoilerbilges.java index 9a1850c0e0b..fd7a76736c8 100644 --- a/Mage.Sets/src/mage/cards/o/OverlordOfTheBoilerbilges.java +++ b/Mage.Sets/src/mage/cards/o/OverlordOfTheBoilerbilges.java @@ -27,7 +27,7 @@ public final class OverlordOfTheBoilerbilges extends CardImpl { this.toughness = new MageInt(5); // Impending 4--{2}{R}{R} - this.addAbility(new ImpendingAbility("{2}{R}{R}")); + this.addAbility(new ImpendingAbility(4, "{2}{R}{R}")); // Whenever Overlord of the Boilerbilges enters or attacks, it deals 4 damage to any target. Ability ability = new EntersBattlefieldOrAttacksSourceTriggeredAbility(new DamageTargetEffect(4)); diff --git a/Mage.Sets/src/mage/cards/o/OverlordOfTheFloodpits.java b/Mage.Sets/src/mage/cards/o/OverlordOfTheFloodpits.java index ac54cf2aad6..45bd2989f43 100644 --- a/Mage.Sets/src/mage/cards/o/OverlordOfTheFloodpits.java +++ b/Mage.Sets/src/mage/cards/o/OverlordOfTheFloodpits.java @@ -26,7 +26,7 @@ public final class OverlordOfTheFloodpits extends CardImpl { this.toughness = new MageInt(3); // Impending 4--{1}{U}{U} - this.addAbility(new ImpendingAbility("{1}{U}{U}")); + this.addAbility(new ImpendingAbility(4, "{1}{U}{U}")); // Flying this.addAbility(FlyingAbility.getInstance()); diff --git a/Mage.Sets/src/mage/cards/o/OverlordOfTheHauntwoods.java b/Mage.Sets/src/mage/cards/o/OverlordOfTheHauntwoods.java index 63df6a010ef..e9c437059f5 100644 --- a/Mage.Sets/src/mage/cards/o/OverlordOfTheHauntwoods.java +++ b/Mage.Sets/src/mage/cards/o/OverlordOfTheHauntwoods.java @@ -26,7 +26,7 @@ public final class OverlordOfTheHauntwoods extends CardImpl { this.toughness = new MageInt(5); // Impending 4--{1}{G}{G} - this.addAbility(new ImpendingAbility("{1}{G}{G}")); + this.addAbility(new ImpendingAbility(4, "{1}{G}{G}")); // Whenever Overlord of the Hauntwoods enters or attacks, create a tapped colorless land token named Everywhere that is every basic land type. this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/o/OverlordOfTheMistmoors.java b/Mage.Sets/src/mage/cards/o/OverlordOfTheMistmoors.java index c3124d29013..8a8309acc0a 100644 --- a/Mage.Sets/src/mage/cards/o/OverlordOfTheMistmoors.java +++ b/Mage.Sets/src/mage/cards/o/OverlordOfTheMistmoors.java @@ -26,7 +26,7 @@ public final class OverlordOfTheMistmoors extends CardImpl { this.toughness = new MageInt(6); // Impending 4--{2}{W}{W} - this.addAbility(new ImpendingAbility("{2}{W}{W}")); + this.addAbility(new ImpendingAbility(4, "{2}{W}{W}")); // Whenever Overlord of the Mistmoors enters or attacks, create two 2/1 white Insect creature tokens with flying. this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new CreateTokenEffect(new InsectWhiteToken(), 2))); diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 40833ea6990..93bb23b19b9 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -224,7 +224,5 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Winter, Misanthropic Guide", 240, Rarity.RARE, mage.cards.w.WinterMisanthropicGuide.class)); cards.add(new SetCardInfo("Withering Torment", 124, Rarity.UNCOMMON, mage.cards.w.WitheringTorment.class)); cards.add(new SetCardInfo("Zimone, All-Questioning", 241, Rarity.RARE, mage.cards.z.ZimoneAllQuestioning.class)); - - cards.removeIf(setCardInfo -> setCardInfo.getName().startsWith("Overlord")); } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ImpendingTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ImpendingTest.java new file mode 100644 index 00000000000..33b65e5ec3d --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/ImpendingTest.java @@ -0,0 +1,226 @@ +package org.mage.test.cards.abilities.keywords; + +import mage.abilities.keyword.ImpendingAbility; +import mage.constants.CardType; +import mage.constants.PhaseStep; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.game.permanent.Permanent; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author TheElk801 + */ +public class ImpendingTest extends CardTestPlayerBase { + + private static final String hauntwoods = "Overlord of the Hauntwoods"; + + public void assertHasImpending(String name, boolean hasAbility) { + Permanent permanent = getPermanent(name); + Assert.assertEquals( + "Should" + (hasAbility ? "" : "n't") + " have Impending ability", + hasAbility, permanent.getAbilities(currentGame).containsClass(ImpendingAbility.class) + ); + } + + @Test + public void testCastRegular() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 5); + addCard(Zone.HAND, playerA, hauntwoods); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hauntwoods); + setChoice(playerA, "Cast with no"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, hauntwoods, 1); + assertType(hauntwoods, CardType.ENCHANTMENT, true); + assertType(hauntwoods, CardType.CREATURE, true); + assertSubtype(hauntwoods, SubType.AVATAR); + assertSubtype(hauntwoods, SubType.HORROR); + assertCounterCount(playerA, hauntwoods, CounterType.TIME, 0); + assertPowerToughness(playerA, hauntwoods, 6, 5); + assertHasImpending(hauntwoods, true); + + assertPermanentCount(playerA, "Everywhere", 1); + } + + @Test + public void testCastImpending() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.HAND, playerA, hauntwoods); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hauntwoods); + setChoice(playerA, "Cast with Impending"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, hauntwoods, 1); + assertType(hauntwoods, CardType.ENCHANTMENT, true); + assertType(hauntwoods, CardType.CREATURE, false); + assertCounterCount(playerA, hauntwoods, CounterType.TIME, 4); + assertHasImpending(hauntwoods, true); + + assertPermanentCount(playerA, "Everywhere", 1); + } + + @Test + public void testImpendingRemoveCounter() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.HAND, playerA, hauntwoods); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hauntwoods); + setChoice(playerA, "Cast with Impending"); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, hauntwoods, 1); + assertType(hauntwoods, CardType.ENCHANTMENT, true); + assertType(hauntwoods, CardType.CREATURE, false); + assertCounterCount(playerA, hauntwoods, CounterType.TIME, 4 - 1); + assertHasImpending(hauntwoods, true); + + assertPermanentCount(playerA, "Everywhere", 1); + } + + @Test + public void testCastImpendingRemoveAllCounters() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.HAND, playerA, hauntwoods); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hauntwoods); + setChoice(playerA, "Cast with Impending"); + + setStrictChooseMode(true); + setStopAt(8, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, hauntwoods, 1); + assertType(hauntwoods, CardType.ENCHANTMENT, true); + assertType(hauntwoods, CardType.CREATURE, true); + assertSubtype(hauntwoods, SubType.AVATAR); + assertSubtype(hauntwoods, SubType.HORROR); + assertCounterCount(playerA, hauntwoods, CounterType.TIME, 0); + assertPowerToughness(playerA, hauntwoods, 6, 5); + assertHasImpending(hauntwoods, false); + + assertPermanentCount(playerA, "Everywhere", 1); + } + + private static final String hexmage = "Vampire Hexmage"; + + @Test + public void testCastImpendingHexmage() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.BATTLEFIELD, playerA, hexmage); + addCard(Zone.HAND, playerA, hauntwoods); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hauntwoods); + setChoice(playerA, "Cast with Impending"); + + activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "Sacrifice", hauntwoods); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, hauntwoods, 1); + assertType(hauntwoods, CardType.ENCHANTMENT, true); + assertType(hauntwoods, CardType.CREATURE, true); + assertSubtype(hauntwoods, SubType.AVATAR); + assertSubtype(hauntwoods, SubType.HORROR); + assertCounterCount(playerA, hauntwoods, CounterType.TIME, 0); + assertPowerToughness(playerA, hauntwoods, 6, 5); + assertHasImpending(hauntwoods, true); + + assertPermanentCount(playerA, "Everywhere", 1); + } + + @Test + public void testCastImpendingHexmageNextTurn() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.BATTLEFIELD, playerA, hexmage); + addCard(Zone.HAND, playerA, hauntwoods); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hauntwoods); + setChoice(playerA, "Cast with Impending"); + + activateAbility(1, PhaseStep.BEGIN_COMBAT, playerA, "Sacrifice", hauntwoods); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, hauntwoods, 1); + assertType(hauntwoods, CardType.ENCHANTMENT, true); + assertType(hauntwoods, CardType.CREATURE, true); + assertSubtype(hauntwoods, SubType.AVATAR); + assertSubtype(hauntwoods, SubType.HORROR); + assertCounterCount(playerA, hauntwoods, CounterType.TIME, 0); + assertPowerToughness(playerA, hauntwoods, 6, 5); + assertHasImpending(hauntwoods, false); + + assertPermanentCount(playerA, "Everywhere", 1); + } + + private static final String solemnity = "Solemnity"; + + @Test + public void testCastImpendingSolemnity() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.BATTLEFIELD, playerA, solemnity); + addCard(Zone.HAND, playerA, hauntwoods); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hauntwoods); + setChoice(playerA, "Cast with Impending"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, hauntwoods, 1); + assertType(hauntwoods, CardType.ENCHANTMENT, true); + assertType(hauntwoods, CardType.CREATURE, true); + assertSubtype(hauntwoods, SubType.AVATAR); + assertSubtype(hauntwoods, SubType.HORROR); + assertCounterCount(playerA, hauntwoods, CounterType.TIME, 0); + assertPowerToughness(playerA, hauntwoods, 6, 5); + assertHasImpending(hauntwoods, true); + + assertPermanentCount(playerA, "Everywhere", 1); + } + + @Test + public void testCastImpendingSolemnityNextTurn() { + addCard(Zone.BATTLEFIELD, playerA, "Forest", 3); + addCard(Zone.BATTLEFIELD, playerA, solemnity); + addCard(Zone.HAND, playerA, hauntwoods); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, hauntwoods); + setChoice(playerA, "Cast with Impending"); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.POSTCOMBAT_MAIN); + execute(); + + assertPermanentCount(playerA, hauntwoods, 1); + assertType(hauntwoods, CardType.ENCHANTMENT, true); + assertType(hauntwoods, CardType.CREATURE, true); + assertSubtype(hauntwoods, SubType.AVATAR); + assertSubtype(hauntwoods, SubType.HORROR); + assertCounterCount(playerA, hauntwoods, CounterType.TIME, 0); + assertPowerToughness(playerA, hauntwoods, 6, 5); + assertHasImpending(hauntwoods, false); + + assertPermanentCount(playerA, "Everywhere", 1); + } +} diff --git a/Mage/src/main/java/mage/abilities/costs/AlternativeSourceCostsImpl.java b/Mage/src/main/java/mage/abilities/costs/AlternativeSourceCostsImpl.java index 0f947e5568e..7657a0a336d 100644 --- a/Mage/src/main/java/mage/abilities/costs/AlternativeSourceCostsImpl.java +++ b/Mage/src/main/java/mage/abilities/costs/AlternativeSourceCostsImpl.java @@ -30,11 +30,15 @@ public abstract class AlternativeSourceCostsImpl extends StaticAbility implement } protected AlternativeSourceCostsImpl(String name, String reminderText, Cost cost) { + this(name, reminderText, cost, name); + } + + protected AlternativeSourceCostsImpl(String name, String reminderText, Cost cost, String activationKey) { super(Zone.ALL, null); this.name = name; this.reminderText = reminderText; this.alternativeCost = new AlternativeCostImpl<>(name, reminderText, cost); - this.activationKey = getActivationKey(name); + this.activationKey = getActivationKey(activationKey); } protected AlternativeSourceCostsImpl(final AlternativeSourceCostsImpl ability) { diff --git a/Mage/src/main/java/mage/abilities/keyword/ImpendingAbility.java b/Mage/src/main/java/mage/abilities/keyword/ImpendingAbility.java index a6594965d07..4d02211948b 100644 --- a/Mage/src/main/java/mage/abilities/keyword/ImpendingAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/ImpendingAbility.java @@ -1,12 +1,33 @@ package mage.abilities.keyword; import mage.abilities.Ability; +import mage.abilities.common.BeginningOfEndStepTriggeredAbility; +import mage.abilities.common.EntersBattlefieldAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.condition.Condition; +import mage.abilities.condition.common.SourceHasCounterCondition; import mage.abilities.costs.AlternativeSourceCostsImpl; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.AddContinuousEffectToGame; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.effects.common.counter.RemoveCounterSourceEffect; +import mage.constants.*; +import mage.counters.CounterType; import mage.game.Game; +import mage.game.permanent.Permanent; import mage.util.CardUtil; +import java.util.stream.Collectors; + /** - * TODO: Implement this + * "Impending N–[cost]" is a keyword that represents multiple abilities. + * The official rules are as follows: + * (a) You may choose to pay [cost] rather than pay this spell's mana cost. + * (b) If you chose to pay this spell's impending cost, it enters the battlefield with N time counters on it. + * (c) As long as this permanent has a time counter on it, if it was cast for its impending cost, it's not a creature. + * (d) At the beginning of your end step, if this permanent was cast for its impending cost, remove a time counter from it. Then if it has no time counters on it, it loses impending. * * @author TheElk801 */ @@ -16,14 +37,24 @@ public class ImpendingAbility extends AlternativeSourceCostsImpl { private static final String IMPENDING_REMINDER = "If you cast this spell for its impending cost, " + "it enters with %s time counters and isn't a creature until the last is removed. " + "At the beginning of your end step, remove a time counter from it."; + private static final Condition counterCondition = new SourceHasCounterCondition(CounterType.TIME, 0, 0); - public ImpendingAbility(String manaString) { - this(manaString, 4); - } - - public ImpendingAbility(String manaString, int amount) { - super(IMPENDING_KEYWORD + ' ' + amount, String.format(IMPENDING_REMINDER, CardUtil.numberToText(amount)), manaString); + public ImpendingAbility(int amount, String manaString) { + super(IMPENDING_KEYWORD + ' ' + amount, String.format(IMPENDING_REMINDER, CardUtil.numberToText(amount)), new ManaCostsImpl<>(manaString), IMPENDING_KEYWORD); this.setRuleAtTheTop(true); + this.addSubAbility(new EntersBattlefieldAbility(new ConditionalOneShotEffect( + new AddCountersSourceEffect(CounterType.TIME.createInstance(amount)), ImpendingCondition.instance, "" + ), "").setRuleVisible(false)); + this.addSubAbility(new SimpleStaticAbility(new ImpendingAbilityTypeEffect()).setRuleVisible(false)); + Ability ability = new BeginningOfEndStepTriggeredAbility( + new RemoveCounterSourceEffect(CounterType.TIME.createInstance()), + TargetController.YOU, ImpendingCondition.instance, false + ); + ability.addEffect(new ConditionalOneShotEffect( + new AddContinuousEffectToGame(new ImpendingAbilityRemoveEffect()), + counterCondition, "Then if it has no time counters on it, it loses impending" + )); + this.addSubAbility(ability.setRuleVisible(false)); } private ImpendingAbility(final ImpendingAbility ability) { @@ -35,12 +66,81 @@ public class ImpendingAbility extends AlternativeSourceCostsImpl { return new ImpendingAbility(this); } - @Override - public boolean isAvailable(Ability source, Game game) { - return true; - } - public static String getActivationKey() { return getActivationKey(IMPENDING_KEYWORD); } } + +enum ImpendingCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + return CardUtil.checkSourceCostsTagExists(game, source, ImpendingAbility.getActivationKey()); + } +} + +class ImpendingAbilityTypeEffect extends ContinuousEffectImpl { + + ImpendingAbilityTypeEffect() { + super(Duration.WhileOnBattlefield, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Detriment); + staticText = "As long as this permanent has a time counter on it, if it was cast for its impending cost, it's not a creature."; + } + + private ImpendingAbilityTypeEffect(final ImpendingAbilityTypeEffect effect) { + super(effect); + } + + @Override + public ImpendingAbilityTypeEffect copy() { + return new ImpendingAbilityTypeEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + if (!ImpendingCondition.instance.apply(game, source)) { + return false; + } + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent.getCounters(game).getCount(CounterType.TIME) < 1) { + return false; + } + permanent.removeCardType(game, CardType.CREATURE); + permanent.removeAllCreatureTypes(game); + return true; + } +} + +class ImpendingAbilityRemoveEffect extends ContinuousEffectImpl { + + ImpendingAbilityRemoveEffect() { + super(Duration.Custom, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.LoseAbility); + } + + private ImpendingAbilityRemoveEffect(final ImpendingAbilityRemoveEffect effect) { + super(effect); + } + + @Override + public ImpendingAbilityRemoveEffect copy() { + return new ImpendingAbilityRemoveEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + discard(); + return false; + } + permanent.removeAbilities( + permanent + .getAbilities(game) + .stream() + .filter(ImpendingAbility.class::isInstance) + .collect(Collectors.toList()), + source.getSourceId(), game + ); + return true; + } +} From d5b23705c756f40ba0d7d06ce029a1adbe505c3e Mon Sep 17 00:00:00 2001 From: xenohedron Date: Fri, 13 Sep 2024 20:23:03 -0400 Subject: [PATCH 10/62] refactor: use getter method when setting paw prints, rather than direct access --- Mage.Sets/src/mage/cards/s/SeasonOfGathering.java | 4 ++-- Mage.Sets/src/mage/cards/s/SeasonOfLoss.java | 7 +++---- Mage.Sets/src/mage/cards/s/SeasonOfTheBold.java | 2 +- Mage.Sets/src/mage/cards/s/SeasonOfTheBurrow.java | 2 +- Mage.Sets/src/mage/cards/s/SeasonOfWeaving.java | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Mage.Sets/src/mage/cards/s/SeasonOfGathering.java b/Mage.Sets/src/mage/cards/s/SeasonOfGathering.java index 96b73cd7a23..628ee0dad99 100644 --- a/Mage.Sets/src/mage/cards/s/SeasonOfGathering.java +++ b/Mage.Sets/src/mage/cards/s/SeasonOfGathering.java @@ -44,7 +44,7 @@ public final class SeasonOfGathering extends CardImpl { // {P} -- Put a +1/+1 counter on a creature you control. It gains vigilance and trample until end of turn. this.getSpellAbility().addEffect(new SeasonOfGatheringCounterEffect()); - this.spellAbility.getModes().getMode().withPawPrintValue(1); + this.getSpellAbility().getModes().getMode().withPawPrintValue(1); // {P}{P} -- Choose artifact or enchantment. Destroy all permanents of the chosen type. Mode mode2 = new Mode(new SeasonOfGatheringRemovalEffect()); @@ -159,4 +159,4 @@ class SeasonOfGatheringRemovalEffect extends OneShotEffect { game.informPlayers(controller.getLogName() + " has chosen " + choiceImpl.getChoiceKey()); return new DestroyAllEffect(filter).apply(game, source); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/s/SeasonOfLoss.java b/Mage.Sets/src/mage/cards/s/SeasonOfLoss.java index 119e67a245d..581db0f199d 100644 --- a/Mage.Sets/src/mage/cards/s/SeasonOfLoss.java +++ b/Mage.Sets/src/mage/cards/s/SeasonOfLoss.java @@ -1,12 +1,9 @@ package mage.cards.s; -import java.util.UUID; - import mage.abilities.Ability; import mage.abilities.Mode; import mage.abilities.dynamicvalue.DynamicValue; import mage.abilities.dynamicvalue.common.CardsInControllerGraveyardCount; -import mage.abilities.dynamicvalue.common.GreatestPowerAmongControlledCreaturesValue; import mage.abilities.effects.Effect; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.LoseLifeOpponentsEffect; @@ -18,6 +15,8 @@ import mage.filter.StaticFilters; import mage.game.Game; import mage.watchers.common.CreaturesDiedWatcher; +import java.util.UUID; + /** * * @author jimga150 @@ -35,7 +34,7 @@ public final class SeasonOfLoss extends CardImpl { // {P} -- Each player sacrifices a creature. this.getSpellAbility().addEffect(new SacrificeAllEffect(1, StaticFilters.FILTER_PERMANENT_CREATURE)); - this.spellAbility.getModes().getMode().withPawPrintValue(1); + this.getSpellAbility().getModes().getMode().withPawPrintValue(1); // {P}{P} -- Draw a card for each creature you controlled that died this turn. Mode mode2 = new Mode(new DrawCardSourceControllerEffect(SeasonOfLossValue.instance)); diff --git a/Mage.Sets/src/mage/cards/s/SeasonOfTheBold.java b/Mage.Sets/src/mage/cards/s/SeasonOfTheBold.java index 1e4cca5e2cd..ba56af51cb7 100644 --- a/Mage.Sets/src/mage/cards/s/SeasonOfTheBold.java +++ b/Mage.Sets/src/mage/cards/s/SeasonOfTheBold.java @@ -34,7 +34,7 @@ public final class SeasonOfTheBold extends CardImpl { // {P} -- Create a tapped Treasure token. this.getSpellAbility().addEffect(new CreateTokenEffect(new TreasureToken(), 1, true)); - this.spellAbility.getModes().getMode().withPawPrintValue(1); + this.getSpellAbility().getModes().getMode().withPawPrintValue(1); // {P}{P} -- Exile the top two cards of your library. Until the end of your next turn, you may play them. Mode mode2 = new Mode(new ExileTopXMayPlayUntilEffect(2, Duration.UntilEndOfYourNextTurn)); diff --git a/Mage.Sets/src/mage/cards/s/SeasonOfTheBurrow.java b/Mage.Sets/src/mage/cards/s/SeasonOfTheBurrow.java index 529371b0a3d..3966d11d8f4 100644 --- a/Mage.Sets/src/mage/cards/s/SeasonOfTheBurrow.java +++ b/Mage.Sets/src/mage/cards/s/SeasonOfTheBurrow.java @@ -40,7 +40,7 @@ public final class SeasonOfTheBurrow extends CardImpl { // {P} -- Create a 1/1 white Rabbit creature token. this.getSpellAbility().addEffect(new CreateTokenEffect(new RabbitToken())); - this.spellAbility.getModes().getMode().withPawPrintValue(1); + this.getSpellAbility().getModes().getMode().withPawPrintValue(1); // {P}{P} -- Exile target nonland permanent. Its controller draws a card. Mode mode2 = new Mode(new ExileTargetEffect()); diff --git a/Mage.Sets/src/mage/cards/s/SeasonOfWeaving.java b/Mage.Sets/src/mage/cards/s/SeasonOfWeaving.java index 26f62b14ef5..b5999376830 100644 --- a/Mage.Sets/src/mage/cards/s/SeasonOfWeaving.java +++ b/Mage.Sets/src/mage/cards/s/SeasonOfWeaving.java @@ -44,7 +44,7 @@ public final class SeasonOfWeaving extends CardImpl { // {P} -- Draw a card. this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(1)); - this.spellAbility.getModes().getMode().withPawPrintValue(1); + this.getSpellAbility().getModes().getMode().withPawPrintValue(1); // {P}{P} -- Choose an artifact or creature you control. Create a token that's a copy of it. Mode mode2 = new Mode(new SeasonOfWeavingEffect()); From df80856e795e6d16a8e464fb1fa24487ca14e4c1 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Fri, 13 Sep 2024 21:37:33 -0400 Subject: [PATCH 11/62] fix a few text discrepancies --- Mage.Sets/src/mage/cards/b/BloodSeeker.java | 2 +- Mage.Sets/src/mage/cards/f/FoundFootage.java | 2 +- Mage.Sets/src/mage/cards/s/SeasonOfTheBold.java | 3 ++- Mage.Sets/src/mage/cards/s/SeasonOfWeaving.java | 3 ++- Mage.Sets/src/mage/cards/s/SuturePriest.java | 3 +-- Mage.Sets/src/mage/cards/v/ViolentUrge.java | 2 +- .../EntersBattlefieldOrAttacksSourceTriggeredAbility.java | 1 + .../main/java/mage/abilities/costs/AlternativeCostImpl.java | 5 ++++- 8 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BloodSeeker.java b/Mage.Sets/src/mage/cards/b/BloodSeeker.java index bd2e6f1b7bd..f730bb572ce 100644 --- a/Mage.Sets/src/mage/cards/b/BloodSeeker.java +++ b/Mage.Sets/src/mage/cards/b/BloodSeeker.java @@ -81,6 +81,6 @@ class BloodSeekerTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever a creature enters the battlefield under an opponent's control, you may have that player lose 1 life."; + return "Whenever a creature an opponent controls enters, you may have that player lose 1 life."; } } diff --git a/Mage.Sets/src/mage/cards/f/FoundFootage.java b/Mage.Sets/src/mage/cards/f/FoundFootage.java index 299600caf3e..f3f425f863f 100644 --- a/Mage.Sets/src/mage/cards/f/FoundFootage.java +++ b/Mage.Sets/src/mage/cards/f/FoundFootage.java @@ -33,7 +33,7 @@ public final class FoundFootage extends CardImpl { )); // {2}, Sacrifice Found Footage: Surveil 2, then draw a card. - Ability ability = new SimpleActivatedAbility(new SurveilEffect(2), new GenericManaCost(2)); + Ability ability = new SimpleActivatedAbility(new SurveilEffect(2, false), new GenericManaCost(2)); ability.addCost(new SacrificeSourceCost()); ability.addEffect(new DrawCardSourceControllerEffect(1).concatBy(", then")); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/s/SeasonOfTheBold.java b/Mage.Sets/src/mage/cards/s/SeasonOfTheBold.java index ba56af51cb7..3a285a0a450 100644 --- a/Mage.Sets/src/mage/cards/s/SeasonOfTheBold.java +++ b/Mage.Sets/src/mage/cards/s/SeasonOfTheBold.java @@ -37,7 +37,8 @@ public final class SeasonOfTheBold extends CardImpl { this.getSpellAbility().getModes().getMode().withPawPrintValue(1); // {P}{P} -- Exile the top two cards of your library. Until the end of your next turn, you may play them. - Mode mode2 = new Mode(new ExileTopXMayPlayUntilEffect(2, Duration.UntilEndOfYourNextTurn)); + Mode mode2 = new Mode(new ExileTopXMayPlayUntilEffect(2, Duration.UntilEndOfYourNextTurn) + .withTextOptions("them", false)); this.getSpellAbility().addMode(mode2.withPawPrintValue(2)); // {P}{P}{P} -- Until the end of your next turn, whenever you cast a spell, Season of the Bold deals 2 damage to up to one target creature. diff --git a/Mage.Sets/src/mage/cards/s/SeasonOfWeaving.java b/Mage.Sets/src/mage/cards/s/SeasonOfWeaving.java index b5999376830..0b34b7a8755 100644 --- a/Mage.Sets/src/mage/cards/s/SeasonOfWeaving.java +++ b/Mage.Sets/src/mage/cards/s/SeasonOfWeaving.java @@ -51,7 +51,8 @@ public final class SeasonOfWeaving extends CardImpl { this.getSpellAbility().addMode(mode2.withPawPrintValue(2)); // {P}{P}{P} -- Return each nonland, nontoken permanent to its owner's hand. - Mode mode3 = new Mode(new ReturnToHandFromBattlefieldAllEffect(filter)); + Mode mode3 = new Mode(new ReturnToHandFromBattlefieldAllEffect(filter) + .setText("return each nonland, nontoken permanent to its owner's hand")); this.getSpellAbility().addMode(mode3.withPawPrintValue(3)); } diff --git a/Mage.Sets/src/mage/cards/s/SuturePriest.java b/Mage.Sets/src/mage/cards/s/SuturePriest.java index 32b5435e573..7e3a63bf8d6 100644 --- a/Mage.Sets/src/mage/cards/s/SuturePriest.java +++ b/Mage.Sets/src/mage/cards/s/SuturePriest.java @@ -1,4 +1,3 @@ - package mage.cards.s; import java.util.UUID; @@ -90,6 +89,6 @@ class SuturePriestSecondTriggeredAbility extends TriggeredAbilityImpl { @Override public String getRule() { - return "Whenever a creature enters the battlefield under an opponent's control, you may have that player lose 1 life."; + return "Whenever a creature an opponent controls enters, you may have that player lose 1 life."; } } diff --git a/Mage.Sets/src/mage/cards/v/ViolentUrge.java b/Mage.Sets/src/mage/cards/v/ViolentUrge.java index 5f4c6814789..dda8d7c1cdf 100644 --- a/Mage.Sets/src/mage/cards/v/ViolentUrge.java +++ b/Mage.Sets/src/mage/cards/v/ViolentUrge.java @@ -39,7 +39,7 @@ public final class ViolentUrge extends CardImpl { new AddContinuousEffectToGame(new GainAbilityTargetEffect(DoubleStrikeAbility.getInstance())), DeliriumCondition.instance, AbilityWord.DELIRIUM.formatWord() + "If there are four or more " + "card types among cards in your graveyard, that creature gains double strike until end of turn" - )); + ).concatBy("
")); this.getSpellAbility().addHint(CardTypesInGraveyardHint.YOU); } diff --git a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldOrAttacksSourceTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldOrAttacksSourceTriggeredAbility.java index c073bc3e6d8..b18b1f29b2a 100644 --- a/Mage/src/main/java/mage/abilities/common/EntersBattlefieldOrAttacksSourceTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/EntersBattlefieldOrAttacksSourceTriggeredAbility.java @@ -19,6 +19,7 @@ public class EntersBattlefieldOrAttacksSourceTriggeredAbility extends TriggeredA public EntersBattlefieldOrAttacksSourceTriggeredAbility(Effect effect, boolean optional) { super(Zone.BATTLEFIELD, effect, optional); setTriggerPhrase("Whenever {this} enters or attacks, "); + this.withRuleTextReplacement(true); } protected EntersBattlefieldOrAttacksSourceTriggeredAbility(final EntersBattlefieldOrAttacksSourceTriggeredAbility ability) { diff --git a/Mage/src/main/java/mage/abilities/costs/AlternativeCostImpl.java b/Mage/src/main/java/mage/abilities/costs/AlternativeCostImpl.java index 1bccc0d6348..3d86769ed51 100644 --- a/Mage/src/main/java/mage/abilities/costs/AlternativeCostImpl.java +++ b/Mage/src/main/java/mage/abilities/costs/AlternativeCostImpl.java @@ -51,7 +51,10 @@ public class AlternativeCostImpl> extends Costs if (onlyCost) { return getText(); } else { - return (name != null ? name : "") + (isMana ? " " : "—") + getText() + (isMana ? "" : '.'); + String costName = (name != null ? name : ""); + String delimiter = (!isMana || (!costName.isEmpty() && costName.substring(costName.length() - 1).matches("\\d"))) + ? "—" : " "; + return costName + delimiter + getText() + (isMana ? "" : '.'); } } From 5db01266d2911c9aa5add31d0a51b73be10eaafc Mon Sep 17 00:00:00 2001 From: jimga150 Date: Fri, 13 Sep 2024 21:39:07 -0400 Subject: [PATCH 12/62] Fix Party Thrasher play from exile effect (#12836) * Fix Party Thrasher * Add test for playable duration --- Mage.Sets/src/mage/cards/p/PartyThrasher.java | 4 +- .../cards/single/mh3/PartyThrasherTest.java | 110 ++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/PartyThrasherTest.java diff --git a/Mage.Sets/src/mage/cards/p/PartyThrasher.java b/Mage.Sets/src/mage/cards/p/PartyThrasher.java index bbce966d953..b9fb6fbd44c 100644 --- a/Mage.Sets/src/mage/cards/p/PartyThrasher.java +++ b/Mage.Sets/src/mage/cards/p/PartyThrasher.java @@ -21,7 +21,7 @@ import mage.game.ExileZone; import mage.game.Game; import mage.players.Player; import mage.target.TargetCard; -import mage.target.targetpointer.FixedTargets; +import mage.target.targetpointer.FixedTarget; import mage.util.CardUtil; import java.util.UUID; @@ -118,7 +118,7 @@ class PartyThrasherEffect extends OneShotEffect { exileZone.setCleanupOnEndTurn(true); game.getExile().moveToAnotherZone(card, game, exileZone); game.addEffect(new PlayFromNotOwnHandZoneTargetEffect(Zone.EXILED, Duration.EndOfTurn) - .setTargetPointer(new FixedTargets(cards, game)), source); + .setTargetPointer(new FixedTarget(card, game)), source); return true; } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/PartyThrasherTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/PartyThrasherTest.java new file mode 100644 index 00000000000..0f8b2d7051f --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/mh3/PartyThrasherTest.java @@ -0,0 +1,110 @@ +package org.mage.test.cards.single.mh3; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Assert; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author jimga150 + */ +public class PartyThrasherTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.p.PartyThrasher Party Thrasher} {1}{R} + * Creature — Lizard Wizard + * Noncreature spells you cast from exile have convoke. + * (Each creature you tap while casting a noncreature spell from exile + * pays for {1} or one mana of that creature’s color.) + * At the beginning of your first main phase, you may discard a card. + * If you do, exile the top two cards of your library, then choose one of them. You may play that card this turn. + * 1/4 + */ + private static final String partyThrasher = "Party Thrasher"; + + @Test + public void testPlayFromExile() { + skipInitShuffling(); + + addCard(Zone.LIBRARY, playerA, "Memnite"); + addCard(Zone.LIBRARY, playerA, "Accorder's Shield"); + addCard(Zone.BATTLEFIELD, playerA, partyThrasher, 1); + addCard(Zone.HAND, playerA, "Memnarch", 1); + + setChoice(playerA, "Yes"); // Discard card? + setChoice(playerA, "Memnarch"); // Which card to pitch? + setChoice(playerA, "Memnite"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Memnite"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertGraveyardCount(playerA, "Memnarch", 1); + assertPermanentCount(playerA, "Memnite", 1); + assertExileCount(playerA, "Accorder's Shield", 1); + } + + @Test + public void testTryUnplayableCard() { + skipInitShuffling(); + + addCard(Zone.LIBRARY, playerA, "Memnite"); + addCard(Zone.LIBRARY, playerA, "Accorder's Shield"); + addCard(Zone.BATTLEFIELD, playerA, partyThrasher, 1); + addCard(Zone.HAND, playerA, "Memnarch", 1); + + setChoice(playerA, "Yes"); // Discard card? + setChoice(playerA, "Memnarch"); // Which card to pitch? + setChoice(playerA, "Memnite"); + + castSpell(1, PhaseStep.POSTCOMBAT_MAIN, playerA, "Accorder's Shield"); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + + try { + execute(); + Assert.fail("Should have failed to execute, as Accorder's Shield should not be castable"); + } catch (Throwable e) { + if (!e.getMessage().contains("Can't find ability to activate command: Cast Accorder's Shield")) { + Assert.fail("must throw error about missing ability:\n" + e.getMessage()); + } + } + + } + + @Test + public void testTryPlayFromExileNextTurn() { + skipInitShuffling(); + + addCard(Zone.LIBRARY, playerA, "Memnite"); + addCard(Zone.LIBRARY, playerA, "Accorder's Shield"); + addCard(Zone.BATTLEFIELD, playerA, partyThrasher, 1); + addCard(Zone.HAND, playerA, "Memnarch", 1); + + setChoice(playerA, "Yes"); // Discard card? + setChoice(playerA, "Memnarch"); // Which card to pitch? + setChoice(playerA, "Memnite"); + + setChoice(playerA, "No"); // Discard card? (next turn) + + castSpell(3, PhaseStep.POSTCOMBAT_MAIN, playerA, "Memnite"); + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + + try { + execute(); + Assert.fail("Should have failed to execute, as Memnite should not be castable the turn after Party Thrasher Exiled it."); + } catch (Throwable e) { + if (!e.getMessage().contains("Can't find ability to activate command: Cast Memnite")) { + Assert.fail("must throw error about missing ability:\n" + e.getMessage()); + } + } + + } + +} From 13a2e20f6630b2f2c36ae5595cc651283ffad76a Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Fri, 13 Sep 2024 22:46:58 -0400 Subject: [PATCH 13/62] Fix Adrestia to use existing effect, use {this} in text. --- Mage.Sets/src/mage/cards/a/Adrestia.java | 31 +++--------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/Adrestia.java b/Mage.Sets/src/mage/cards/a/Adrestia.java index 86f4638d9c7..c0df6888563 100644 --- a/Mage.Sets/src/mage/cards/a/Adrestia.java +++ b/Mage.Sets/src/mage/cards/a/Adrestia.java @@ -10,6 +10,7 @@ import mage.abilities.condition.Condition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.AddCardSubTypeSourceEffect; import mage.abilities.hint.ConditionHint; import mage.abilities.hint.Hint; import mage.constants.*; @@ -45,8 +46,8 @@ public final class Adrestia extends CardImpl { // Whenever Adrestia attacks, if an Assassin crewed it this turn, draw a card. Adrestia becomes an Assassin in addition to its other types until end of turn. Ability ability = new ConditionalInterveningIfTriggeredAbility( new AttacksTriggeredAbility(new DrawCardSourceControllerEffect(1), false), - condition, "Whenever Adrestia attacks, if an Assassin crewed it this turn, draw a card. Adrestia becomes an Assassin in addition to its other types until end of turn."); - ability.addEffect(new AdrestiaEffect()); + condition, "Whenever {this} attacks, if an Assassin crewed it this turn, draw a card. {this} becomes an Assassin in addition to its other types until end of turn."); + ability.addEffect(new AddCardSubTypeSourceEffect(Duration.EndOfTurn, true, SubType.ASSASSIN)); ability.addHint(AdrestiaCondition.getHint()); this.addAbility(ability, new AdrestiaWatcher()); @@ -65,32 +66,6 @@ public final class Adrestia extends CardImpl { } } -class AdrestiaEffect extends ContinuousEffectImpl { - - public AdrestiaEffect() { - super(Duration.EndOfTurn, Layer.TypeChangingEffects_4, SubLayer.NA, Outcome.Benefit); - } - - protected AdrestiaEffect(final AdrestiaEffect effect) { - super(effect); - } - - @Override - public AdrestiaEffect copy() { - return new AdrestiaEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Permanent permanent = game.getPermanent(source.getSourceId()); - if (permanent == null) { - return false; - } - permanent.addSubType(game, SubType.ASSASSIN); - return true; - } -} - enum AdrestiaCondition implements Condition { instance; private static final Hint hint = new ConditionHint(instance, "an Assassin crewed it this turn"); From ccd656c22a8641863881d12a4952a7d2edfc4154 Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Fri, 13 Sep 2024 22:56:26 -0400 Subject: [PATCH 14/62] Fix Aveline de Grandpre and Necropolis Regent to use SavedDamageValue. --- .../src/mage/cards/a/AvelineDeGrandpre.java | 77 ++++--------------- .../src/mage/cards/n/NecropolisRegent.java | 60 +++------------ 2 files changed, 24 insertions(+), 113 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AvelineDeGrandpre.java b/Mage.Sets/src/mage/cards/a/AvelineDeGrandpre.java index c04d54c072e..255d72eaf8b 100644 --- a/Mage.Sets/src/mage/cards/a/AvelineDeGrandpre.java +++ b/Mage.Sets/src/mage/cards/a/AvelineDeGrandpre.java @@ -2,28 +2,19 @@ package mage.cards.a; import java.util.UUID; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; -import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.counter.AddCountersTargetEffect; -import mage.constants.SubType; -import mage.constants.SuperType; +import mage.constants.*; import mage.abilities.keyword.DeathtouchAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.keyword.DisguiseAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Zone; import mage.counters.CounterType; import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.predicate.mageobject.AbilityPredicate; -import mage.game.Game; -import mage.game.events.DamagedPlayerEvent; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.target.targetpointer.FixedTarget; /** * @@ -31,6 +22,13 @@ import mage.target.targetpointer.FixedTarget; */ public final class AvelineDeGrandpre extends CardImpl { + private static final FilterPermanent filter + = new FilterControlledCreaturePermanent("a creature you control with deathtouch"); + + static { + filter.add(new AbilityPredicate(DeathtouchAbility.class)); + } + public AvelineDeGrandpre(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{G}"); @@ -44,7 +42,11 @@ public final class AvelineDeGrandpre extends CardImpl { this.addAbility(DeathtouchAbility.getInstance()); // Whenever a creature you control with deathtouch deals combat damage to a player, put that many +1/+1 counters on that creature. - this.addAbility(new AvelineDeGrandpreTriggeredAbility()); + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance(), SavedDamageValue.MANY), + filter, + false, SetTargetPointer.PERMANENT, true + )); // Disguise {B}{G} this.addAbility(new DisguiseAbility(this, new ManaCostsImpl<>("{B}{G}"))); @@ -60,52 +62,3 @@ public final class AvelineDeGrandpre extends CardImpl { return new AvelineDeGrandpre(this); } } - -class AvelineDeGrandpreTriggeredAbility extends TriggeredAbilityImpl { - - private static final FilterPermanent filter - = new FilterControlledCreaturePermanent("a creature you control with deathtouch"); - - static { - filter.add(new AbilityPredicate(DeathtouchAbility.class)); - } - - public AvelineDeGrandpreTriggeredAbility() { - // Copied from Necropolis Regent, I don't know why QUEST counters. - super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.QUEST.createInstance()), false); - } - - private AvelineDeGrandpreTriggeredAbility(final AvelineDeGrandpreTriggeredAbility ability) { - super(ability); - } - - @Override - public AvelineDeGrandpreTriggeredAbility copy() { - return new AvelineDeGrandpreTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (((DamagedPlayerEvent) event).isCombatDamage()) { - Permanent creature = game.getPermanent(event.getSourceId()); - if (creature != null && creature.isControlledBy(controllerId) && filter.match(creature, game)) { - this.getEffects().clear(); - Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance(event.getAmount())); - effect.setTargetPointer(new FixedTarget(creature.getId(), game)); - this.addEffect(effect); - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever a creature you control with deathtouch deals combat damage to a player, put that many +1/+1 counters on it."; - } -} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/n/NecropolisRegent.java b/Mage.Sets/src/mage/cards/n/NecropolisRegent.java index 39d967d705e..f64f314f3fb 100644 --- a/Mage.Sets/src/mage/cards/n/NecropolisRegent.java +++ b/Mage.Sets/src/mage/cards/n/NecropolisRegent.java @@ -2,22 +2,17 @@ package mage.cards.n; import java.util.UUID; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; -import mage.abilities.effects.Effect; -import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.common.DealsDamageToAPlayerAllTriggeredAbility; +import mage.abilities.dynamicvalue.common.SavedDamageValue; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.constants.SetTargetPointer; import mage.constants.SubType; -import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.DamagedPlayerEvent; -import mage.game.events.GameEvent; -import mage.game.permanent.Permanent; -import mage.target.targetpointer.FixedTarget; +import mage.filter.StaticFilters; /** * @@ -36,7 +31,11 @@ public final class NecropolisRegent extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever a creature you control deals combat damage to a player, put that many +1/+1 counters on it. - this.addAbility(new NecropolisRegentTriggeredAbility()); + this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( + new AddCountersTargetEffect(CounterType.P1P1.createInstance(), SavedDamageValue.MANY), + StaticFilters.FILTER_CONTROLLED_A_CREATURE, + false, SetTargetPointer.PERMANENT, true + )); } private NecropolisRegent(final NecropolisRegent card) { @@ -48,44 +47,3 @@ public final class NecropolisRegent extends CardImpl { return new NecropolisRegent(this); } } - -class NecropolisRegentTriggeredAbility extends TriggeredAbilityImpl { - - public NecropolisRegentTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.QUEST.createInstance()), false); - } - - private NecropolisRegentTriggeredAbility(final NecropolisRegentTriggeredAbility ability) { - super(ability); - } - - @Override - public NecropolisRegentTriggeredAbility copy() { - return new NecropolisRegentTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (((DamagedPlayerEvent) event).isCombatDamage()) { - Permanent creature = game.getPermanent(event.getSourceId()); - if (creature != null && creature.isControlledBy(controllerId)) { - this.getEffects().clear(); - Effect effect = new AddCountersTargetEffect(CounterType.P1P1.createInstance(event.getAmount())); - effect.setTargetPointer(new FixedTarget(creature.getId(), game)); - this.addEffect(effect); - return true; - } - } - return false; - } - - @Override - public String getRule() { - return "Whenever a creature you control deals combat damage to a player, put that many +1/+1 counters on it."; - } -} From ee8ae028d2f5c80fe22d398044eca01c4644452e Mon Sep 17 00:00:00 2001 From: Grath <1895280+Grath@users.noreply.github.com> Date: Fri, 13 Sep 2024 23:35:22 -0400 Subject: [PATCH 15/62] [ACR] Implement Staff of Eden, Vault's Key (#12869) --- .../mage/cards/s/StaffOfEdenVaultsKey.java | 70 +++++++++++++++++++ Mage.Sets/src/mage/sets/AssassinsCreed.java | 1 + 2 files changed, 71 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/StaffOfEdenVaultsKey.java diff --git a/Mage.Sets/src/mage/cards/s/StaffOfEdenVaultsKey.java b/Mage.Sets/src/mage/cards/s/StaffOfEdenVaultsKey.java new file mode 100644 index 00000000000..00db62d13e3 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StaffOfEdenVaultsKey.java @@ -0,0 +1,70 @@ +package mage.cards.s; + +import java.util.UUID; + +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.PermanentsOnBattlefieldCount; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.hint.ValueHint; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.filter.FilterCard; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.NamePredicate; +import mage.filter.predicate.mageobject.PermanentPredicate; +import mage.target.common.TargetCardInGraveyard; + +/** + * + * @author Grath + */ +public final class StaffOfEdenVaultsKey extends CardImpl { + + private static final FilterCard filter = new FilterCard("legendary permanent card not named Staff of Eden, Vault's Key from a graveyard"); + private static final FilterPermanent filter2 = new FilterControlledPermanent("permanent you control but don't own"); + private static final DynamicValue xValue = new PermanentsOnBattlefieldCount(filter2); + private static final Hint hint = new ValueHint("Permanents you control but don't own", xValue); + + static { + filter.add(SuperType.LEGENDARY.getPredicate()); + filter.add(PermanentPredicate.instance); + filter.add(Predicates.not(new NamePredicate("Staff of Eden, Vault's Key"))); + filter2.add(TargetController.NOT_YOU.getOwnerPredicate()); + } + + public StaffOfEdenVaultsKey(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{6}"); + + this.supertype.add(SuperType.LEGENDARY); + + // When Staff of Eden, Vault's Key enters the battlefield, put target legendary permanent card not named Staff of Eden, Vault's Key from a graveyard onto the battlefield under your control. + Ability ability = new EntersBattlefieldTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()); + ability.addTarget(new TargetCardInGraveyard(filter)); + this.addAbility(ability); + + // {T}: Draw a card for each permanent you control but don't own. + ability = new SimpleActivatedAbility(new DrawCardSourceControllerEffect(xValue), new TapSourceCost()); + ability.addHint(hint); + this.addAbility(ability); + } + + private StaffOfEdenVaultsKey(final StaffOfEdenVaultsKey card) { + super(card); + } + + @Override + public StaffOfEdenVaultsKey copy() { + return new StaffOfEdenVaultsKey(this); + } +} diff --git a/Mage.Sets/src/mage/sets/AssassinsCreed.java b/Mage.Sets/src/mage/sets/AssassinsCreed.java index f5dad8fa2cf..3150e3f8845 100644 --- a/Mage.Sets/src/mage/sets/AssassinsCreed.java +++ b/Mage.Sets/src/mage/sets/AssassinsCreed.java @@ -118,6 +118,7 @@ public final class AssassinsCreed extends ExpansionSet { cards.add(new SetCardInfo("Silent Clearing", 115, Rarity.RARE, mage.cards.s.SilentClearing.class)); cards.add(new SetCardInfo("Smoke Bomb", 75, Rarity.UNCOMMON, mage.cards.s.SmokeBomb.class)); cards.add(new SetCardInfo("Spartan Veteran", 292, Rarity.COMMON, mage.cards.s.SpartanVeteran.class)); + cards.add(new SetCardInfo("Staff of Eden, Vault's Key", 76, Rarity.MYTHIC, mage.cards.s.StaffOfEdenVaultsKey.class)); cards.add(new SetCardInfo("Stone Quarry", 300, Rarity.UNCOMMON, mage.cards.s.StoneQuarry.class)); cards.add(new SetCardInfo("Submerged Boneyard", 301, Rarity.UNCOMMON, mage.cards.s.SubmergedBoneyard.class)); cards.add(new SetCardInfo("Sunbaked Canyon", 111, Rarity.RARE, mage.cards.s.SunbakedCanyon.class)); From d5396444519f9439b069f2ba43fa5c5e5159ea19 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 09:55:03 -0400 Subject: [PATCH 16/62] [DSC] update spoiler and reprints --- .../sets/DuskmournHouseOfHorrorCommander.java | 113 ++++++++++++++++- Utils/mtg-cards-data.txt | 119 +++++++++++++++++- 2 files changed, 227 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java index 293ed4ce304..c12c4a2d15d 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java @@ -23,6 +23,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Aesi, Tyrant of Gyre Strait", 210, Rarity.MYTHIC, mage.cards.a.AesiTyrantOfGyreStrait.class)); cards.add(new SetCardInfo("Aether Gale", 109, Rarity.RARE, mage.cards.a.AetherGale.class)); cards.add(new SetCardInfo("Aminatou's Augury", 71, Rarity.RARE, mage.cards.a.AminatousAugury.class)); + cards.add(new SetCardInfo("Arachnogenesis", 169, Rarity.RARE, mage.cards.a.Arachnogenesis.class)); cards.add(new SetCardInfo("Arcane Denial", 110, Rarity.COMMON, mage.cards.a.ArcaneDenial.class)); cards.add(new SetCardInfo("Arcane Sanctum", 259, Rarity.UNCOMMON, mage.cards.a.ArcaneSanctum.class)); cards.add(new SetCardInfo("Arcane Signet", 92, Rarity.COMMON, mage.cards.a.ArcaneSignet.class)); @@ -37,31 +38,52 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Auramancer", 97, Rarity.COMMON, mage.cards.a.Auramancer.class)); cards.add(new SetCardInfo("Azorius Chancery", 261, Rarity.UNCOMMON, mage.cards.a.AzoriusChancery.class)); cards.add(new SetCardInfo("Azorius Signet", 240, Rarity.UNCOMMON, mage.cards.a.AzoriusSignet.class)); + cards.add(new SetCardInfo("Barren Moor", 262, Rarity.UNCOMMON, mage.cards.b.BarrenMoor.class)); + cards.add(new SetCardInfo("Basilisk Collar", 241, Rarity.RARE, mage.cards.b.BasiliskCollar.class)); + cards.add(new SetCardInfo("Bastion of Remembrance", 131, Rarity.UNCOMMON, mage.cards.b.BastionOfRemembrance.class)); cards.add(new SetCardInfo("Beanstalk Giant", 172, Rarity.UNCOMMON, mage.cards.b.BeanstalkGiant.class)); cards.add(new SetCardInfo("Beast Within", 80, Rarity.UNCOMMON, mage.cards.b.BeastWithin.class)); cards.add(new SetCardInfo("Bedevil", 84, Rarity.RARE, mage.cards.b.Bedevil.class)); + cards.add(new SetCardInfo("Binding the Old Gods", 213, Rarity.UNCOMMON, mage.cards.b.BindingTheOldGods.class)); cards.add(new SetCardInfo("Biomass Mutation", 214, Rarity.RARE, mage.cards.b.BiomassMutation.class)); + cards.add(new SetCardInfo("Blackcleave Cliffs", 263, Rarity.RARE, mage.cards.b.BlackcleaveCliffs.class)); + cards.add(new SetCardInfo("Blasphemous Act", 160, Rarity.RARE, mage.cards.b.BlasphemousAct.class)); + cards.add(new SetCardInfo("Blood Artist", 132, Rarity.UNCOMMON, mage.cards.b.BloodArtist.class)); cards.add(new SetCardInfo("Blood Pact", 76, Rarity.COMMON, mage.cards.b.BloodPact.class)); cards.add(new SetCardInfo("Blood Seeker", 77, Rarity.COMMON, mage.cards.b.BloodSeeker.class)); + cards.add(new SetCardInfo("Bloodfell Caves", 264, Rarity.COMMON, mage.cards.b.BloodfellCaves.class)); cards.add(new SetCardInfo("Body of Knowledge", 112, Rarity.RARE, mage.cards.b.BodyOfKnowledge.class)); cards.add(new SetCardInfo("Bojuka Bog", 265, Rarity.COMMON, mage.cards.b.BojukaBog.class)); + cards.add(new SetCardInfo("Braids, Arisen Nightmare", 133, Rarity.RARE, mage.cards.b.BraidsArisenNightmare.class)); cards.add(new SetCardInfo("Brainstone", 242, Rarity.UNCOMMON, mage.cards.b.Brainstone.class)); cards.add(new SetCardInfo("Brainstorm", 113, Rarity.COMMON, mage.cards.b.Brainstorm.class)); + cards.add(new SetCardInfo("Brash Taunter", 161, Rarity.RARE, mage.cards.b.BrashTaunter.class)); cards.add(new SetCardInfo("Burnished Hart", 243, Rarity.UNCOMMON, mage.cards.b.BurnishedHart.class)); cards.add(new SetCardInfo("Cackling Counterpart", 72, Rarity.RARE, mage.cards.c.CacklingCounterpart.class)); + cards.add(new SetCardInfo("Canyon Slough", 266, Rarity.RARE, mage.cards.c.CanyonSlough.class)); + cards.add(new SetCardInfo("Carrion Grub", 134, Rarity.COMMON, mage.cards.c.CarrionGrub.class)); cards.add(new SetCardInfo("Cast Out", 98, Rarity.UNCOMMON, mage.cards.c.CastOut.class)); cards.add(new SetCardInfo("Castle Vantress", 267, Rarity.RARE, mage.cards.c.CastleVantress.class)); cards.add(new SetCardInfo("Caves of Koilos", 268, Rarity.RARE, mage.cards.c.CavesOfKoilos.class)); + cards.add(new SetCardInfo("Cemetery Tampering", 135, Rarity.RARE, mage.cards.c.CemeteryTampering.class)); + cards.add(new SetCardInfo("Chaos Warp", 162, Rarity.RARE, mage.cards.c.ChaosWarp.class)); cards.add(new SetCardInfo("Citanul Hierophants", 81, Rarity.RARE, mage.cards.c.CitanulHierophants.class)); + cards.add(new SetCardInfo("Combustible Gearhulk", 163, Rarity.MYTHIC, mage.cards.c.CombustibleGearhulk.class)); cards.add(new SetCardInfo("Command Tower", 96, Rarity.COMMON, mage.cards.c.CommandTower.class)); cards.add(new SetCardInfo("Commander's Sphere", 244, Rarity.COMMON, mage.cards.c.CommandersSphere.class)); cards.add(new SetCardInfo("Convert to Slime", 37, Rarity.RARE, mage.cards.c.ConvertToSlime.class)); cards.add(new SetCardInfo("Counterspell", 114, Rarity.COMMON, mage.cards.c.Counterspell.class)); + cards.add(new SetCardInfo("Crawling Sensation", 173, Rarity.UNCOMMON, mage.cards.c.CrawlingSensation.class)); cards.add(new SetCardInfo("Crypt Ghast", 368, Rarity.MYTHIC, mage.cards.c.CryptGhast.class)); + cards.add(new SetCardInfo("Culling Ritual", 85, Rarity.RARE, mage.cards.c.CullingRitual.class)); cards.add(new SetCardInfo("Cultivate", 174, Rarity.COMMON, mage.cards.c.Cultivate.class)); cards.add(new SetCardInfo("Damn", 369, Rarity.MYTHIC, mage.cards.d.Damn.class)); + cards.add(new SetCardInfo("Darkmoss Bridge", 269, Rarity.COMMON, mage.cards.d.DarkmossBridge.class)); + cards.add(new SetCardInfo("Deadbridge Chant", 215, Rarity.MYTHIC, mage.cards.d.DeadbridgeChant.class)); + cards.add(new SetCardInfo("Deathcap Cultivator", 175, Rarity.RARE, mage.cards.d.DeathcapCultivator.class)); cards.add(new SetCardInfo("Deathmist Raptor", 176, Rarity.MYTHIC, mage.cards.d.DeathmistRaptor.class)); cards.add(new SetCardInfo("Deathreap Ritual", 86, Rarity.UNCOMMON, mage.cards.d.DeathreapRitual.class)); + cards.add(new SetCardInfo("Decree of Pain", 136, Rarity.RARE, mage.cards.d.DecreeOfPain.class)); cards.add(new SetCardInfo("Deluge of Doom", 18, Rarity.RARE, mage.cards.d.DelugeOfDoom.class)); cards.add(new SetCardInfo("Demolisher Spawn", 31, Rarity.RARE, mage.cards.d.DemolisherSpawn.class)); cards.add(new SetCardInfo("Demon of Fate's Design", 137, Rarity.RARE, mage.cards.d.DemonOfFatesDesign.class)); @@ -69,46 +91,95 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Dig Through Time", 115, Rarity.RARE, mage.cards.d.DigThroughTime.class)); cards.add(new SetCardInfo("Dimir Aqueduct", 270, Rarity.UNCOMMON, mage.cards.d.DimirAqueduct.class)); cards.add(new SetCardInfo("Doomwake Giant", 138, Rarity.RARE, mage.cards.d.DoomwakeGiant.class)); + cards.add(new SetCardInfo("Dragonskull Summit", 271, Rarity.RARE, mage.cards.d.DragonskullSummit.class)); cards.add(new SetCardInfo("Dream Eater", 116, Rarity.MYTHIC, mage.cards.d.DreamEater.class)); cards.add(new SetCardInfo("Drownyard Temple", 272, Rarity.RARE, mage.cards.d.DrownyardTemple.class)); + cards.add(new SetCardInfo("Dryad Arbor", 273, Rarity.RARE, mage.cards.d.DryadArbor.class)); + cards.add(new SetCardInfo("Enchanter's Bane", 164, Rarity.RARE, mage.cards.e.EnchantersBane.class)); cards.add(new SetCardInfo("Entreat the Angels", 99, Rarity.MYTHIC, mage.cards.e.EntreatTheAngels.class)); cards.add(new SetCardInfo("Eureka Moment", 216, Rarity.COMMON, mage.cards.e.EurekaMoment.class)); cards.add(new SetCardInfo("Evolving Wilds", 274, Rarity.COMMON, mage.cards.e.EvolvingWilds.class)); cards.add(new SetCardInfo("Exhume", 370, Rarity.MYTHIC, mage.cards.e.Exhume.class)); + cards.add(new SetCardInfo("Exotic Orchard", 275, Rarity.RARE, mage.cards.e.ExoticOrchard.class)); cards.add(new SetCardInfo("Explosive Vegetation", 177, Rarity.UNCOMMON, mage.cards.e.ExplosiveVegetation.class)); cards.add(new SetCardInfo("Extravagant Replication", 117, Rarity.RARE, mage.cards.e.ExtravagantReplication.class)); cards.add(new SetCardInfo("Ezuri's Predation", 178, Rarity.RARE, mage.cards.e.EzurisPredation.class)); + cards.add(new SetCardInfo("Falkenrath Noble", 140, Rarity.UNCOMMON, mage.cards.f.FalkenrathNoble.class)); + cards.add(new SetCardInfo("Fate Unraveler", 141, Rarity.RARE, mage.cards.f.FateUnraveler.class)); cards.add(new SetCardInfo("Feed the Swarm", 78, Rarity.COMMON, mage.cards.f.FeedTheSwarm.class)); + cards.add(new SetCardInfo("Fellwar Stone", 245, Rarity.UNCOMMON, mage.cards.f.FellwarStone.class)); cards.add(new SetCardInfo("Flooded Grove", 276, Rarity.RARE, mage.cards.f.FloodedGrove.class)); + cards.add(new SetCardInfo("Florian, Voldaren Scion", 217, Rarity.RARE, mage.cards.f.FlorianVoldarenScion.class)); + cards.add(new SetCardInfo("Foreboding Ruins", 277, Rarity.RARE, mage.cards.f.ForebodingRuins.class)); + cards.add(new SetCardInfo("Geothermal Bog", 278, Rarity.COMMON, mage.cards.g.GeothermalBog.class)); + cards.add(new SetCardInfo("Giant Adephage", 179, Rarity.MYTHIC, mage.cards.g.GiantAdephage.class)); cards.add(new SetCardInfo("Gleeful Arsonist", 27, Rarity.RARE, mage.cards.g.GleefulArsonist.class)); + cards.add(new SetCardInfo("Gnarlwood Dryad", 180, Rarity.UNCOMMON, mage.cards.g.GnarlwoodDryad.class)); + cards.add(new SetCardInfo("Golgari Rot Farm", 279, Rarity.COMMON, mage.cards.g.GolgariRotFarm.class)); + cards.add(new SetCardInfo("Golgari Signet", 246, Rarity.COMMON, mage.cards.g.GolgariSignet.class)); cards.add(new SetCardInfo("Goryo's Vengeance", 372, Rarity.MYTHIC, mage.cards.g.GoryosVengeance.class)); cards.add(new SetCardInfo("Grapple with the Past", 82, Rarity.COMMON, mage.cards.g.GrappleWithThePast.class)); + cards.add(new SetCardInfo("Graven Cairns", 280, Rarity.RARE, mage.cards.g.GravenCairns.class)); + cards.add(new SetCardInfo("Gray Merchant of Asphodel", 142, Rarity.UNCOMMON, mage.cards.g.GrayMerchantOfAsphodel.class)); cards.add(new SetCardInfo("Greater Tanuki", 181, Rarity.COMMON, mage.cards.g.GreaterTanuki.class)); + cards.add(new SetCardInfo("Grim Backwoods", 281, Rarity.RARE, mage.cards.g.GrimBackwoods.class)); + cards.add(new SetCardInfo("Grim Flayer", 218, Rarity.RARE, mage.cards.g.GrimFlayer.class)); + cards.add(new SetCardInfo("Grisly Salvage", 219, Rarity.COMMON, mage.cards.g.GrislySalvage.class)); + cards.add(new SetCardInfo("Grist, the Hunger Tide", 220, Rarity.MYTHIC, mage.cards.g.GristTheHungerTide.class)); cards.add(new SetCardInfo("Growth Spiral", 88, Rarity.COMMON, mage.cards.g.GrowthSpiral.class)); cards.add(new SetCardInfo("Halimar Depths", 282, Rarity.COMMON, mage.cards.h.HalimarDepths.class)); cards.add(new SetCardInfo("Hall of Heliod's Generosity", 283, Rarity.RARE, mage.cards.h.HallOfHeliodsGenerosity.class)); + cards.add(new SetCardInfo("Harmonize", 182, Rarity.UNCOMMON, mage.cards.h.Harmonize.class)); + cards.add(new SetCardInfo("Harrow", 183, Rarity.COMMON, mage.cards.h.Harrow.class)); + cards.add(new SetCardInfo("Harsh Mentor", 165, Rarity.RARE, mage.cards.h.HarshMentor.class)); + cards.add(new SetCardInfo("Haywire Mite", 247, Rarity.UNCOMMON, mage.cards.h.HaywireMite.class)); cards.add(new SetCardInfo("Hinterland Harbor", 284, Rarity.RARE, mage.cards.h.HinterlandHarbor.class)); + cards.add(new SetCardInfo("Hornet Queen", 184, Rarity.MYTHIC, mage.cards.h.HornetQueen.class)); cards.add(new SetCardInfo("Hydra Omnivore", 185, Rarity.MYTHIC, mage.cards.h.HydraOmnivore.class)); + cards.add(new SetCardInfo("Infernal Grasp", 143, Rarity.UNCOMMON, mage.cards.i.InfernalGrasp.class)); cards.add(new SetCardInfo("Inkshield", 221, Rarity.RARE, mage.cards.i.Inkshield.class)); + cards.add(new SetCardInfo("Inscription of Abundance", 186, Rarity.RARE, mage.cards.i.InscriptionOfAbundance.class)); + cards.add(new SetCardInfo("Ishkanah, Grafwidow", 187, Rarity.MYTHIC, mage.cards.i.IshkanahGrafwidow.class)); + cards.add(new SetCardInfo("Jungle Hollow", 285, Rarity.COMMON, mage.cards.j.JungleHollow.class)); + cards.add(new SetCardInfo("Kaervek the Merciless", 222, Rarity.RARE, mage.cards.k.KaervekTheMerciless.class)); + cards.add(new SetCardInfo("Kardur, Doomscourge", 223, Rarity.UNCOMMON, mage.cards.k.KardurDoomscourge.class)); + cards.add(new SetCardInfo("Kederekt Parasite", 144, Rarity.RARE, mage.cards.k.KederektParasite.class)); cards.add(new SetCardInfo("Kefnet the Mindful", 118, Rarity.MYTHIC, mage.cards.k.KefnetTheMindful.class)); cards.add(new SetCardInfo("Kheru Spellsnatcher", 119, Rarity.RARE, mage.cards.k.KheruSpellsnatcher.class)); + cards.add(new SetCardInfo("Leechridden Swamp", 286, Rarity.UNCOMMON, mage.cards.l.LeechriddenSwamp.class)); cards.add(new SetCardInfo("Life Insurance", 224, Rarity.RARE, mage.cards.l.LifeInsurance.class)); + cards.add(new SetCardInfo("Light Up the Stage", 166, Rarity.UNCOMMON, mage.cards.l.LightUpTheStage.class)); cards.add(new SetCardInfo("Lightning Greaves", 93, Rarity.UNCOMMON, mage.cards.l.LightningGreaves.class)); cards.add(new SetCardInfo("Living Death", 373, Rarity.MYTHIC, mage.cards.l.LivingDeath.class)); + cards.add(new SetCardInfo("Llanowar Wastes", 287, Rarity.RARE, mage.cards.l.LlanowarWastes.class)); + cards.add(new SetCardInfo("Mask of Griselbrand", 145, Rarity.RARE, mage.cards.m.MaskOfGriselbrand.class)); + cards.add(new SetCardInfo("Massacre Girl", 146, Rarity.RARE, mage.cards.m.MassacreGirl.class)); + cards.add(new SetCardInfo("Massacre Wurm", 147, Rarity.MYTHIC, mage.cards.m.MassacreWurm.class)); + cards.add(new SetCardInfo("Mayhem Devil", 225, Rarity.UNCOMMON, mage.cards.m.MayhemDevil.class)); cards.add(new SetCardInfo("Mesa Enchantress", 68, Rarity.RARE, mage.cards.m.MesaEnchantress.class)); cards.add(new SetCardInfo("Metamorphosis Fanatic", 21, Rarity.RARE, mage.cards.m.MetamorphosisFanatic.class)); cards.add(new SetCardInfo("Mind Stone", 248, Rarity.UNCOMMON, mage.cards.m.MindStone.class)); cards.add(new SetCardInfo("Mirrormade", 120, Rarity.RARE, mage.cards.m.Mirrormade.class)); cards.add(new SetCardInfo("Mogis, God of Slaughter", 89, Rarity.MYTHIC, mage.cards.m.MogisGodOfSlaughter.class)); + cards.add(new SetCardInfo("Moldgraf Millipede", 188, Rarity.COMMON, mage.cards.m.MoldgrafMillipede.class)); cards.add(new SetCardInfo("Moldgraf Monstrosity", 83, Rarity.RARE, mage.cards.m.MoldgrafMonstrosity.class)); cards.add(new SetCardInfo("Monologue Tax", 100, Rarity.RARE, mage.cards.m.MonologueTax.class)); cards.add(new SetCardInfo("Moon-Blessed Cleric", 69, Rarity.UNCOMMON, mage.cards.m.MoonBlessedCleric.class)); + cards.add(new SetCardInfo("Morbid Opportunist", 148, Rarity.UNCOMMON, mage.cards.m.MorbidOpportunist.class)); cards.add(new SetCardInfo("Mosswort Bridge", 288, Rarity.RARE, mage.cards.m.MosswortBridge.class)); + cards.add(new SetCardInfo("Mulch", 189, Rarity.COMMON, mage.cards.m.Mulch.class)); cards.add(new SetCardInfo("Multani, Yavimaya's Avatar", 190, Rarity.MYTHIC, mage.cards.m.MultaniYavimayasAvatar.class)); cards.add(new SetCardInfo("Myriad Landscape", 289, Rarity.UNCOMMON, mage.cards.m.MyriadLandscape.class)); + cards.add(new SetCardInfo("Necroblossom Snarl", 290, Rarity.RARE, mage.cards.n.NecroblossomSnarl.class)); cards.add(new SetCardInfo("Night's Whisper", 79, Rarity.COMMON, mage.cards.n.NightsWhisper.class)); cards.add(new SetCardInfo("Nightmare Shepherd", 149, Rarity.RARE, mage.cards.n.NightmareShepherd.class)); + cards.add(new SetCardInfo("Nightshade Harvester", 150, Rarity.RARE, mage.cards.n.NightshadeHarvester.class)); + cards.add(new SetCardInfo("Noxious Gearhulk", 151, Rarity.MYTHIC, mage.cards.n.NoxiousGearhulk.class)); + cards.add(new SetCardInfo("Nyx Weaver", 226, Rarity.UNCOMMON, mage.cards.n.NyxWeaver.class)); + cards.add(new SetCardInfo("Ob Nixilis Reignited", 152, Rarity.MYTHIC, mage.cards.o.ObNixilisReignited.class)); cards.add(new SetCardInfo("Obscura Storefront", 291, Rarity.COMMON, mage.cards.o.ObscuraStorefront.class)); + cards.add(new SetCardInfo("Obsessive Skinner", 191, Rarity.UNCOMMON, mage.cards.o.ObsessiveSkinner.class)); + cards.add(new SetCardInfo("Old Stickfingers", 227, Rarity.RARE, mage.cards.o.OldStickfingers.class)); cards.add(new SetCardInfo("Ondu Spiritdancer", 101, Rarity.RARE, mage.cards.o.OnduSpiritdancer.class)); cards.add(new SetCardInfo("One with the Multiverse", 121, Rarity.MYTHIC, mage.cards.o.OneWithTheMultiverse.class)); cards.add(new SetCardInfo("Orzhov Basilica", 292, Rarity.UNCOMMON, mage.cards.o.OrzhovBasilica.class)); @@ -121,63 +192,103 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Ponder", 73, Rarity.COMMON, mage.cards.p.Ponder.class)); cards.add(new SetCardInfo("Portent", 74, Rarity.COMMON, mage.cards.p.Portent.class)); cards.add(new SetCardInfo("Primordial Mist", 123, Rarity.RARE, mage.cards.p.PrimordialMist.class)); + cards.add(new SetCardInfo("Professor Onyx", 153, Rarity.MYTHIC, mage.cards.p.ProfessorOnyx.class)); cards.add(new SetCardInfo("Prognostic Sphinx", 124, Rarity.RARE, mage.cards.p.PrognosticSphinx.class)); cards.add(new SetCardInfo("Putrefy", 90, Rarity.UNCOMMON, mage.cards.p.Putrefy.class)); cards.add(new SetCardInfo("Quandrix Campus", 294, Rarity.COMMON, mage.cards.q.QuandrixCampus.class)); + cards.add(new SetCardInfo("Rakdos Charm", 229, Rarity.UNCOMMON, mage.cards.r.RakdosCharm.class)); + cards.add(new SetCardInfo("Rakdos Signet", 250, Rarity.UNCOMMON, mage.cards.r.RakdosSignet.class)); + cards.add(new SetCardInfo("Rakdos, Lord of Riots", 230, Rarity.MYTHIC, mage.cards.r.RakdosLordOfRiots.class)); + cards.add(new SetCardInfo("Rampaging Ferocidon", 167, Rarity.RARE, mage.cards.r.RampagingFerocidon.class)); cards.add(new SetCardInfo("Rampant Growth", 193, Rarity.COMMON, mage.cards.r.RampantGrowth.class)); cards.add(new SetCardInfo("Rashmi, Eternities Crafter", 231, Rarity.MYTHIC, mage.cards.r.RashmiEternitiesCrafter.class)); cards.add(new SetCardInfo("Read the Bones", 154, Rarity.COMMON, mage.cards.r.ReadTheBones.class)); cards.add(new SetCardInfo("Reality Shift", 125, Rarity.UNCOMMON, mage.cards.r.RealityShift.class)); + cards.add(new SetCardInfo("Reanimate", 155, Rarity.RARE, mage.cards.r.Reanimate.class)); cards.add(new SetCardInfo("Reliquary Tower", 295, Rarity.UNCOMMON, mage.cards.r.ReliquaryTower.class)); cards.add(new SetCardInfo("Retreat to Coralhelm", 126, Rarity.UNCOMMON, mage.cards.r.RetreatToCoralhelm.class)); cards.add(new SetCardInfo("Return to Dust", 102, Rarity.UNCOMMON, mage.cards.r.ReturnToDust.class)); cards.add(new SetCardInfo("Sakura-Tribe Elder", 194, Rarity.COMMON, mage.cards.s.SakuraTribeElder.class)); cards.add(new SetCardInfo("Sandwurm Convergence", 195, Rarity.RARE, mage.cards.s.SandwurmConvergence.class)); + cards.add(new SetCardInfo("Scavenging Ooze", 196, Rarity.RARE, mage.cards.s.ScavengingOoze.class)); cards.add(new SetCardInfo("Scroll of Fate", 251, Rarity.RARE, mage.cards.s.ScrollOfFate.class)); cards.add(new SetCardInfo("Scute Swarm", 197, Rarity.RARE, mage.cards.s.ScuteSwarm.class)); + cards.add(new SetCardInfo("Shadowblood Ridge", 296, Rarity.RARE, mage.cards.s.ShadowbloodRidge.class)); cards.add(new SetCardInfo("Shark Typhoon", 127, Rarity.RARE, mage.cards.s.SharkTyphoon.class)); cards.add(new SetCardInfo("Shigeki, Jukai Visionary", 198, Rarity.RARE, mage.cards.s.ShigekiJukaiVisionary.class)); + cards.add(new SetCardInfo("Shivan Gorge", 297, Rarity.RARE, mage.cards.s.ShivanGorge.class)); cards.add(new SetCardInfo("Sigil of the Empty Throne", 103, Rarity.RARE, mage.cards.s.SigilOfTheEmptyThrone.class)); + cards.add(new SetCardInfo("Sign in Blood", 156, Rarity.COMMON, mage.cards.s.SignInBlood.class)); cards.add(new SetCardInfo("Simic Growth Chamber", 298, Rarity.UNCOMMON, mage.cards.s.SimicGrowthChamber.class)); cards.add(new SetCardInfo("Simic Signet", 252, Rarity.UNCOMMON, mage.cards.s.SimicSignet.class)); cards.add(new SetCardInfo("Skaab Ruinator", 128, Rarity.MYTHIC, mage.cards.s.SkaabRuinator.class)); + cards.add(new SetCardInfo("Skola Grovedancer", 199, Rarity.COMMON, mage.cards.s.SkolaGrovedancer.class)); + cards.add(new SetCardInfo("Smoldering Marsh", 299, Rarity.RARE, mage.cards.s.SmolderingMarsh.class)); cards.add(new SetCardInfo("Sol Ring", 94, Rarity.UNCOMMON, mage.cards.s.SolRing.class)); cards.add(new SetCardInfo("Solemn Simulacrum", 253, Rarity.RARE, mage.cards.s.SolemnSimulacrum.class)); cards.add(new SetCardInfo("Sphere of Safety", 104, Rarity.UNCOMMON, mage.cards.s.SphereOfSafety.class)); + cards.add(new SetCardInfo("Spinerock Knoll", 300, Rarity.RARE, mage.cards.s.SpinerockKnoll.class)); cards.add(new SetCardInfo("Spirit-Sister's Call", 232, Rarity.MYTHIC, mage.cards.s.SpiritSistersCall.class)); + cards.add(new SetCardInfo("Spiteful Visions", 233, Rarity.RARE, mage.cards.s.SpitefulVisions.class)); cards.add(new SetCardInfo("Starfield Mystic", 105, Rarity.RARE, mage.cards.s.StarfieldMystic.class)); - cards.add(new SetCardInfo("Suspicious Bookcase", 95, Rarity.COMMON, mage.cards.s.SuspiciousBookcase.class)); + cards.add(new SetCardInfo("Stitcher's Supplier", 157, Rarity.UNCOMMON, mage.cards.s.StitchersSupplier.class)); + cards.add(new SetCardInfo("Stormfist Crusader", 234, Rarity.RARE, mage.cards.s.StormfistCrusader.class)); + cards.add(new SetCardInfo("Sulfurous Springs", 301, Rarity.RARE, mage.cards.s.SulfurousSprings.class)); + cards.add(new SetCardInfo("Suspicious Bookcase", 95, Rarity.UNCOMMON, mage.cards.s.SuspiciousBookcase.class)); cards.add(new SetCardInfo("Swords to Plowshares", 106, Rarity.UNCOMMON, mage.cards.s.SwordsToPlowshares.class)); + cards.add(new SetCardInfo("Syr Konrad, the Grim", 158, Rarity.UNCOMMON, mage.cards.s.SyrKonradTheGrim.class)); cards.add(new SetCardInfo("Tainted Field", 302, Rarity.UNCOMMON, mage.cards.t.TaintedField.class)); cards.add(new SetCardInfo("Tainted Isle", 303, Rarity.UNCOMMON, mage.cards.t.TaintedIsle.class)); + cards.add(new SetCardInfo("Tainted Peak", 304, Rarity.UNCOMMON, mage.cards.t.TaintedPeak.class)); + cards.add(new SetCardInfo("Tainted Wood", 305, Rarity.UNCOMMON, mage.cards.t.TaintedWood.class)); + cards.add(new SetCardInfo("Talisman of Indulgence", 254, Rarity.UNCOMMON, mage.cards.t.TalismanOfIndulgence.class)); + cards.add(new SetCardInfo("Talisman of Resilience", 255, Rarity.UNCOMMON, mage.cards.t.TalismanOfResilience.class)); cards.add(new SetCardInfo("Tangled Islet", 306, Rarity.COMMON, mage.cards.t.TangledIslet.class)); cards.add(new SetCardInfo("Tatyova, Benthic Druid", 235, Rarity.UNCOMMON, mage.cards.t.TatyovaBenthicDruid.class)); + cards.add(new SetCardInfo("Tectonic Giant", 168, Rarity.RARE, mage.cards.t.TectonicGiant.class)); cards.add(new SetCardInfo("Telling Time", 75, Rarity.UNCOMMON, mage.cards.t.TellingTime.class)); cards.add(new SetCardInfo("Temple of Deceit", 307, Rarity.RARE, mage.cards.t.TempleOfDeceit.class)); cards.add(new SetCardInfo("Temple of Enlightenment", 308, Rarity.RARE, mage.cards.t.TempleOfEnlightenment.class)); + cards.add(new SetCardInfo("Temple of Malady", 309, Rarity.RARE, mage.cards.t.TempleOfMalady.class)); + cards.add(new SetCardInfo("Temple of Malice", 310, Rarity.RARE, mage.cards.t.TempleOfMalice.class)); cards.add(new SetCardInfo("Temple of Mystery", 311, Rarity.RARE, mage.cards.t.TempleOfMystery.class)); cards.add(new SetCardInfo("Temple of Silence", 312, Rarity.RARE, mage.cards.t.TempleOfSilence.class)); cards.add(new SetCardInfo("Temple of the False God", 313, Rarity.UNCOMMON, mage.cards.t.TempleOfTheFalseGod.class)); cards.add(new SetCardInfo("Temur War Shaman", 200, Rarity.RARE, mage.cards.t.TemurWarShaman.class)); cards.add(new SetCardInfo("Terminus", 70, Rarity.RARE, mage.cards.t.Terminus.class)); cards.add(new SetCardInfo("The Eldest Reborn", 139, Rarity.UNCOMMON, mage.cards.t.TheEldestReborn.class)); + cards.add(new SetCardInfo("Theater of Horrors", 236, Rarity.RARE, mage.cards.t.TheaterOfHorrors.class)); cards.add(new SetCardInfo("They Came from the Pipes", 14, Rarity.RARE, mage.cards.t.TheyCameFromThePipes.class)); cards.add(new SetCardInfo("Thirst for Meaning", 129, Rarity.COMMON, mage.cards.t.ThirstForMeaning.class)); cards.add(new SetCardInfo("Thornwood Falls", 314, Rarity.COMMON, mage.cards.t.ThornwoodFalls.class)); + cards.add(new SetCardInfo("Thought Vessel", 256, Rarity.UNCOMMON, mage.cards.t.ThoughtVessel.class)); cards.add(new SetCardInfo("Thriving Heath", 315, Rarity.COMMON, mage.cards.t.ThrivingHeath.class)); cards.add(new SetCardInfo("Thriving Isle", 316, Rarity.COMMON, mage.cards.t.ThrivingIsle.class)); cards.add(new SetCardInfo("Thriving Moor", 317, Rarity.COMMON, mage.cards.t.ThrivingMoor.class)); + cards.add(new SetCardInfo("Thunderfoot Baloth", 201, Rarity.RARE, mage.cards.t.ThunderfootBaloth.class)); cards.add(new SetCardInfo("Time Wipe", 237, Rarity.RARE, mage.cards.t.TimeWipe.class)); cards.add(new SetCardInfo("Timely Ward", 107, Rarity.RARE, mage.cards.t.TimelyWard.class)); + cards.add(new SetCardInfo("Titania, Nature's Force", 202, Rarity.MYTHIC, mage.cards.t.TitaniaNaturesForce.class)); cards.add(new SetCardInfo("Trail of Mystery", 203, Rarity.RARE, mage.cards.t.TrailOfMystery.class)); + cards.add(new SetCardInfo("Tranquil Thicket", 318, Rarity.COMMON, mage.cards.t.TranquilThicket.class)); + cards.add(new SetCardInfo("Tree of Tales", 319, Rarity.COMMON, mage.cards.t.TreeOfTales.class)); cards.add(new SetCardInfo("Trygon Predator", 238, Rarity.UNCOMMON, mage.cards.t.TrygonPredator.class)); + cards.add(new SetCardInfo("Twilight Mire", 320, Rarity.RARE, mage.cards.t.TwilightMire.class)); cards.add(new SetCardInfo("Underground River", 321, Rarity.RARE, mage.cards.u.UndergroundRiver.class)); cards.add(new SetCardInfo("Utter End", 91, Rarity.RARE, mage.cards.u.UtterEnd.class)); + cards.add(new SetCardInfo("Vault of Whispers", 322, Rarity.COMMON, mage.cards.v.VaultOfWhispers.class)); cards.add(new SetCardInfo("Verge Rangers", 108, Rarity.RARE, mage.cards.v.VergeRangers.class)); + cards.add(new SetCardInfo("Vial Smasher the Fierce", 239, Rarity.MYTHIC, mage.cards.v.VialSmasherTheFierce.class)); cards.add(new SetCardInfo("Vineglimmer Snarl", 323, Rarity.RARE, mage.cards.v.VineglimmerSnarl.class)); + cards.add(new SetCardInfo("Viridescent Bog", 324, Rarity.RARE, mage.cards.v.ViridescentBog.class)); + cards.add(new SetCardInfo("Whip of Erebos", 159, Rarity.RARE, mage.cards.w.WhipOfErebos.class)); + cards.add(new SetCardInfo("Whispersilk Cloak", 257, Rarity.UNCOMMON, mage.cards.w.WhispersilkCloak.class)); cards.add(new SetCardInfo("Whisperwood Elemental", 204, Rarity.MYTHIC, mage.cards.w.WhisperwoodElemental.class)); cards.add(new SetCardInfo("Wilderness Reclamation", 205, Rarity.UNCOMMON, mage.cards.w.WildernessReclamation.class)); + cards.add(new SetCardInfo("Witch's Clinic", 325, Rarity.RARE, mage.cards.w.WitchsClinic.class)); + cards.add(new SetCardInfo("Woodland Cemetery", 326, Rarity.RARE, mage.cards.w.WoodlandCemetery.class)); cards.add(new SetCardInfo("Worldspine Wurm", 206, Rarity.MYTHIC, mage.cards.w.WorldspineWurm.class)); + cards.add(new SetCardInfo("Wrenn and Seven", 207, Rarity.MYTHIC, mage.cards.w.WrennAndSeven.class)); cards.add(new SetCardInfo("Yavimaya Coast", 327, Rarity.RARE, mage.cards.y.YavimayaCoast.class)); cards.add(new SetCardInfo("Yavimaya Elder", 208, Rarity.COMMON, mage.cards.y.YavimayaElder.class)); cards.add(new SetCardInfo("Yedora, Grave Gardener", 209, Rarity.UNCOMMON, mage.cards.y.YedoraGraveGardener.class)); diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 95af5781d46..6d73ade7d88 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -54628,7 +54628,7 @@ Zimone's Hypothesis|Duskmourn: House of Horror Commander|15|R|{3}{U}{U}|Instant| Ancient Cellarspawn|Duskmourn: House of Horror Commander|16|R|{1}{B}{B}|Enchantment Creature - Horror|3|3|Each spell you cast that's a Demon, Horror, or Nightmare costs {1} less to cast.$Whenever you cast a spell, if the amount of mana spent to cast it was less than its mana value, target opponent loses life equal to the difference.| Cramped Vents // Access Maze|Duskmourn: House of Horror Commander|17|R|{3}{B}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, this Room deals 6 damage to target creature an opponent controls. You gain life equal to the excess damage dealt this way.$Access Maze${5}{B}{B}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Once during each of your turns, you may cast a spell from your hand by paying life equal to its mana value rather than paying its mana cost.| Deluge of Doom|Duskmourn: House of Horror Commander|18|R|{2}{B}|Sorcery|||All creatures get -X/-X until end of turn, where X is the number of card types among cards in your graveyard.| -Demonic Covenant|Duskmourn: House of Horror Commander|19|R|{4}{B}{B}|Kindred Enchantment -- Demon|||Whenever one or more Demons you control attack a player, you draw a card and lose 1 life. 4$At the beginning of your end step, create a 5/5 black Demon creature token with flying, then mill two cards. If two cards that share all their card types were milled this way, sacrifice Demonic Covenant.| +Demonic Covenant|Duskmourn: House of Horror Commander|19|R|{4}{B}{B}|Kindred Enchantment - Demon|||Whenever one or more Demons you control attack a player, you draw a card and lose 1 life.$At the beginning of your end step, create a 5/5 black Demon creature token with flying, then mill two cards. If two cards that share all their card types were milled this way, sacrifice Demonic Covenant.| Into the Pit|Duskmourn: House of Horror Commander|20|R|{2}{B}|Enchantment|||You may look at the top card of your library any time.$You may cast spells from the top of your library by sacrificing a nonland permanent in addition to paying their other costs.| Metamorphosis Fanatic|Duskmourn: House of Horror Commander|21|R|{4}{B}{B}|Creature - Human Cleric|4|4|Lifelink$When Metamorphosis Fanatic enters, return up to one target creature card from your graveyard to the battlefield with a lifelink counter on it.$Miracle {1}{B}| Persistent Constrictor|Duskmourn: House of Horror Commander|22|R|{4}{B}|Creature - Zombie Snake|5|3|At the beginning of each opponent's upkeep, they lose 1 life and you put a -1/-1 counter on up to one target creature they control.$Persist| @@ -54637,13 +54637,13 @@ Sadistic Shell Game|Duskmourn: House of Horror Commander|24|R|{4}{B}|Sorcery|||S Suspended Sentence|Duskmourn: House of Horror Commander|25|R|{3}{B}|Instant|||Destroy target creature an opponent controls. That player loses 3 life. Exile Suspended Sentence with three time counters on it.$Suspend 3--{1}{B}| Barbflare Gremlin|Duskmourn: House of Horror Commander|26|R|{3}{R}|Creature - Gremlin|3|2|First strike, haste$Whenever a player taps a land for mana, if Barbflare Gremlin is tapped, that player adds one mana of any type that land produced. Then that land deals 1 damage to that player.| Gleeful Arsonist|Duskmourn: House of Horror Commander|27|R|{2}{R}|Creature - Human Wizard|1|2|Whenever an opponent casts a noncreature spell, Gleeful Arsonist deals damage equal to its power to that player.$Undying| -Spiked Corridor // Torture Pit|Duskmourn: House of Horror Commander|28|R|{3}{R}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, create three 1/1 red Devil creature tokens with "When this creature dies, it deals 1 damage to any target."$Torture Pit${3}{R}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.) If a source you control would deal noncombat damage to an opponent, it deals that much damage plus 2 instead.| +Spiked Corridor // Torture Pit|Duskmourn: House of Horror Commander|28|R|{3}{R}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, create three 1/1 red Devil creature tokens with "When this creature dies, it deals 1 damage to any target."$Torture Pit${3}{R}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$If a source you control would deal noncombat damage to an opponent, it deals that much damage plus 2 instead.| Star Athlete|Duskmourn: House of Horror Commander|29|R|{1}{R}{R}|Creature - Human Warrior|3|2|Menace$Whenever Star Athlete attacks, choose up to one target nonland permanent. Its controller may sacrifice it. If they don't, Star Athlete deals 5 damage to that player.$Blitz {3}{R}| Curator Beastie|Duskmourn: House of Horror Commander|30|R|{4}{G}{G}|Creature - Beast|6|6|Reach$Colorless creatures you control enter with two additional +1/+1 counters on them.$Whenever Curator Beastie enters or attacks, manifest dread.| Demolisher Spawn|Duskmourn: House of Horror Commander|31|R|{5}{G}{G}|Enchantment Creature - Horror|7|7|Trample, haste$Delirium -- Whenever Demolisher Spawn attacks, if there are four or more card types among cards in your graveyard, other attacking creatures get +4/+4 until end of turn.| Disorienting Choice|Duskmourn: House of Horror Commander|32|R|{3}{G}|Sorcery|||For each opponent, choose up to one target artifact or enchantment that player controls. For each permanent chosen this way, its controller may exile it. Then if one or more of the chosen permanents are still on the battlefield, you search your library for up to that many land cards, put them onto the battlefield tapped, then shuffle.| Experimental Lab // Staff Room|Duskmourn: House of Horror Commander|33|R|{3}{G}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, manifest dread, then put two +1/+1 counters and a trample counter on that creature.$Staff Room${2}{G}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Whenever a creature you control deals combat damage to a player, turn that creature face up or put a +1/+1 counter on it.| -Formless Genesis|Duskmourn: House of Horror Commander|34|R|{2}{G}|Kindred Sorcery - Shapeshifter be|||Changeling$Create an X/X colorless Shapeshifter creature token with changeling and deathtouch, where X is the number of land cards in your graveyard.$Retrace| +Formless Genesis|Duskmourn: House of Horror Commander|34|R|{2}{G}|Kindred Sorcery - Shapeshifter|||Changeling$Create an X/X colorless Shapeshifter creature token with changeling and deathtouch, where X is the number of land cards in your graveyard.$Retrace| Shriekwood Devourer|Duskmourn: House of Horror Commander|35|R|{5}{G}{G}|Creature - Treefolk|7|5|Trample$Whenever you attack with one or more creatures, untap up to X lands, where X is the greatest power among those creatures.| Ursine Monstrosity|Duskmourn: House of Horror Commander|36|R|{2}{G}|Creature - Bear Mutant|3|3|Trample$At the beginning of combat on your turn, mill a card and choose an opponent at random. Ursine Monstrosity attacks that player this combat if able. Until end of turn, Ursine Monstrosity gains indestructible and gets +1/+1 for each card type among cards in your graveyard.| Convert to Slime|Duskmourn: House of Horror Commander|37|R|{3}{B}{G}|Sorcery|||Destroy up to one target artifact, up to one target creature, and up to one target enchantment.$Delirium -- Then if there are four or more card types among cards in your graveyard, create an X/X green Ooze creature token, where X is the total mana value of permanents destroyed this way.| @@ -54667,6 +54667,7 @@ Citanul Hierophants|Duskmourn: House of Horror Commander|81|R|{3}{G}|Creature - Grapple with the Past|Duskmourn: House of Horror Commander|82|C|{1}{G}|Instant|||Mill three cards, then you may return a creature or land card from your graveyard to your hand.| Moldgraf Monstrosity|Duskmourn: House of Horror Commander|83|R|{4}{G}{G}{G}|Creature - Insect|8|8|Trample$When Moldgraf Monstrosity dies, exile it, then return two creature cards at random from your graveyard to the battlefield.| Bedevil|Duskmourn: House of Horror Commander|84|R|{B}{B}{R}|Instant|||Destroy target artifact, creature, or planeswalker.| +Culling Ritual|Duskmourn: House of Horror Commander|85|R|{2}{B}{G}|Sorcery|||Destroy each nonland permanent with mana value 2 or less. Add {B} or {G} for each permanent destroyed this way.| Deathreap Ritual|Duskmourn: House of Horror Commander|86|U|{2}{B}{G}|Enchantment|||Morbid -- At the beginning of each end step, if a creature died this turn, you may draw a card.| Diabolic Vision|Duskmourn: House of Horror Commander|87|U|{U}{B}|Sorcery|||Look at the top five cards of your library. Put one of them into your hand and the rest on top of your library in any order.| Growth Spiral|Duskmourn: House of Horror Commander|88|C|{G}{U}|Instant|||Draw a card. You may put a land card from your hand onto the battlefield.| @@ -54676,7 +54677,7 @@ Utter End|Duskmourn: House of Horror Commander|91|R|{2}{W}{B}|Instant|||Exile ta Arcane Signet|Duskmourn: House of Horror Commander|92|C|{2}|Artifact|||{T}: Add one mana of any color in your commander's color identity.| Lightning Greaves|Duskmourn: House of Horror Commander|93|U|{2}|Artifact - Equipment|||Equipped creature has haste and shroud.$Equip {0}| Sol Ring|Duskmourn: House of Horror Commander|94|U|{1}|Artifact|||{T}: Add {C}{C}.| -Suspicious Bookcase|Duskmourn: House of Horror Commander|95|C|{2}|Artifact Creature - Wall|0|4|Defender${3}, {T}: Target creature can't be blocked this turn.| +Suspicious Bookcase|Duskmourn: House of Horror Commander|95|U|{2}|Artifact Creature - Wall|0|4|Defender${3}, {T}: Target creature can't be blocked this turn.| Command Tower|Duskmourn: House of Horror Commander|96|C||Land|||{T}: Add one mana of any color in your commander's color identity.| Auramancer|Duskmourn: House of Horror Commander|97|C|{2}{W}|Creature - Human Wizard|2|2|When Auramancer enters, you may return target enchantment card from your graveyard to your hand.| Cast Out|Duskmourn: House of Horror Commander|98|U|{3}{W}|Enchantment|||Flash$When Cast Out enters, exile target nonland permanent an opponent controls until Cast Out leaves the battlefield.$Cycling {W}| @@ -54712,84 +54713,187 @@ Shark Typhoon|Duskmourn: House of Horror Commander|127|R|{5}{U}|Enchantment|||Wh Skaab Ruinator|Duskmourn: House of Horror Commander|128|M|{1}{U}{U}|Creature - Zombie Horror|5|6|As an additional cost to cast this spell, exile three creature cards from your graveyard.$Flying$You may cast Skaab Ruinator from your graveyard.| Thirst for Meaning|Duskmourn: House of Horror Commander|129|C|{2}{U}|Instant|||Draw three cards. Then discard two cards unless you discard an enchantment card.| Arvinox, the Mind Flail|Duskmourn: House of Horror Commander|130|M|{4}{B}{B}{B}|Legendary Enchantment Creature - Horror|9|9|Arvinox, the Mind Flail isn't a creature unless you control three or more permanents you don't own.$At the beginning of your end step, exile the bottom card of each opponent's library face down. For as long as those cards remain exiled, you may look at them, you may cast permanent spells from among them, and you may spend mana as though it were mana of any color to cast those spells.| +Bastion of Remembrance|Duskmourn: House of Horror Commander|131|U|{2}{B}|Enchantment|||When Bastion of Remembrance enters, create a 1/1 white Human Soldier creature token.$Whenever a creature you control dies, each opponent loses 1 life and you gain 1 life.| +Blood Artist|Duskmourn: House of Horror Commander|132|U|{1}{B}|Creature - Vampire|0|1|Whenever Blood Artist or another creature dies, target player loses 1 life and you gain 1 life.| +Braids, Arisen Nightmare|Duskmourn: House of Horror Commander|133|R|{1}{B}{B}|Legendary Creature - Nightmare|3|3|At the beginning of your end step, you may sacrifice an artifact, creature, enchantment, land, or planeswalker. If you do, each opponent may sacrifice a permanent that shares a card type with it. For each opponent who doesn't, that player loses 2 life and you draw a card.| +Carrion Grub|Duskmourn: House of Horror Commander|134|C|{3}{B}|Creature - Insect|0|5|Carrion Grub gets +X/+0, where X is the greatest power among creature cards in your graveyard.$When Carrion Grub enters, mill four cards.| +Cemetery Tampering|Duskmourn: House of Horror Commander|135|R|{2}{B}|Enchantment|||Hideaway 5$At the beginning of your upkeep, you may mill three cards. Then if there are twenty or more cards in your graveyard, you may play the exiled card without paying its mana cost.| +Decree of Pain|Duskmourn: House of Horror Commander|136|R|{6}{B}{B}|Sorcery|||Destroy all creatures. They can't be regenerated. Draw a card for each creature destroyed this way.$Cycling {3}{B}{B}$When you cycle Decree of Pain, all creatures get -2/-2 until end of turn.| Demon of Fate's Design|Duskmourn: House of Horror Commander|137|R|{4}{B}{B}|Enchantment Creature - Demon|6|6|Flying, trample$Once during each of your turns, you may cast an enchantment spell by paying life equal to its mana value rather than paying its mana cost.${2}{B}, Sacrifice another enchantment: Demon of Fate's Design gets +X/+0 until end of turn, where X is the sacrificed enchantment's mana value.| Doomwake Giant|Duskmourn: House of Horror Commander|138|R|{4}{B}|Enchantment Creature - Giant|4|6|Constellation -- Whenever Doomwake Giant or another enchantment you control enters, creatures your opponents control get -1/-1 until end of turn.| The Eldest Reborn|Duskmourn: House of Horror Commander|139|U|{4}{B}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Each opponent sacrifices a creature or planeswalker.$II -- Each opponent discards a card.$III -- Put target creature or planeswalker card from a graveyard onto the battlefield under your control.| +Falkenrath Noble|Duskmourn: House of Horror Commander|140|U|{3}{B}|Creature - Vampire Noble|2|2|Flying$Whenever Falkenrath Noble or another creature dies, target player loses 1 life and you gain 1 life.| +Fate Unraveler|Duskmourn: House of Horror Commander|141|R|{3}{B}|Enchantment Creature - Hag|3|4|Whenever an opponent draws a card, Fate Unraveler deals 1 damage to that player.| +Gray Merchant of Asphodel|Duskmourn: House of Horror Commander|142|U|{3}{B}{B}|Creature - Zombie|2|4|When Gray Merchant of Asphodel enters, each opponent loses X life, where X is your devotion to black. You gain life equal to the life lost this way.| +Infernal Grasp|Duskmourn: House of Horror Commander|143|U|{1}{B}|Instant|||Destroy target creature. You lose 2 life.| +Kederekt Parasite|Duskmourn: House of Horror Commander|144|R|{B}|Creature - Horror|1|1|Whenever an opponent draws a card, if you control a red permanent, you may have Kederekt Parasite deal 1 damage to that player.| +Mask of Griselbrand|Duskmourn: House of Horror Commander|145|R|{1}{B}{B}|Legendary Artifact - Equipment|||Equipped creature has flying and lifelink.$Whenever equipped creature dies, you may pay X life, where X is its power. If you do, draw X cards.$Equip {3}| +Massacre Girl|Duskmourn: House of Horror Commander|146|R|{3}{B}{B}|Legendary Creature - Human Assassin|4|4|Menace$When Massacre Girl enters, each other creature gets -1/-1 until end of turn. Whenever a creature dies this turn, each creature other than Massacre Girl gets -1/-1 until end of turn.| +Massacre Wurm|Duskmourn: House of Horror Commander|147|M|{3}{B}{B}{B}|Creature - Phyrexian Wurm|6|5|When Massacre Wurm enters, creatures your opponents control get -2/-2 until end of turn.$Whenever a creature an opponent controls dies, that player loses 2 life.| +Morbid Opportunist|Duskmourn: House of Horror Commander|148|U|{2}{B}|Creature - Human Rogue|1|3|Whenever one or more other creatures die, draw a card. This ability triggers only once each turn.| Nightmare Shepherd|Duskmourn: House of Horror Commander|149|R|{2}{B}{B}|Enchantment Creature - Demon|4|4|Flying$Whenever another nontoken creature you control dies, you may exile it. If you do, create a token that's a copy of that creature, except it's 1/1 and it's a Nightmare in addition to its other types.| +Nightshade Harvester|Duskmourn: House of Horror Commander|150|R|{3}{B}|Creature - Elf Shaman|2|2|Whenever a land an opponent controls enters, that player loses 1 life. Put a +1/+1 counter on Nightshade Harvester.| +Noxious Gearhulk|Duskmourn: House of Horror Commander|151|M|{4}{B}{B}|Artifact Creature - Construct|5|4|Menace$When Noxious Gearhulk enters, you may destroy another target creature. If a creature is destroyed this way, you gain life equal to its toughness.| +Ob Nixilis Reignited|Duskmourn: House of Horror Commander|152|M|{3}{B}{B}|Legendary Planeswalker - Nixilis|5|+1: You draw a card and you lose 1 life.$-3: Destroy target creature.$-8: Target opponent gets an emblem with "Whenever a player draws a card, you lose 2 life."| +Professor Onyx|Duskmourn: House of Horror Commander|153|M|{4}{B}{B}|Legendary Planeswalker - Liliana|5|Magecraft -- Whenever you cast or copy an instant or sorcery spell, each opponent loses 2 life and you gain 2 life.$+1: You lose 1 life. Look at the top three cards of your library. Put one of them into your hand and the rest into your graveyard.$-3: Each opponent sacrifices a creature with the greatest power among creatures that player controls.$-8: Each opponent may discard a card. If they don't, they lose 3 life. Repeat this process six more times.| Read the Bones|Duskmourn: House of Horror Commander|154|C|{2}{B}|Sorcery|||Scry 2, then draw two cards. You lose 2 life.| +Reanimate|Duskmourn: House of Horror Commander|155|R|{B}|Sorcery|||Put target creature card from a graveyard onto the battlefield under your control. You lose life equal to its mana value.| +Sign in Blood|Duskmourn: House of Horror Commander|156|C|{B}{B}|Sorcery|||Target player draws two cards and loses 2 life.| +Stitcher's Supplier|Duskmourn: House of Horror Commander|157|U|{B}|Creature - Zombie|1|1|When Stitcher's Supplier enters or dies, mill three cards.| +Syr Konrad, the Grim|Duskmourn: House of Horror Commander|158|U|{3}{B}{B}|Legendary Creature - Human Knight|5|4|Whenever another creature dies, or a creature card is put into a graveyard from anywhere other than the battlefield, or a creature card leaves your graveyard, Syr Konrad, the Grim deals 1 damage to each opponent.${1}{B}: Each player mills a card.| +Whip of Erebos|Duskmourn: House of Horror Commander|159|R|{2}{B}{B}|Legendary Enchantment Artifact|||Creatures you control have lifelink.${2}{B}{B}, {T}: Return target creature card from your graveyard to the battlefield. It gains haste. Exile it at the beginning of the next end step. If it would leave the battlefield, exile it instead of putting it anywhere else. Activate only as a sorcery.| +Blasphemous Act|Duskmourn: House of Horror Commander|160|R|{8}{R}|Sorcery|||This spell costs {1} less to cast for each creature on the battlefield.$Blasphemous Act deals 13 damage to each creature.| +Brash Taunter|Duskmourn: House of Horror Commander|161|R|{4}{R}|Creature - Goblin|1|1|Indestructible$Whenever Brash Taunter is dealt damage, it deals that much damage to target opponent.${2}{R}, {T}: Brash Taunter fights another target creature.| +Chaos Warp|Duskmourn: House of Horror Commander|162|R|{2}{R}|Instant|||The owner of target permanent shuffles it into their library, then reveals the top card of their library. If it's a permanent card, they put it onto the battlefield.| +Combustible Gearhulk|Duskmourn: House of Horror Commander|163|M|{4}{R}{R}|Artifact Creature - Construct|6|6|First strike$When Combustible Gearhulk enters, target opponent may have you draw three cards. If the player doesn't, you mill three cards, then Combustible Gearhulk deals damage to that player equal to the total mana value of those cards.| +Enchanter's Bane|Duskmourn: House of Horror Commander|164|R|{1}{R}|Enchantment|||At the beginning of your end step, target enchantment deals damage equal to its mana value to its controller unless that player sacrifices it.| +Harsh Mentor|Duskmourn: House of Horror Commander|165|R|{1}{R}|Creature - Human Cleric|2|2|Whenever an opponent activates an ability of an artifact, creature, or land on the battlefield, if it isn't a mana ability, Harsh Mentor deals 2 damage to that player.| +Light Up the Stage|Duskmourn: House of Horror Commander|166|U|{2}{R}|Sorcery|||Spectacle {R}$Exile the top two cards of your library. Until the end of your next turn, you may play those cards.| +Rampaging Ferocidon|Duskmourn: House of Horror Commander|167|R|{2}{R}|Creature - Dinosaur|3|3|Menace$Players can't gain life.$Whenever another creature enters, Rampaging Ferocidon deals 1 damage to that creature's controller.| +Tectonic Giant|Duskmourn: House of Horror Commander|168|R|{2}{R}{R}|Creature - Elemental Giant|3|4|Whenever Tectonic Giant attacks or becomes the target of a spell an opponent controls, choose one --$* Tectonic Giant deals 3 damage to each opponent.$* Exile the top two cards of your library. Choose one of them. Until the end of your next turn, you may play that card.| +Arachnogenesis|Duskmourn: House of Horror Commander|169|R|{2}{G}|Instant|||Create X 1/2 green Spider creature tokens with reach, where X is the number of creatures attacking you. Prevent all combat damage that would be dealt this turn by non-Spider creatures.| Ashaya, Soul of the Wild|Duskmourn: House of Horror Commander|170|M|{3}{G}{G}|Legendary Creature - Elemental|*|*|Ashaya, Soul of the Wild's power and toughness are each equal to the number of lands you control.$Nontoken creatures you control are Forest lands in addition to their other types.| Augur of Autumn|Duskmourn: House of Horror Commander|171|R|{1}{G}{G}|Creature - Human Druid|2|3|You may look at the top card of your library any time.$You may play lands from the top of your library.$Coven -- As long as you control three or more creatures with different powers, you may cast creature spells from the top of your library.| Beanstalk Giant|Duskmourn: House of Horror Commander|172|U|{6}{G}|Creature - Giant|*|*|Beanstalk Giant's power and toughness are each equal to the number of lands you control.| Fertile Footsteps|Duskmourn: House of Horror Commander|172|U|{2}{G}|Sorcery - Adventure|*|*|Search your library for a basic land card, put it onto the battlefield, then shuffle.| +Crawling Sensation|Duskmourn: House of Horror Commander|173|U|{2}{G}|Enchantment|||At the beginning of your upkeep, you may mill two cards.$Whenever one or more land cards are put into your graveyard from anywhere for the first time each turn, create a 1/1 green Insect creature token.| Cultivate|Duskmourn: House of Horror Commander|174|C|{2}{G}|Sorcery|||Search your library for up to two basic land cards, reveal those cards, put one onto the battlefield tapped and the other into your hand, then shuffle.| +Deathcap Cultivator|Duskmourn: House of Horror Commander|175|R|{1}{G}|Creature - Human Druid|2|1|{T}: Add {B} or {G}.$Delirium -- Deathcap Cultivator has deathtouch as long as there are four or more card types among cards in your graveyard.| Deathmist Raptor|Duskmourn: House of Horror Commander|176|M|{1}{G}{G}|Creature - Dinosaur Beast|3|3|Deathtouch$Whenever a permanent you control is turned face up, you may return Deathmist Raptor from your graveyard to the battlefield face up or face down.$Megamorph {4}{G}| Explosive Vegetation|Duskmourn: House of Horror Commander|177|U|{3}{G}|Sorcery|||Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle.| Ezuri's Predation|Duskmourn: House of Horror Commander|178|R|{5}{G}{G}{G}|Sorcery|||For each creature your opponents control, create a 4/4 green Phyrexian Beast creature token. Each of those tokens fights a different one of those creatures.| +Giant Adephage|Duskmourn: House of Horror Commander|179|M|{5}{G}{G}|Creature - Insect|7|7|Trample$Whenever Giant Adephage deals combat damage to a player, create a token that's a copy of Giant Adephage.| +Gnarlwood Dryad|Duskmourn: House of Horror Commander|180|U|{G}|Creature - Dryad Horror|1|1|Deathtouch$Delirium -- Gnarlwood Dryad gets +2/+2 as long as there are four or more card types among cards in your graveyard.| Greater Tanuki|Duskmourn: House of Horror Commander|181|C|{4}{G}{G}|Enchantment Creature - Dog|6|5|Trample$Channel -- {2}{G}, Discard Greater Tanuki: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.| +Harmonize|Duskmourn: House of Horror Commander|182|U|{2}{G}{G}|Sorcery|||Draw three cards.| +Harrow|Duskmourn: House of Horror Commander|183|C|{2}{G}|Instant|||As an additional cost to cast this spell, sacrifice a land.$Search your library for up to two basic land cards, put them onto the battlefield, then shuffle.| +Hornet Queen|Duskmourn: House of Horror Commander|184|M|{4}{G}{G}{G}|Creature - Insect|2|2|Flying, deathtouch$When Hornet Queen enters, create four 1/1 green Insect creature tokens with flying and deathtouch.| Hydra Omnivore|Duskmourn: House of Horror Commander|185|M|{4}{G}{G}|Creature - Hydra|8|8|Whenever Hydra Omnivore deals combat damage to an opponent, it deals that much damage to each other opponent.| +Inscription of Abundance|Duskmourn: House of Horror Commander|186|R|{1}{G}|Instant|||Kicker {2}{G}$Choose one. If this spell was kicked, choose any number instead.$* Put two +1/+1 counters on target creature.$* Target player gains X life, where X is the greatest power among creatures they control.$* Target creature you control fights target creature you don't control.| +Ishkanah, Grafwidow|Duskmourn: House of Horror Commander|187|M|{4}{G}|Legendary Creature - Spider|3|5|Reach$Delirium -- When Ishkanah, Grafwidow enters, if there are four or more card types among cards in your graveyard, create three 1/2 green Spider creature tokens with reach.${6}{B}: Target opponent loses 1 life for each Spider you control.| +Moldgraf Millipede|Duskmourn: House of Horror Commander|188|C|{4}{G}|Creature - Insect Horror|2|2|When Moldgraf Millipede enters, mill three cards, then put a +1/+1 counter on Moldgraf Millipede for each creature card in your graveyard.| +Mulch|Duskmourn: House of Horror Commander|189|C|{1}{G}|Sorcery|||Reveal the top four cards of your library. Put all land cards revealed this way into your hand and the rest into your graveyard.| Multani, Yavimaya's Avatar|Duskmourn: House of Horror Commander|190|M|{4}{G}{G}|Legendary Creature - Elemental Avatar|0|0|Reach, trample$Multani, Yavimaya's Avatar gets +1/+1 for each land you control and each land card in your graveyard.${1}{G}, Return two lands you control to their owner's hand: Return Multani from your graveyard to your hand.| +Obsessive Skinner|Duskmourn: House of Horror Commander|191|U|{1}{G}|Creature - Human Rogue|1|1|When Obsessive Skinner enters, put a +1/+1 counter on target creature.$Delirium -- At the beginning of each opponent's upkeep, if there are four or more card types among cards in your graveyard, put a +1/+1 counter on target creature.| Overwhelming Stampede|Duskmourn: House of Horror Commander|192|R|{3}{G}{G}|Sorcery|||Until end of turn, creatures you control gain trample and get +X/+X, where X is the greatest power among creatures you control.| Rampant Growth|Duskmourn: House of Horror Commander|193|C|{1}{G}|Sorcery|||Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.| Sakura-Tribe Elder|Duskmourn: House of Horror Commander|194|C|{1}{G}|Creature - Snake Shaman|1|1|Sacrifice Sakura-Tribe Elder: Search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.| Sandwurm Convergence|Duskmourn: House of Horror Commander|195|R|{6}{G}{G}|Enchantment|||Creatures with flying can't attack you or planeswalkers you control.$At the beginning of your end step, create a 5/5 green Wurm creature token.| +Scavenging Ooze|Duskmourn: House of Horror Commander|196|R|{1}{G}|Creature - Ooze|2|2|{G}: Exile target card from a graveyard. If it was a creature card, put a +1/+1 counter on Scavenging Ooze and you gain 1 life.| Scute Swarm|Duskmourn: House of Horror Commander|197|R|{2}{G}|Creature - Insect|1|1|Landfall -- Whenever a land you control enters, create a 1/1 green Insect creature token. If you control six or more lands, create a token that's a copy of Scute Swarm instead.| Shigeki, Jukai Visionary|Duskmourn: House of Horror Commander|198|R|{1}{G}|Legendary Enchantment Creature - Snake Druid|1|3|{1}{G}, {T}, Return Shigeki, Jukai Visionary to its owner's hand: Reveal the top four cards of your library. You may put a land card from among them onto the battlefield tapped. Put the rest into your graveyard.$Channel -- {X}{X}{G}{G}, Discard Shigeki: Return X target nonlegendary cards from your graveyard to your hand.| +Skola Grovedancer|Duskmourn: House of Horror Commander|199|C|{1}{G}|Enchantment Creature - Satyr Druid|2|2|Whenever a land card is put into your graveyard from anywhere, you gain 1 life.${2}{G}: Mill a card.| Temur War Shaman|Duskmourn: House of Horror Commander|200|R|{4}{G}{G}|Creature - Human Shaman|4|5|When Temur War Shaman enters, manifest the top card of your library.$Whenever a permanent you control is turned face up, if it's a creature, you may have it fight target creature you don't control.| +Thunderfoot Baloth|Duskmourn: House of Horror Commander|201|R|{4}{G}{G}|Creature - Beast|5|5|Trample$Lieutenant -- As long as you control your commander, Thunderfoot Baloth gets +2/+2 and other creatures you control get +2/+2 and have trample.| +Titania, Nature's Force|Duskmourn: House of Horror Commander|202|M|{4}{G}{G}|Legendary Creature - Elemental|6|6|You may play Forests from your graveyard.$Whenever a Forest you control enters, create a 5/3 green Elemental creature token.$Whenever an Elemental you control dies, you may mill three cards.| Trail of Mystery|Duskmourn: House of Horror Commander|203|R|{1}{G}|Enchantment|||Whenever a face-down creature you control enters, you may search your library for a basic land card, reveal it, put it into your hand, then shuffle.$Whenever a permanent you control is turned face up, if it's a creature, it gets +2/+2 until end of turn.| Whisperwood Elemental|Duskmourn: House of Horror Commander|204|M|{3}{G}{G}|Creature - Elemental|4|4|At the beginning of your end step, manifest the top card of your library.$Sacrifice Whisperwood Elemental: Until end of turn, face-up nontoken creatures you control gain "When this creature dies, manifest the top card of your library."| Wilderness Reclamation|Duskmourn: House of Horror Commander|205|U|{3}{G}|Enchantment|||At the beginning of your end step, untap all lands you control.| Worldspine Wurm|Duskmourn: House of Horror Commander|206|M|{8}{G}{G}{G}|Creature - Wurm|15|15|Trample$When Worldspine Wurm dies, create three 5/5 green Wurm creature tokens with trample.$When Worldspine Wurm is put into a graveyard from anywhere, shuffle it into its owner's library.| +Wrenn and Seven|Duskmourn: House of Horror Commander|207|M|{3}{G}{G}|Legendary Planeswalker - Wrenn|5|+1: Reveal the top four cards of your library. Put all land cards revealed this way into your hand and the rest into your graveyard.$0: Put any number of land cards from your hand onto the battlefield tapped.$-3: Create a green Treefolk creature token with reach and "This creature's power and toughness are each equal to the number of lands you control."$-8: Return all permanent cards from your graveyard to your hand. You get an emblem with "You have no maximum hand size."| Yavimaya Elder|Duskmourn: House of Horror Commander|208|C|{1}{G}{G}|Creature - Human Druid|2|1|When Yavimaya Elder dies, you may search your library for up to two basic land cards, reveal them, put them into your hand, then shuffle.${2}, Sacrifice Yavimaya Elder: Draw a card.| Yedora, Grave Gardener|Duskmourn: House of Horror Commander|209|U|{4}{G}|Legendary Creature - Treefolk Druid|5|5|Whenever another nontoken creature you control dies, you may return it to the battlefield face down under its owner's control. It's a Forest land.| Aesi, Tyrant of Gyre Strait|Duskmourn: House of Horror Commander|210|M|{4}{G}{U}|Legendary Creature - Serpent|5|5|You may play an additional land on each of your turns.$Landfall -- Whenever a land you control enters, you may draw a card.| Arixmethes, Slumbering Isle|Duskmourn: House of Horror Commander|211|R|{2}{G}{U}|Legendary Creature - Kraken|12|12|Arixmethes, Slumbering Isle enters tapped with five slumber counters on it.$As long as Arixmethes has a slumber counter on it, it's a land.$Whenever you cast a spell, you may remove a slumber counter from Arixmethes.${T}: Add {G}{U}.| Athreos, Shroud-Veiled|Duskmourn: House of Horror Commander|212|M|{4}{W}{B}|Legendary Enchantment Creature - God|4|7|Indestructible$As long as your devotion to white and black is less than seven, Athreos isn't a creature.$At the beginning of your end step, put a coin counter on another target creature.$Whenever a creature with a coin counter on it dies or is put into exile, return that card to the battlefield under your control.| +Binding the Old Gods|Duskmourn: House of Horror Commander|213|U|{2}{B}{G}|Enchantment - Saga|||(As this Saga enters and after your draw step, add a lore counter. Sacrifice after III.)$I -- Destroy target nonland permanent an opponent controls.$II -- Search your library for a Forest card, put it onto the battlefield tapped, then shuffle.$III -- Creatures you control gain deathtouch until end of turn.| Biomass Mutation|Duskmourn: House of Horror Commander|214|R|{X}{G/U}{G/U}|Instant|||Creatures you control have base power and toughness X/X until end of turn.| +Deadbridge Chant|Duskmourn: House of Horror Commander|215|M|{4}{B}{G}|Enchantment|||When Deadbridge Chant enters, mill ten cards.$At the beginning of your upkeep, choose a card at random in your graveyard. If it's a creature card, return it to the battlefield. Otherwise, return it to your hand.| Eureka Moment|Duskmourn: House of Horror Commander|216|C|{2}{G}{U}|Instant|||Draw two cards. You may put a land card from your hand onto the battlefield.| +Florian, Voldaren Scion|Duskmourn: House of Horror Commander|217|R|{1}{B}{R}|Legendary Creature - Vampire Noble|3|3|First strike$At the beginning of your second main phase, look at the top X cards of your library, where X is the total amount of life your opponents lost this turn. Exile one of those cards and put the rest on the bottom of your library in a random order. You may play the exiled card this turn.| +Grim Flayer|Duskmourn: House of Horror Commander|218|R|{B}{G}|Creature - Human Warrior|2|2|Trample$Whenever Grim Flayer deals combat damage to a player, surveil 3.$Delirium -- Grim Flayer gets +2/+2 as long as there are four or more card types among cards in your graveyard.| +Grisly Salvage|Duskmourn: House of Horror Commander|219|C|{B}{G}|Instant|||Reveal the top five cards of your library. You may put a creature or land card from among them into your hand. Put the rest into your graveyard.| +Grist, the Hunger Tide|Duskmourn: House of Horror Commander|220|M|{1}{B}{G}|Legendary Planeswalker - Grist|3|As long as Grist, the Hunger Tide isn't on the battlefield, it's a 1/1 Insect creature in addition to its other types.$+1: Create a 1/1 black and green Insect creature token, then mill a card. If an Insect card was milled this way, put a loyalty counter on Grist and repeat this process.$-2: You may sacrifice a creature. When you do, destroy target creature or planeswalker.$-5: Each opponent loses life equal to the number of creature cards in your graveyard.| Inkshield|Duskmourn: House of Horror Commander|221|R|{3}{W}{B}|Instant|||Prevent all combat damage that would be dealt to you this turn. For each 1 damage prevented this way, create a 2/1 white and black Inkling creature token with flying.| +Kaervek the Merciless|Duskmourn: House of Horror Commander|222|R|{5}{B}{R}|Legendary Creature - Human Shaman|5|4|Whenever an opponent casts a spell, Kaervek the Merciless deals damage equal to that spell's mana value to any target.| +Kardur, Doomscourge|Duskmourn: House of Horror Commander|223|U|{2}{B}{R}|Legendary Creature - Demon Berserker|4|3|When Kardur, Doomscourge enters, until your next turn, creatures your opponents control attack each combat if able and attack a player other than you if able.$Whenever an attacking creature dies, each opponent loses 1 life and you gain 1 life.| Life Insurance|Duskmourn: House of Horror Commander|224|R|{3}{W}{B}|Enchantment|||Extort$Whenever a nontoken creature dies, you lose 1 life and create a Treasure token.| +Mayhem Devil|Duskmourn: House of Horror Commander|225|U|{1}{B}{R}|Creature - Devil|3|3|Whenever a player sacrifices a permanent, Mayhem Devil deals 1 damage to any target.| +Nyx Weaver|Duskmourn: House of Horror Commander|226|U|{1}{B}{G}|Enchantment Creature - Spider|2|3|Reach$At the beginning of your upkeep, mill two cards.${1}{B}{G}, Exile Nyx Weaver: Return target card from your graveyard to your hand.| +Old Stickfingers|Duskmourn: House of Horror Commander|227|R|{X}{B}{G}|Legendary Creature - Horror|*|*|When you cast this spell, reveal cards from the top of your library until you reveal X creature cards. Put all creature cards revealed this way into your graveyard, then put the rest on the bottom of your library in a random order.$Old Stickfingers's power and toughness are each equal to the number of creature cards in your graveyard.| Oversimplify|Duskmourn: House of Horror Commander|228|R|{3}{G}{U}|Sorcery|||Exile all creatures. Each player creates a 0/0 green and blue Fractal creature token and puts a number of +1/+1 counters on it equal to the total power of creatures they controlled that were exiled this way.| +Rakdos Charm|Duskmourn: House of Horror Commander|229|U|{B}{R}|Instant|||Choose one --$* Exile target player's graveyard.$* Destroy target artifact.$* Each creature deals 1 damage to its controller.| +Rakdos, Lord of Riots|Duskmourn: House of Horror Commander|230|M|{B}{B}{R}{R}|Legendary Creature - Demon|6|6|You can't cast this spell unless an opponent lost life this turn.$Flying, trample$Creature spells you cast cost {1} less to cast for each 1 life your opponents have lost this turn.| Rashmi, Eternities Crafter|Duskmourn: House of Horror Commander|231|M|{2}{G}{U}|Legendary Creature - Elf Druid|2|3|Whenever you cast your first spell each turn, reveal the top card of your library. You may cast it without paying its mana cost if it's a spell with lesser mana value. If you don't cast it, put it into your hand.| Spirit-Sister's Call|Duskmourn: House of Horror Commander|232|M|{3}{W}{B}|Enchantment|||At the beginning of your end step, choose target permanent card in your graveyard. You may sacrifice a permanent that shares a card type with the chosen card. If you do, return the chosen card from your graveyard to the battlefield and it gains "If this permanent would leave the battlefield, exile it instead of putting it anywhere else."| +Spiteful Visions|Duskmourn: House of Horror Commander|233|R|{2}{B/R}{B/R}|Enchantment|||At the beginning of each player's draw step, that player draws an additional card.$Whenever a player draws a card, Spiteful Visions deals 1 damage to that player.| +Stormfist Crusader|Duskmourn: House of Horror Commander|234|R|{B}{R}|Creature - Human Knight|2|2|Menace$At the beginning of your upkeep, each player draws a card and loses 1 life.| Tatyova, Benthic Druid|Duskmourn: House of Horror Commander|235|U|{3}{G}{U}|Legendary Creature - Merfolk Druid|3|3|Landfall -- Whenever a land you control enters, you gain 1 life and draw a card.| +Theater of Horrors|Duskmourn: House of Horror Commander|236|R|{1}{B}{R}|Enchantment|||At the beginning of your upkeep, exile the top card of your library.$During your turn, if an opponent lost life this turn, you may play lands and cast spells from among cards exiled with Theater of Horrors.${3}{R}: Theater of Horrors deals 1 damage to target opponent or planeswalker.| Time Wipe|Duskmourn: House of Horror Commander|237|R|{2}{W}{W}{U}|Sorcery|||Return a creature you control to its owner's hand, then destroy all creatures.| Trygon Predator|Duskmourn: House of Horror Commander|238|U|{1}{G}{U}|Creature - Beast|2|3|Flying$Whenever Trygon Predator deals combat damage to a player, you may destroy target artifact or enchantment that player controls.| +Vial Smasher the Fierce|Duskmourn: House of Horror Commander|239|M|{1}{B}{R}|Legendary Creature - Goblin Berserker|2|3|Whenever you cast your first spell each turn, choose an opponent at random. Vial Smasher the Fierce deals damage equal to that spell's mana value to that player or a planeswalker that player controls.$Partner| Azorius Signet|Duskmourn: House of Horror Commander|240|U|{2}|Artifact|||{1}, {T}: Add {W}{U}.| +Basilisk Collar|Duskmourn: House of Horror Commander|241|R|{1}|Artifact - Equipment|||Equipped creature has deathtouch and lifelink.$Equip {2}| Brainstone|Duskmourn: House of Horror Commander|242|U|{1}|Artifact|||{2}, {T}, Sacrifice Brainstone: Draw three cards, then put two cards from your hand on top of your library in any order.| Burnished Hart|Duskmourn: House of Horror Commander|243|U|{3}|Artifact Creature - Elk|2|2|{3}, Sacrifice Burnished Hart: Search your library for up to two basic land cards, put them onto the battlefield tapped, then shuffle.| Commander's Sphere|Duskmourn: House of Horror Commander|244|C|{3}|Artifact|||{T}: Add one mana of any color in your commander's color identity.$Sacrifice Commander's Sphere: Draw a card.| +Fellwar Stone|Duskmourn: House of Horror Commander|245|U|{2}|Artifact|||{T}: Add one mana of any color that a land an opponent controls could produce.| +Golgari Signet|Duskmourn: House of Horror Commander|246|C|{2}|Artifact|||{1}, {T}: Add {B}{G}.| +Haywire Mite|Duskmourn: House of Horror Commander|247|U|{1}|Artifact Creature - Insect|1|1|When Haywire Mite dies, you gain 2 life.${G}, Sacrifice Haywire Mite: Exile target noncreature artifact or noncreature enchantment.| Mind Stone|Duskmourn: House of Horror Commander|248|U|{2}|Artifact|||{T}: Add {C}.${1}, {T}, Sacrifice Mind Stone: Draw a card.| Orzhov Signet|Duskmourn: House of Horror Commander|249|U|{2}|Artifact|||{1}, {T}: Add {W}{B}.| +Rakdos Signet|Duskmourn: House of Horror Commander|250|U|{2}|Artifact|||{1}, {T}: Add {B}{R}.| Scroll of Fate|Duskmourn: House of Horror Commander|251|R|{3}|Artifact|||{T}: Manifest a card from your hand.| Simic Signet|Duskmourn: House of Horror Commander|252|U|{2}|Artifact|||{1}, {T}: Add {G}{U}.| Solemn Simulacrum|Duskmourn: House of Horror Commander|253|R|{4}|Artifact Creature - Golem|2|2|When Solemn Simulacrum enters, you may search your library for a basic land card, put that card onto the battlefield tapped, then shuffle.$When Solemn Simulacrum dies, you may draw a card.| +Talisman of Indulgence|Duskmourn: House of Horror Commander|254|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {B} or {R}. Talisman of Indulgence deals 1 damage to you.| +Talisman of Resilience|Duskmourn: House of Horror Commander|255|U|{2}|Artifact|||{T}: Add {C}.${T}: Add {B} or {G}. Talisman of Resilience deals 1 damage to you.| +Thought Vessel|Duskmourn: House of Horror Commander|256|U|{2}|Artifact|||You have no maximum hand size.${T}: Add {C}.| +Whispersilk Cloak|Duskmourn: House of Horror Commander|257|U|{3}|Artifact - Equipment|||Equipped creature can't be blocked and has shroud.$Equip {2}| Adarkar Wastes|Duskmourn: House of Horror Commander|258|R||Land|||{T}: Add {C}.${T}: Add {W} or {U}. Adarkar Wastes deals 1 damage to you.| Arcane Sanctum|Duskmourn: House of Horror Commander|259|U||Land|||Arcane Sanctum enters tapped.${T}: Add {W}, {U}, or {B}.| Ash Barrens|Duskmourn: House of Horror Commander|260|C||Land|||{T}: Add {C}.$Basic landcycling {1}| Azorius Chancery|Duskmourn: House of Horror Commander|261|U||Land|||Azorius Chancery enters tapped.$When Azorius Chancery enters, return a land you control to its owner's hand.${T}: Add {W}{U}.| +Barren Moor|Duskmourn: House of Horror Commander|262|U||Land|||Barren Moor enters tapped.${T}: Add {B}.$Cycling {B}| +Blackcleave Cliffs|Duskmourn: House of Horror Commander|263|R||Land|||Blackcleave Cliffs enters tapped unless you control two or fewer other lands.${T}: Add {B} or {R}.| +Bloodfell Caves|Duskmourn: House of Horror Commander|264|C||Land|||Bloodfell Caves enters tapped.$When Bloodfell Caves enters, you gain 1 life.${T}: Add {B} or {R}.| Bojuka Bog|Duskmourn: House of Horror Commander|265|C||Land|||Bojuka Bog enters tapped.$When Bojuka Bog enters, exile target player's graveyard.${T}: Add {B}.| +Canyon Slough|Duskmourn: House of Horror Commander|266|R||Land - Swamp Mountain|||({T}: Add {B} or {R}.)$Canyon Slough enters tapped.$Cycling {2}| Castle Vantress|Duskmourn: House of Horror Commander|267|R||Land|||Castle Vantress enters tapped unless you control an Island.${T}: Add {U}.${2}{U}{U}, {T}: Scry 2.| Caves of Koilos|Duskmourn: House of Horror Commander|268|R||Land|||{T}: Add {C}.${T}: Add {W} or {B}. Caves of Koilos deals 1 damage to you.| +Darkmoss Bridge|Duskmourn: House of Horror Commander|269|C||Artifact Land|||Darkmoss Bridge enters tapped.$Indestructible${T}: Add {B} or {G}.| Dimir Aqueduct|Duskmourn: House of Horror Commander|270|U||Land|||Dimir Aqueduct enters tapped.$When Dimir Aqueduct enters, return a land you control to its owner's hand.${T}: Add {U}{B}.| +Dragonskull Summit|Duskmourn: House of Horror Commander|271|R||Land|||Dragonskull Summit enters tapped unless you control a Swamp or a Mountain.${T}: Add {B} or {R}.| Drownyard Temple|Duskmourn: House of Horror Commander|272|R||Land|||{T}: Add {C}.${3}: Return Drownyard Temple from your graveyard to the battlefield tapped.| +Dryad Arbor|Duskmourn: House of Horror Commander|273|R||Land Creature - Forest Dryad|1|1|(Dryad Arbor isn't a spell, it's affected by summoning sickness, and it has "{T}: Add {G}.")| Evolving Wilds|Duskmourn: House of Horror Commander|274|C||Land|||{T}, Sacrifice Evolving Wilds: Search your library for a basic land card, put it onto the battlefield tapped, then shuffle.| +Exotic Orchard|Duskmourn: House of Horror Commander|275|R||Land|||{T}: Add one mana of any color that a land an opponent controls could produce.| Flooded Grove|Duskmourn: House of Horror Commander|276|R||Land|||{T}: Add {C}.${G/U}, {T}: Add {G}{G}, {G}{U}, or {U}{U}.| +Foreboding Ruins|Duskmourn: House of Horror Commander|277|R||Land|||As Foreboding Ruins enters, you may reveal a Swamp or Mountain card from your hand. If you don't, Foreboding Ruins enters tapped.${T}: Add {B} or {R}.| +Geothermal Bog|Duskmourn: House of Horror Commander|278|C||Land - Swamp Mountain|||({T}: Add {B} or {R}.)$Geothermal Bog enters tapped.| +Golgari Rot Farm|Duskmourn: House of Horror Commander|279|C||Land|||Golgari Rot Farm enters tapped.$When Golgari Rot Farm enters, return a land you control to its owner's hand.${T}: Add {B}{G}.| +Graven Cairns|Duskmourn: House of Horror Commander|280|R||Land|||{T}: Add {C}.${B/R}, {T}: Add {B}{B}, {B}{R}, or {R}{R}.| +Grim Backwoods|Duskmourn: House of Horror Commander|281|R||Land|||{T}: Add {C}.${2}{B}{G}, {T}, Sacrifice a creature: Draw a card.| Halimar Depths|Duskmourn: House of Horror Commander|282|C||Land|||Halimar Depths enters tapped.$When Halimar Depths enters, look at the top three cards of your library, then put them back in any order.${T}: Add {U}.| Hall of Heliod's Generosity|Duskmourn: House of Horror Commander|283|R||Legendary Land|||{T}: Add {C}.${1}{W}, {T}: Put target enchantment card from your graveyard on top of your library.| Hinterland Harbor|Duskmourn: House of Horror Commander|284|R||Land|||Hinterland Harbor enters tapped unless you control a Forest or an Island.${T}: Add {G} or {U}.| +Jungle Hollow|Duskmourn: House of Horror Commander|285|C||Land|||Jungle Hollow enters tapped.$When Jungle Hollow enters, you gain 1 life.${T}: Add {B} or {G}.| +Leechridden Swamp|Duskmourn: House of Horror Commander|286|U||Land - Swamp|||({T}: Add {B}.)$Leechridden Swamp enters tapped.${B}, {T}: Each opponent loses 1 life. Activate only if you control two or more black permanents.| +Llanowar Wastes|Duskmourn: House of Horror Commander|287|R||Land|||{T}: Add {C}.${T}: Add {B} or {G}. Llanowar Wastes deals 1 damage to you.| Mosswort Bridge|Duskmourn: House of Horror Commander|288|R||Land|||Hideaway 4$Mosswort Bridge enters tapped.${T}: Add {G}.${G}, {T}: You may play the exiled card without paying its mana cost if creatures you control have total power 10 or greater.| Myriad Landscape|Duskmourn: House of Horror Commander|289|U||Land|||Myriad Landscape enters tapped.${T}: Add {C}.${2}, {T}, Sacrifice Myriad Landscape: Search your library for up to two basic land cards that share a land type, put them onto the battlefield tapped, then shuffle.| +Necroblossom Snarl|Duskmourn: House of Horror Commander|290|R||Land|||As Necroblossom Snarl enters, you may reveal a Swamp or Forest card from your hand. If you don't, Necroblossom Snarl enters tapped.${T}: Add {B} or {G}.| Obscura Storefront|Duskmourn: House of Horror Commander|291|C||Land|||When Obscura Storefront enters, sacrifice it. When you do, search your library for a basic Plains, Island, or Swamp card, put it onto the battlefield tapped, then shuffle and you gain 1 life.| Orzhov Basilica|Duskmourn: House of Horror Commander|292|U||Land|||Orzhov Basilica enters tapped.$When Orzhov Basilica enters, return a land you control to its owner's hand.${T}: Add {W}{B}.| Overflowing Basin|Duskmourn: House of Horror Commander|293|R||Land|||{1}, {T}: Add {G}{U}.| Quandrix Campus|Duskmourn: House of Horror Commander|294|C||Land|||Quandrix Campus enters tapped.${T}: Add {G} or {U}.${4}, {T}: Scry 1.| Reliquary Tower|Duskmourn: House of Horror Commander|295|U||Land|||You have no maximum hand size.${T}: Add {C}.| +Shadowblood Ridge|Duskmourn: House of Horror Commander|296|R||Land|||{1}, {T}: Add {B}{R}.| +Shivan Gorge|Duskmourn: House of Horror Commander|297|R||Legendary Land|||{T}: Add {C}.${2}{R}, {T}: Shivan Gorge deals 1 damage to each opponent.| Simic Growth Chamber|Duskmourn: House of Horror Commander|298|U||Land|||Simic Growth Chamber enters tapped.$When Simic Growth Chamber enters, return a land you control to its owner's hand.${T}: Add {G}{U}.| +Smoldering Marsh|Duskmourn: House of Horror Commander|299|R||Land - Swamp Mountain|||({T}: Add {B} or {R}.)$Smoldering Marsh enters tapped unless you control two or more basic lands.| +Spinerock Knoll|Duskmourn: House of Horror Commander|300|R||Land|||Hideaway 4$Spinerock Knoll enters tapped.${T}: Add {R}.${R}, {T}: You may play the exiled card without paying its mana cost if an opponent was dealt 7 or more damage this turn.| +Sulfurous Springs|Duskmourn: House of Horror Commander|301|R||Land|||{T}: Add {C}.${T}: Add {B} or {R}. Sulfurous Springs deals 1 damage to you.| Tainted Field|Duskmourn: House of Horror Commander|302|U||Land|||{T}: Add {C}.${T}: Add {W} or {B}. Activate only if you control a Swamp.| Tainted Isle|Duskmourn: House of Horror Commander|303|U||Land|||{T}: Add {C}.${T}: Add {U} or {B}. Activate only if you control a Swamp.| +Tainted Peak|Duskmourn: House of Horror Commander|304|U||Land|||{T}: Add {C}.${T}: Add {B} or {R}. Activate only if you control a Swamp.| +Tainted Wood|Duskmourn: House of Horror Commander|305|U||Land|||{T}: Add {C}.${T}: Add {B} or {G}. Activate only if you control a Swamp.| Tangled Islet|Duskmourn: House of Horror Commander|306|C||Land - Forest Island|||({T}: Add {G} or {U}.)$Tangled Islet enters tapped.| Temple of Deceit|Duskmourn: House of Horror Commander|307|R||Land|||Temple of Deceit enters tapped.$When Temple of Deceit enters, scry 1.${T}: Add {U} or {B}.| Temple of Enlightenment|Duskmourn: House of Horror Commander|308|R||Land|||Temple of Enlightenment enters tapped.$When Temple of Enlightenment enters, scry 1.${T}: Add {W} or {U}.| +Temple of Malady|Duskmourn: House of Horror Commander|309|R||Land|||Temple of Malady enters tapped.$When Temple of Malady enters, scry 1.${T}: Add {B} or {G}.| +Temple of Malice|Duskmourn: House of Horror Commander|310|R||Land|||Temple of Malice enters tapped.$When Temple of Malice enters, scry 1.${T}: Add {B} or {R}.| Temple of Mystery|Duskmourn: House of Horror Commander|311|R||Land|||Temple of Mystery enters tapped.$When Temple of Mystery enters, scry 1.${T}: Add {G} or {U}.| Temple of Silence|Duskmourn: House of Horror Commander|312|R||Land|||Temple of Silence enters tapped.$When Temple of Silence enters, scry 1.${T}: Add {W} or {B}.| Temple of the False God|Duskmourn: House of Horror Commander|313|U||Land|||{T}: Add {C}{C}. Activate only if you control five or more lands.| @@ -54797,8 +54901,15 @@ Thornwood Falls|Duskmourn: House of Horror Commander|314|C||Land|||Thornwood Fal Thriving Heath|Duskmourn: House of Horror Commander|315|C||Land|||Thriving Heath enters tapped. As it enters, choose a color other than white.${T}: Add {W} or one mana of the chosen color.| Thriving Isle|Duskmourn: House of Horror Commander|316|C||Land|||Thriving Isle enters tapped. As it enters, choose a color other than blue.${T}: Add {U} or one mana of the chosen color.| Thriving Moor|Duskmourn: House of Horror Commander|317|C||Land|||Thriving Moor enters tapped. As it enters, choose a color other than black.${T}: Add {B} or one mana of the chosen color.| +Tranquil Thicket|Duskmourn: House of Horror Commander|318|C||Land|||Tranquil Thicket enters tapped.${T}: Add {G}.$Cycling {G}| +Tree of Tales|Duskmourn: House of Horror Commander|319|C||Artifact Land|||{T}: Add {G}.| +Twilight Mire|Duskmourn: House of Horror Commander|320|R||Land|||{T}: Add {C}.${B/G}, {T}: Add {B}{B}, {B}{G}, or {G}{G}.| Underground River|Duskmourn: House of Horror Commander|321|R||Land|||{T}: Add {C}.${T}: Add {U} or {B}. Underground River deals 1 damage to you.| +Vault of Whispers|Duskmourn: House of Horror Commander|322|C||Artifact Land|||{T}: Add {B}.| Vineglimmer Snarl|Duskmourn: House of Horror Commander|323|R||Land|||As Vineglimmer Snarl enters, you may reveal a Forest or Island card from your hand. If you don't, Vineglimmer Snarl enters tapped.${T}: Add {G} or {U}.| +Viridescent Bog|Duskmourn: House of Horror Commander|324|R||Land|||{1}, {T}: Add {B}{G}.| +Witch's Clinic|Duskmourn: House of Horror Commander|325|R||Land|||{T}: Add {C}.${2}, {T}: Target commander gains lifelink until end of turn.| +Woodland Cemetery|Duskmourn: House of Horror Commander|326|R||Land|||Woodland Cemetery enters tapped unless you control a Swamp or a Forest.${T}: Add {B} or {G}.| Yavimaya Coast|Duskmourn: House of Horror Commander|327|R||Land|||{T}: Add {C}.${T}: Add {G} or {U}. Yavimaya Coast deals 1 damage to you.| Crypt Ghast|Duskmourn: House of Horror Commander|368|M|{3}{B}|Creature - Spirit|2|2|Extort$Whenever you tap a Swamp for mana, add an additional {B}.| Damn|Duskmourn: House of Horror Commander|369|M|{B}{B}|Sorcery|||Destroy target creature. A creature destroyed this way can't be regenerated.$Overload {2}{W}{W}| From f0904f6a293b3858f1b20b3368f343d08238d6e6 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 09:55:53 -0400 Subject: [PATCH 17/62] [DSK] update spoiler --- Utils/mtg-cards-data.txt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Utils/mtg-cards-data.txt b/Utils/mtg-cards-data.txt index 6d73ade7d88..a763fb8efbb 100644 --- a/Utils/mtg-cards-data.txt +++ b/Utils/mtg-cards-data.txt @@ -54041,6 +54041,7 @@ Fear of Isolation|Duskmourn: House of Horror|58|U|{1}{U}|Enchantment Creature - Floodpits Drowner|Duskmourn: House of Horror|59|U|{1}{U}|Creature - Merfolk|2|1|Flash$Vigilance$When Floodpits Drowner enters, tap target creature an opponent controls and put a stun counter on it.${1}{U}, {T}: Shuffle Floodpits Drowner and target creature with a stun counter on it into their owners' libraries.| Get Out|Duskmourn: House of Horror|60|U|{U}{U}|Instant|||Choose one --$* Counter target creature or enchantment spell.$* Return one or two target creatures and/or enchantments you own to your hand.| Ghostly Keybearer|Duskmourn: House of Horror|61|U|{3}{U}|Creature - Spirit|3|3|Flying$Whenever Ghostly Keybearer deals combat damage to a player, unlock a locked door of up to one target Room you control.| +Glimmerburst|Duskmourn: House of Horror|62|C|{3}{U}|Instant|||Draw two cards. Create a 1/1 white Glimmer enchantment creature token.| Leyline of Transformation|Duskmourn: House of Horror|63|R|{2}{U}{U}|Enchantment|||If Leyline of Transformation is in your opening hand, you may begin the game with it on the battlefield.$As Leyline of Transformation enters, choose a creature type.$Creatures you control are the chosen type in addition to their other types. The same is true for creature spells you control and creature cards you own that aren't on the battlefield.| Marina Vendrell's Grimoire|Duskmourn: House of Horror|64|R|{5}{U}|Legendary Artifact|||When Marina Vendrell's Grimoire enters, if you cast it, draw five cards.$You have no maximum hand size and don't lose the game for having 0 or less life.$Whenever you gain life, draw that many cards.$Whenever you lose life, discard that many cards. Then if you have no cards in hand, you lose the game.| Meat Locker // Drowned Diner|Duskmourn: House of Horror|65|C|{2}{U}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, tap up to one target creature and put two stun counters on it.$Drowned Diner${3}{U}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, draw three cards, then discard a card.| @@ -54104,7 +54105,7 @@ Vile Mutilator|Duskmourn: House of Horror|122|U|{5}{B}{B}|Creature - Demon|6|5|A Winter's Intervention|Duskmourn: House of Horror|123|C|{1}{B}|Instant|||Winter's Intervention deals 2 damage to target creature. You gain 2 life.| Withering Torment|Duskmourn: House of Horror|124|U|{2}{B}|Instant|||Destroy target creature or enchantment. You lose 2 life.| Bedhead Beastie|Duskmourn: House of Horror|125|C|{4}{R}{R}|Creature - Beast|5|6|Menace$Mountaincycling {2}| -Betrayer's Bargain|Duskmourn: House of Horror|126|U|{1}{R}|Instant|||As an additioinal cost to cast this spell, sacrifice a creature or enchantment or pay {2}.$Betrayer's Bargain deals 5 damage to target creature. If that creature would die this turn, exile it instead.| +Betrayer's Bargain|Duskmourn: House of Horror|126|U|{1}{R}|Instant|||As an additional cost to cast this spell, sacrifice a creature or enchantment or pay {2}.$Betrayer's Bargain deals 5 damage to target creature. If that creature would die this turn, exile it instead.| Boilerbilges Ripper|Duskmourn: House of Horror|127|C|{4}{R}|Creature - Human Assassin|4|4|When Boilerbilges Ripper enters, you may sacrifice another creature or enchantment. When you do, Boilerbilges Ripper deals 2 damage to any target.| Chainsaw|Duskmourn: House of Horror|128|R|{1}{R}|Artifact - Equipment|||When Chainsaw enters, it deals 3 damage to up to one target creature.$Whenever one or more creatures die, put a rev counter on Chainsaw.$Equipped creature gets +X/+0, where X is the number of rev counters on Chainsaw.$Equip {3}| Charred Foyer // Warped Space|Duskmourn: House of Horror|129|M|{3}{R}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$At the beginning of your upkeep, exile the top card of your library. You may play that card this turn.$Warped Space${4}{R}{R}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Once each turn, you may pay {0} rather than pay the mana cost for a spell you cast from exile.| @@ -54136,7 +54137,7 @@ Ripchain Razorkin|Duskmourn: House of Horror|154|C|{3}{R}|Creature - Human Berse The Rollercrusher Ride|Duskmourn: House of Horror|155|M|{X}{2}{R}|Legendary Enchantment|||Delirium -- If a source you control would deal noncombat damage to a permanent or player while there are four or more card types among cards in your graveyard, it deals double that damage instead.$When The Rollercrusher Ride enters, it deals X damage to each of up to X target creatures.| Scorching Dragonfire|Duskmourn: House of Horror|156|C|{1}{R}|Instant|||Scorching Dragonfire deals 3 damage to target creature or planeswalker. If that creature or planeswalker would die this turn, exile it instead.| Screaming Nemesis|Duskmourn: House of Horror|157|M|{2}{R}|Creature - Spirit|3|3|Haste$Whenever Screaming Nemesis is dealt damage, it deals that much damage to any other target. If a player is dealt damage this way, they can't gain life for the rest of the game.| -Ticket Booth // Tunnel of Hate|Duskmourn: House of Horror|158|C|{2}{R}|Enchantment|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, manifest dread.$Tunnel of Hate${4}{R}{R}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Whenever you attack, target attacking creature gains double strike until end of turn.| +Ticket Booth // Tunnel of Hate|Duskmourn: House of Horror|158|C|{2}{R}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, manifest dread.$Tunnel of Hate${4}{R}{R}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Whenever you attack, target attacking creature gains double strike until end of turn.| Trial of Agony|Duskmourn: House of Horror|159|U|{R}|Sorcery|||Choose two target creatures controlled by the same opponent. That player chooses one of those creatures. Trial of Agony deals 5 damage to that creature, and the other can't block this turn.| Turn Inside Out|Duskmourn: House of Horror|160|C|{R}|Instant|||Target creature gets +3/+0 until end of turn. When it dies this turn, manifest dread.| Untimely Malfunction|Duskmourn: House of Horror|161|U|{1}{R}|Instant|||Choose one --$* Destroy target artifact.$* Change the target of target spell or ability with a single target.$* One or two target creatures can't block this turn.| @@ -54165,7 +54166,7 @@ Hedge Shredder|Duskmourn: House of Horror|183|R|{2}{G}{G}|Artifact - Vehicle|5|5 Horrid Vigor|Duskmourn: House of Horror|184|C|{1}{G}|Instant|||Target creature gains deathtouch and indestructible until end of turn.| House Cartographer|Duskmourn: House of Horror|185|U|{1}{G}|Creature - Human Scout Survivor|2|2|Survival -- At the beginning of your second main phase, if House Cartographer is tapped, reveal cards from the top of your library until you reveal a land card. Put that card into your hand and the rest on the bottom of your library in a random order.| Insidious Fungus|Duskmourn: House of Horror|186|U|{G}|Creature - Fungus|1|2|{2}, Sacrifice Insidious Fungus: Choose one --$* Destroy target artifact.$* Destroy target enchantment.$* Draw a card. Then you may put a land card from your hand onto the battlefield tapped.| -Kona, Rescue Beastie|Duskmourn: House of Horror|187|R|{3}{G}|Legendary Creature - Beast Survivor|4|3|Survival -- At the beginning of your second main phase, if Kona, Rescue Beastie is tapped, you may put a permanent card from your hand onto the battlefield| +Kona, Rescue Beastie|Duskmourn: House of Horror|187|R|{3}{G}|Legendary Creature - Beast Survivor|4|3|Survival -- At the beginning of your second main phase, if Kona, Rescue Beastie is tapped, you may put a permanent card from your hand onto the battlefield.| Leyline of Mutation|Duskmourn: House of Horror|188|R|{2}{G}{G}|Enchantment|||If Leyline of Mutation is in your opening hand, you may begin the game with it on the battlefield.$You may pay {W}{U}{B}{R}{G} rather than pay the mana cost for spells that you cast.| Manifest Dread|Duskmourn: House of Horror|189|C|{1}{G}|Sorcery|||Manifest dread.| Moldering Gym // Weight Room|Duskmourn: House of Horror|190|C|{2}{G}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, search your library for a basic land card, put it onto the battlefield tapped, then shuffle.$Weight Room${5}{G}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, manifest dread, then put three +1/+1 counters on that creature.| @@ -54199,18 +54200,19 @@ Inquisitive Glimmer|Duskmourn: House of Horror|217|U|{W}{U}|Enchantment Creature Intruding Soulrager|Duskmourn: House of Horror|218|U|{U}{R}|Creature - Spirit|2|2|Vigilance${T}, Sacrifice a Room: Intruding Soulrager deals 2 damage to each opponent. Draw a card.| The Jolly Balloon Man|Duskmourn: House of Horror|219|R|{1}{R}{W}|Legendary Creature - Human Clown|1|4|Haste${1}, {T}: Create a token that's a copy of another target creature you control, except it's a 1/1 red Balloon creature in addition to its other colors and types and it has flying and haste. Sacrifice it at the beginning of the next end step. Activate only as a sorcery.| Kaito, Bane of Nightmares|Duskmourn: House of Horror|220|M|{2}{U}{B}|Legendary Planeswalker - Kaito|4|Ninjutsu {1}{U}{B}$During your turn, as long as Kaito has one or more loyalty counters on him, he's a 3/4 Ninja creature and has hexproof.$+1: You get an emblem with "Ninjas you control get +1/+1."$0: Surveil 2. Then draw a card for each opponent who lost life this turn.$-2: Tap target creature. Put two stun counters on it.| +Marina Vendrell|Duskmourn: House of Horror|221|R|{W}{U}{B}{R}{G}|Legendary Creature - Human Warlock|3|5|When Marina Vendrell enters, reveal the top seven cards of your library. Put all enchantment cards from among them into your hand and the rest on the bottom of your library in a random order.${T}: Lock or unlock a door of target Room you control. Activate only as a sorcery.| Midnight Mayhem|Duskmourn: House of Horror|222|U|{2}{R}{W}|Sorcery|||Create three 1/1 red Gremlin creature tokens. Gremlins you control gain menace, lifelink, and haste until end of turn.| Nashi, Searcher in the Dark|Duskmourn: House of Horror|223|R|{U}{B}|Legendary Creature - Rat Ninja Wizard|2|2|Menace$Whenever Nashi, Searcher in the Dark deals combat damage to a player, you mill that many cards. You may put any number of legendary and/or enchantment cards from among them into your hand. If you put no cards into your hand this way, put a +1/+1 counter on Nashi.| Niko, Light of Hope|Duskmourn: House of Horror|224|M|{2}{W}{U}|Legendary Creature - Human Wizard|3|4|When Niko, Light of Hope enters, create two Shard tokens.${2}, {T}: Exile target nonlegendary creature you control. Shards you control become copies of it until the beginning of the next end step. Return it to the battlefield under its owner's control at the beginning of the next end step.| Oblivious Bookworm|Duskmourn: House of Horror|225|U|{G}{U}|Creature - Human Wizard|2|3|At the beginning of your end step, you may draw a card. If you do, discard a card unless a permanent entered the battlefield face down under your control this turn or you turned a permanent face up this turn.| Peer Past the Veil|Duskmourn: House of Horror|226|R|{2}{R}{G}|Instant|||Discard your hand. Then draw X cards, where X is the number of card types among cards in your graveyard.| -Restricted Office // Lecture Hall|Duskmourn: House of Horror|227|R|{2}{W}{W}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this room, destroy all creatures with power 3 or greater.$Lecture Hall${5}{U}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Other permanents you control have hexproof.| +Restricted Office // Lecture Hall|Duskmourn: House of Horror|227|R|{2}{W}{W}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, destroy all creatures with power 3 or greater.$Lecture Hall${5}{U}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$Other permanents you control have hexproof.| Rip, Spawn Hunter|Duskmourn: House of Horror|228|R|{2}{G}{W}|Legendary Creature - Human Survivor|4|4|Survival -- At the beginning of your second main phase, if Rip, Spawn Hunter is tapped, reveal the top X cards of your library, where X is its power. Put any number of creature and/or Vehicle cards with different powers from among them into your hand. Put the rest on the bottom of your library in a random order.| Rite of the Moth|Duskmourn: House of Horror|229|U|{1}{W}{B}{B}|Sorcery|||Return target creature card from your graveyard to the battlefield with a finality counter on it.$Flashback {3}{W}{W}{B}| -Roaring Furnace // Steaming Sauna|Duskmourn: House of Horror|230|R|{1}{R}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, this Room deals damage equal to the number of cards in your hand to target creature an opponent controls.$Steaming Sauna${3}{U}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$You have no maximum hand size. At the beginning of your end step, draw a card.| +Roaring Furnace // Steaming Sauna|Duskmourn: House of Horror|230|R|{1}{R}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, this Room deals damage equal to the number of cards in your hand to target creature an opponent controls.$Steaming Sauna${3}{U}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$You have no maximum hand size.$At the beginning of your end step, draw a card.| Sawblade Skinripper|Duskmourn: House of Horror|231|U|{1}{B}{R}|Creature - Human Assassin|3|2|Menace${2}, Sacrifice another creature or enchantment: Put a +1/+1 counter on Sawblade Skinripper.$At the beginning of your end step, if you sacrificed one or more permanents this turn, Sawblade Skinripper deals that much damage to any target.| Shrewd Storyteller|Duskmourn: House of Horror|232|U|{1}{G}{W}|Creature - Human Survivor|3|3|Survival -- At the beginning of your second main phase, if Shrewd Storyteller is tapped, put a +1/+1 counter on target creature.| -Shroudstomper|Duskmourn: House of Horror|233|U|{3}{W}{W}{B}{B}|Creature - Elemental|5|5|Deathtouch Whenever Shroudstomper enters or attacks, each opponent loses 2 life. You gain 2 life and draw a card.| +Shroudstomper|Duskmourn: House of Horror|233|U|{3}{W}{W}{B}{B}|Creature - Elemental|5|5|Deathtouch$Whenever Shroudstomper enters or attacks, each opponent loses 2 life. You gain 2 life and draw a card.| Skullsnap Nuisance|Duskmourn: House of Horror|234|U|{U}{B}|Creature - Insect Skeleton|1|4|Flying$Eerie -- Whenever an enchantment you control enters and whenever you fully unlock a Room, surveil 1.| Smoky Lounge // Misty Salon|Duskmourn: House of Horror|235|U|{2}{R}|Enchantment - Room|||(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$At the beginning of your first main phase, add {R}{R}. Spend this mana only to cast Room spells and unlock doors.$Misty Salon${3}{U}$Enchantment -- Room$(You may cast either half. That door unlocks on the battlefield. As a sorcery, you may pay the mana cost of a locked door to unlock it.)$When you unlock this door, create an X/X blue Spirit creature token with flying, where X is the number of unlocked doors among Rooms you control.| The Swarmweaver|Duskmourn: House of Horror|236|R|{2}{B}{G}|Legendary Artifact Creature - Scarecrow|2|3|When The Swarmweaver enters, create two 1/1 black and green Insect creature tokens with flying.$Delirium -- As long as there are four or more card types among cards in your graveyard, Insects and Spiders you control get +1/+1 and have deathtouch.| From 01655f89cbcb670671d48a9403c55544b90f0265 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 09:56:39 -0400 Subject: [PATCH 18/62] [DSK] Implement Glimmerburst --- Mage.Sets/src/mage/cards/g/Glimmerburst.java | 33 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 34 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/g/Glimmerburst.java diff --git a/Mage.Sets/src/mage/cards/g/Glimmerburst.java b/Mage.Sets/src/mage/cards/g/Glimmerburst.java new file mode 100644 index 00000000000..2f9694c1a6f --- /dev/null +++ b/Mage.Sets/src/mage/cards/g/Glimmerburst.java @@ -0,0 +1,33 @@ +package mage.cards.g; + +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.game.permanent.token.GlimmerToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Glimmerburst extends CardImpl { + + public Glimmerburst(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{3}{U}"); + + // Draw two cards. Create a 1/1 white Glimmer enchantment creature token. + this.getSpellAbility().addEffect(new DrawCardSourceControllerEffect(2)); + this.getSpellAbility().addEffect(new CreateTokenEffect(new GlimmerToken())); + } + + private Glimmerburst(final Glimmerburst card) { + super(card); + } + + @Override + public Glimmerburst copy() { + return new Glimmerburst(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 93bb23b19b9..7564ee7aea9 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -98,6 +98,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Get Out", 60, Rarity.UNCOMMON, mage.cards.g.GetOut.class)); cards.add(new SetCardInfo("Give In to Violence", 101, Rarity.COMMON, mage.cards.g.GiveInToViolence.class)); cards.add(new SetCardInfo("Glimmer Seeker", 14, Rarity.UNCOMMON, mage.cards.g.GlimmerSeeker.class)); + cards.add(new SetCardInfo("Glimmerburst", 62, Rarity.COMMON, mage.cards.g.Glimmerburst.class)); cards.add(new SetCardInfo("Glimmerlight", 249, Rarity.COMMON, mage.cards.g.Glimmerlight.class)); cards.add(new SetCardInfo("Gloomlake Verge", 260, Rarity.RARE, mage.cards.g.GloomlakeVerge.class)); cards.add(new SetCardInfo("Grasping Longneck", 180, Rarity.COMMON, mage.cards.g.GraspingLongneck.class)); From 05e738efb509d932e14402e874a1b721d3a2f199 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 10:11:08 -0400 Subject: [PATCH 19/62] [DSK] Implement Beastie Beatdown --- .../src/mage/cards/b/BeastieBeatdown.java | 55 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 56 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/b/BeastieBeatdown.java diff --git a/Mage.Sets/src/mage/cards/b/BeastieBeatdown.java b/Mage.Sets/src/mage/cards/b/BeastieBeatdown.java new file mode 100644 index 00000000000..880054a14ad --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BeastieBeatdown.java @@ -0,0 +1,55 @@ +package mage.cards.b; + +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.common.DamageWithPowerFromOneToAnotherTargetEffect; +import mage.abilities.effects.common.InfoEffect; +import mage.abilities.effects.common.counter.AddCountersTargetEffect; +import mage.abilities.hint.common.CardTypesInGraveyardHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.AbilityWord; +import mage.constants.CardType; +import mage.counters.CounterType; +import mage.target.common.TargetControlledCreaturePermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class BeastieBeatdown extends CardImpl { + + public BeastieBeatdown(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{R}{G}"); + + // Choose target creature you control and target creature an opponent controls. + this.getSpellAbility().addEffect(new InfoEffect( + "Choose target creature you control and target creature an opponent controls." + )); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addTarget(new TargetOpponentsCreaturePermanent()); + + // Delirium -- If there are four or more card types among cards in your graveyard, put two +1/+1 counters on the creature you control. + this.getSpellAbility().addEffect(new ConditionalOneShotEffect( + new AddCountersTargetEffect(CounterType.P1P1.createInstance(2)), + DeliriumCondition.instance, AbilityWord.DELIRIUM.formatWord() + "If there are four or more " + + "card types among cards in your graveyard, put two +1/+1 counters on the creature you control." + ).concatBy("
")); + this.getSpellAbility().addHint(CardTypesInGraveyardHint.YOU); + + // The creature you control deals damage equal to its power to the creature an opponent controls. + this.getSpellAbility().addEffect(new DamageWithPowerFromOneToAnotherTargetEffect() + .setText("
The creature you control deals damage equal to its power to the creature an opponent controls.")); + } + + private BeastieBeatdown(final BeastieBeatdown card) { + super(card); + } + + @Override + public BeastieBeatdown copy() { + return new BeastieBeatdown(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 7564ee7aea9..0ce6954b5c0 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -33,6 +33,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Baseball Bat", 209, Rarity.UNCOMMON, mage.cards.b.BaseballBat.class)); cards.add(new SetCardInfo("Bashful Beastie", 169, Rarity.COMMON, mage.cards.b.BashfulBeastie.class)); cards.add(new SetCardInfo("Bear Trap", 243, Rarity.COMMON, mage.cards.b.BearTrap.class)); + cards.add(new SetCardInfo("Beastie Beatdown", 210, Rarity.UNCOMMON, mage.cards.b.BeastieBeatdown.class)); cards.add(new SetCardInfo("Bedhead Beastie", 125, Rarity.COMMON, mage.cards.b.BedheadBeastie.class)); cards.add(new SetCardInfo("Betrayer's Bargain", 126, Rarity.UNCOMMON, mage.cards.b.BetrayersBargain.class)); cards.add(new SetCardInfo("Blazemire Verge", 256, Rarity.RARE, mage.cards.b.BlazemireVerge.class)); From c79ed1ea72f293bda4a67bbc4e98a5c5b293f3f8 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 10:35:30 -0400 Subject: [PATCH 20/62] rework allowing additional modes conditionally --- Mage.Sets/src/mage/cards/a/AkromasWill.java | 2 +- Mage.Sets/src/mage/cards/d/DiscipleOfPerdition.java | 2 +- Mage.Sets/src/mage/cards/d/DrownInDreams.java | 2 +- Mage.Sets/src/mage/cards/f/FlameOfAnor.java | 3 +-- Mage.Sets/src/mage/cards/j/JeskasWill.java | 2 +- Mage.Sets/src/mage/cards/k/KamahlsWill.java | 2 +- Mage.Sets/src/mage/cards/k/KlauthsWill.java | 4 ++-- Mage.Sets/src/mage/cards/l/LetsPlayAGame.java | 2 +- Mage.Sets/src/mage/cards/m/MoltenCollapse.java | 2 +- Mage.Sets/src/mage/cards/s/SakashimasWill.java | 2 +- Mage.Sets/src/mage/cards/s/SeeDouble.java | 2 +- Mage.Sets/src/mage/cards/s/SoulTransfer.java | 2 +- Mage.Sets/src/mage/cards/s/SzatsWill.java | 2 +- Mage.Sets/src/mage/cards/w/WailOfTheForgotten.java | 2 +- Mage/src/main/java/mage/abilities/Modes.java | 9 +++------ 15 files changed, 18 insertions(+), 22 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AkromasWill.java b/Mage.Sets/src/mage/cards/a/AkromasWill.java index f9ea1387a8d..afddbde25d3 100644 --- a/Mage.Sets/src/mage/cards/a/AkromasWill.java +++ b/Mage.Sets/src/mage/cards/a/AkromasWill.java @@ -33,7 +33,7 @@ public final class AkromasWill extends CardImpl { this.getSpellAbility().getModes().setChooseText( "Choose one. If you control a commander as you cast this spell, you may choose both instead." ); - this.getSpellAbility().getModes().setMoreCondition(ControlACommanderCondition.instance); + this.getSpellAbility().getModes().setMoreCondition(2, ControlACommanderCondition.instance); // • Creatures you control gain flying, vigilance, and double strike until end of turn. this.getSpellAbility().addEffect(new GainAbilityControlledEffect( diff --git a/Mage.Sets/src/mage/cards/d/DiscipleOfPerdition.java b/Mage.Sets/src/mage/cards/d/DiscipleOfPerdition.java index dffa69afe73..d4bad5377df 100644 --- a/Mage.Sets/src/mage/cards/d/DiscipleOfPerdition.java +++ b/Mage.Sets/src/mage/cards/d/DiscipleOfPerdition.java @@ -36,7 +36,7 @@ public final class DiscipleOfPerdition extends CardImpl { // * You draw a card and you lose 1 life. Ability ability = new DiesSourceTriggeredAbility(new DrawCardSourceControllerEffect(1, true), false); ability.getModes().setChooseText("choose one. If you have exactly 13 life, you may choose both."); - ability.getModes().setMoreCondition(new LifeCompareCondition(TargetController.YOU, ComparisonType.EQUAL_TO, 13)); + ability.getModes().setMoreCondition(2, new LifeCompareCondition(TargetController.YOU, ComparisonType.EQUAL_TO, 13)); ability.addEffect(new LoseLifeSourceControllerEffect(1).concatBy("and")); // * Exile target opponent's graveyard. That player loses 1 life. diff --git a/Mage.Sets/src/mage/cards/d/DrownInDreams.java b/Mage.Sets/src/mage/cards/d/DrownInDreams.java index 39ae58f77c6..50712f11214 100644 --- a/Mage.Sets/src/mage/cards/d/DrownInDreams.java +++ b/Mage.Sets/src/mage/cards/d/DrownInDreams.java @@ -28,7 +28,7 @@ public final class DrownInDreams extends CardImpl { this.getSpellAbility().getModes().setChooseText( "Choose one. If you control a commander as you cast this spell, you may choose both." ); - this.getSpellAbility().getModes().setMoreCondition(ControlACommanderCondition.instance); + this.getSpellAbility().getModes().setMoreCondition(2, ControlACommanderCondition.instance); // • Target player draws X cards. this.getSpellAbility().addEffect(new DrawCardTargetEffect(GetXValue.instance)); diff --git a/Mage.Sets/src/mage/cards/f/FlameOfAnor.java b/Mage.Sets/src/mage/cards/f/FlameOfAnor.java index 19fc14d2f12..67dd5344c2a 100644 --- a/Mage.Sets/src/mage/cards/f/FlameOfAnor.java +++ b/Mage.Sets/src/mage/cards/f/FlameOfAnor.java @@ -32,8 +32,7 @@ public final class FlameOfAnor extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}{R}"); // Choose one. If you control a Wizard as you cast this spell, you may choose two instead. - this.getSpellAbility().getModes().setMoreCondition(condition); - this.getSpellAbility().getModes().setMoreLimit(2); + this.getSpellAbility().getModes().setMoreCondition(2, condition); this.getSpellAbility().getModes().setChooseText( "Choose one. If you control a Wizard as you cast this spell, you may choose two instead." ); diff --git a/Mage.Sets/src/mage/cards/j/JeskasWill.java b/Mage.Sets/src/mage/cards/j/JeskasWill.java index 7cde1f4968b..041696a974b 100644 --- a/Mage.Sets/src/mage/cards/j/JeskasWill.java +++ b/Mage.Sets/src/mage/cards/j/JeskasWill.java @@ -29,7 +29,7 @@ public final class JeskasWill extends CardImpl { this.getSpellAbility().getModes().setChooseText( "Choose one. If you control a commander as you cast this spell, you may choose both instead." ); - this.getSpellAbility().getModes().setMoreCondition(ControlACommanderCondition.instance); + this.getSpellAbility().getModes().setMoreCondition(2, ControlACommanderCondition.instance); // • Add {R} for each card in target opponent's hand. this.getSpellAbility().addEffect(new JeskasWillEffect()); diff --git a/Mage.Sets/src/mage/cards/k/KamahlsWill.java b/Mage.Sets/src/mage/cards/k/KamahlsWill.java index 14c652aaa0d..6985bfc4e88 100644 --- a/Mage.Sets/src/mage/cards/k/KamahlsWill.java +++ b/Mage.Sets/src/mage/cards/k/KamahlsWill.java @@ -34,7 +34,7 @@ public final class KamahlsWill extends CardImpl { this.getSpellAbility().getModes().setChooseText( "Choose one. If you control a commander as you cast this spell, you may choose both." ); - this.getSpellAbility().getModes().setMoreCondition(ControlACommanderCondition.instance); + this.getSpellAbility().getModes().setMoreCondition(2, ControlACommanderCondition.instance); // • Until end of turn, any number of target lands you control become 1/1 Elemental creatures with vigilance, indestructible, and haste. They're still lands. this.getSpellAbility().addEffect(new BecomesCreatureTargetEffect( diff --git a/Mage.Sets/src/mage/cards/k/KlauthsWill.java b/Mage.Sets/src/mage/cards/k/KlauthsWill.java index bb3b0345926..e2f5d1e6c6f 100644 --- a/Mage.Sets/src/mage/cards/k/KlauthsWill.java +++ b/Mage.Sets/src/mage/cards/k/KlauthsWill.java @@ -40,7 +40,7 @@ public final class KlauthsWill extends CardImpl { this.getSpellAbility().getModes().setChooseText( "Choose one. If you control a commander as you cast this spell, you may choose both." ); - this.getSpellAbility().getModes().setMoreCondition(ControlACommanderCondition.instance); + this.getSpellAbility().getModes().setMoreCondition(2, ControlACommanderCondition.instance); // • Breathe Flame — Klauth's Will deals X damage to each creature without flying. this.getSpellAbility().addEffect(new DamageAllEffect(GetXValue.instance, filter)); @@ -76,4 +76,4 @@ enum KlauthsWillAdjuster implements TargetAdjuster { )); } } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/l/LetsPlayAGame.java b/Mage.Sets/src/mage/cards/l/LetsPlayAGame.java index 85b4f2a6db1..3dd5c6a9a8b 100644 --- a/Mage.Sets/src/mage/cards/l/LetsPlayAGame.java +++ b/Mage.Sets/src/mage/cards/l/LetsPlayAGame.java @@ -30,7 +30,7 @@ public final class LetsPlayAGame extends CardImpl { this.getSpellAbility().getModes().setChooseText( "choose one. If there are four or more card types among cards in your graveyard, choose one or more instead." ); - this.getSpellAbility().getModes().setMoreCondition(DeliriumCondition.instance); + this.getSpellAbility().getModes().setMoreCondition(3, DeliriumCondition.instance); this.getSpellAbility().setAbilityWord(AbilityWord.DELIRIUM); this.getSpellAbility().addHint(CardTypesInGraveyardHint.YOU); diff --git a/Mage.Sets/src/mage/cards/m/MoltenCollapse.java b/Mage.Sets/src/mage/cards/m/MoltenCollapse.java index eb504c08d06..b25ff5cc866 100644 --- a/Mage.Sets/src/mage/cards/m/MoltenCollapse.java +++ b/Mage.Sets/src/mage/cards/m/MoltenCollapse.java @@ -34,7 +34,7 @@ public final class MoltenCollapse extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{B}{R}"); // Choose one. If you descended this turn, you may choose both instead. - this.getSpellAbility().getModes().setMoreCondition(DescendedThisTurnCondition.instance); + this.getSpellAbility().getModes().setMoreCondition(2, DescendedThisTurnCondition.instance); this.getSpellAbility().getModes().setChooseText( "Choose one. If you descended this turn, you may choose both instead." ); diff --git a/Mage.Sets/src/mage/cards/s/SakashimasWill.java b/Mage.Sets/src/mage/cards/s/SakashimasWill.java index 7345b329420..0dc4b8884c3 100644 --- a/Mage.Sets/src/mage/cards/s/SakashimasWill.java +++ b/Mage.Sets/src/mage/cards/s/SakashimasWill.java @@ -34,7 +34,7 @@ public final class SakashimasWill extends CardImpl { this.getSpellAbility().getModes().setChooseText( "Choose one. If you control a commander as you cast this spell, you may choose both." ); - this.getSpellAbility().getModes().setMoreCondition(ControlACommanderCondition.instance); + this.getSpellAbility().getModes().setMoreCondition(2, ControlACommanderCondition.instance); // • Target opponent chooses a creature they control. You gain control of it. this.getSpellAbility().addEffect(new SakashimasWillStealEffect()); diff --git a/Mage.Sets/src/mage/cards/s/SeeDouble.java b/Mage.Sets/src/mage/cards/s/SeeDouble.java index 617e13019aa..50b509f7971 100644 --- a/Mage.Sets/src/mage/cards/s/SeeDouble.java +++ b/Mage.Sets/src/mage/cards/s/SeeDouble.java @@ -30,7 +30,7 @@ public final class SeeDouble extends CardImpl { this.getSpellAbility().getModes().setChooseText( "Choose one. If an opponent has eight or more cards in their graveyard, you may choose both." ); - this.getSpellAbility().getModes().setMoreCondition(CardsInOpponentGraveyardCondition.EIGHT); + this.getSpellAbility().getModes().setMoreCondition(2, CardsInOpponentGraveyardCondition.EIGHT); this.getSpellAbility().addHint(CardsInOpponentGraveyardCondition.EIGHT.getHint()); // * Copy target spell. You may choose new targets for the copy. diff --git a/Mage.Sets/src/mage/cards/s/SoulTransfer.java b/Mage.Sets/src/mage/cards/s/SoulTransfer.java index 47c5caeaf75..7b787b4355f 100644 --- a/Mage.Sets/src/mage/cards/s/SoulTransfer.java +++ b/Mage.Sets/src/mage/cards/s/SoulTransfer.java @@ -36,7 +36,7 @@ public final class SoulTransfer extends CardImpl { this.getSpellAbility().getModes().setChooseText( "Choose one. If you control an artifact and an enchantment as you cast this spell, you may choose both." ); - this.getSpellAbility().getModes().setMoreCondition(SoulTransferCondition.instance); + this.getSpellAbility().getModes().setMoreCondition(2, SoulTransferCondition.instance); // • Exile target creature or planeswalker. this.getSpellAbility().addEffect(new ExileTargetEffect()); diff --git a/Mage.Sets/src/mage/cards/s/SzatsWill.java b/Mage.Sets/src/mage/cards/s/SzatsWill.java index 5e07b91a2a3..2bd46e1b517 100644 --- a/Mage.Sets/src/mage/cards/s/SzatsWill.java +++ b/Mage.Sets/src/mage/cards/s/SzatsWill.java @@ -45,7 +45,7 @@ public final class SzatsWill extends CardImpl { this.getSpellAbility().getModes().setChooseText( "Choose one. If you control a commander as you cast this spell, you may choose both." ); - this.getSpellAbility().getModes().setMoreCondition(ControlACommanderCondition.instance); + this.getSpellAbility().getModes().setMoreCondition(2, ControlACommanderCondition.instance); // • Each opponent sacrifices a creature they control with the greatest power. this.getSpellAbility().addEffect(new SacrificeOpponentsEffect(filter)); diff --git a/Mage.Sets/src/mage/cards/w/WailOfTheForgotten.java b/Mage.Sets/src/mage/cards/w/WailOfTheForgotten.java index 1725675130d..df296ccc797 100644 --- a/Mage.Sets/src/mage/cards/w/WailOfTheForgotten.java +++ b/Mage.Sets/src/mage/cards/w/WailOfTheForgotten.java @@ -27,7 +27,7 @@ public final class WailOfTheForgotten extends CardImpl { this.getSpellAbility().getModes().setChooseText( "choose one. If there are eight or more permanent cards in your graveyard as you cast this spell, choose one or more instead." ); - this.getSpellAbility().getModes().setMoreCondition(DescendCondition.EIGHT); + this.getSpellAbility().getModes().setMoreCondition(3, DescendCondition.EIGHT); this.getSpellAbility().setAbilityWord(AbilityWord.DESCEND_8); // * Return target nonland permanent to its owner's hand. diff --git a/Mage/src/main/java/mage/abilities/Modes.java b/Mage/src/main/java/mage/abilities/Modes.java index f4c1ffc56bf..fa84ea9e0db 100644 --- a/Mage/src/main/java/mage/abilities/Modes.java +++ b/Mage/src/main/java/mage/abilities/Modes.java @@ -38,7 +38,7 @@ public class Modes extends LinkedHashMap implements Copyable private int maxPawPrints; private Filter maxModesFilter; // calculates the max number of available modes private Condition moreCondition; // allows multiple modes choose (example: choose one... if condition, you may choose both) - private int moreLimit = Integer.MAX_VALUE; // if multiple modes are allowed, this limits how many additional modes may be chosen (usually doesn't need to change) + private int moreLimit; // if multiple modes are allowed, this limits how many additional modes may be chosen private boolean limitUsageByOnce = false; // limit mode selection to once per game private boolean limitUsageResetOnNewTurn = false; // reset once per game limit on new turn, example: Galadriel, Light of Valinor @@ -303,12 +303,9 @@ public class Modes extends LinkedHashMap implements Copyable this.put(mode.getId(), mode); } - public void setMoreCondition(Condition moreCondition) { - this.moreCondition = moreCondition; - } - - public void setMoreLimit(int moreLimit) { + public void setMoreCondition(int moreLimit, Condition moreCondition) { this.moreLimit = moreLimit; + this.moreCondition = moreCondition; } private boolean isAlreadySelectedModesOutdated(Game game, Ability source) { From 3c5e3fd928a714cecd5d601a871a984511492ed2 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 10:45:10 -0400 Subject: [PATCH 21/62] [DSK] Implement Saw --- Mage.Sets/src/mage/cards/s/Saw.java | 93 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 94 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/Saw.java diff --git a/Mage.Sets/src/mage/cards/s/Saw.java b/Mage.Sets/src/mage/cards/s/Saw.java new file mode 100644 index 00000000000..c24002b57d6 --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/Saw.java @@ -0,0 +1,93 @@ +package mage.cards.s; + +import mage.MageItem; +import mage.abilities.Ability; +import mage.abilities.common.AttacksAttachedTriggeredAbility; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AnotherPredicate; +import mage.filter.predicate.permanent.PermanentIdPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetSacrifice; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Saw extends CardImpl { + + public Saw(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + this.subtype.add(SubType.EQUIPMENT); + + // Equipped creature gets +2/+0. + this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(2, 0))); + + // Whenever equipped creature attacks, you may sacrifice a permanent other than that creature or Saw. If you do, draw a card. + this.addAbility(new AttacksAttachedTriggeredAbility( + new SawEffect(), AttachmentType.EQUIPMENT, false, SetTargetPointer.PERMANENT + )); + + // Equip {2} + this.addAbility(new EquipAbility(2)); + } + + private Saw(final Saw card) { + super(card); + } + + @Override + public Saw copy() { + return new Saw(this); + } +} + +class SawEffect extends OneShotEffect { + + SawEffect() { + super(Outcome.Benefit); + staticText = "you may sacrifice a permanent other than that creature or {this}. If you do, draw a card"; + } + + private SawEffect(final SawEffect effect) { + super(effect); + } + + @Override + public SawEffect copy() { + return new SawEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + FilterPermanent filter = new FilterPermanent("another permanent"); + filter.add(AnotherPredicate.instance); + Optional.ofNullable(getTargetPointer().getFirst(game, source)) + .map(game::getPermanent) + .map(MageItem::getId) + .map(PermanentIdPredicate::new) + .map(Predicates::not) + .ifPresent(filter::add); + TargetSacrifice target = new TargetSacrifice(0, 1, filter); + player.choose(outcome, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + return permanent != null && permanent.sacrifice(source, game) && player.drawCards(1, source, game) > 0; + } +} +// I see, said the blind man diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 0ce6954b5c0..3dabfb7552b 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -168,6 +168,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Rite of the Moth", 229, Rarity.UNCOMMON, mage.cards.r.RiteOfTheMoth.class)); cards.add(new SetCardInfo("Rootwise Survivor", 196, Rarity.UNCOMMON, mage.cards.r.RootwiseSurvivor.class)); cards.add(new SetCardInfo("Savior of the Small", 27, Rarity.UNCOMMON, mage.cards.s.SaviorOfTheSmall.class)); + cards.add(new SetCardInfo("Saw", 254, Rarity.UNCOMMON, mage.cards.s.Saw.class)); cards.add(new SetCardInfo("Sawblade Skinripper", 231, Rarity.UNCOMMON, mage.cards.s.SawbladeSkinripper.class)); cards.add(new SetCardInfo("Scorching Dragonfire", 156, Rarity.COMMON, mage.cards.s.ScorchingDragonfire.class)); cards.add(new SetCardInfo("Scrabbling Skullcrab", 71, Rarity.UNCOMMON, mage.cards.s.ScrabblingSkullcrab.class)); From 33473adf7772fe50228fc19f46141e9fe882570d Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 10:58:26 -0400 Subject: [PATCH 22/62] [DSK] Implement Coordinated Clobbering --- .../mage/cards/c/CoordinatedClobbering.java | 89 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 90 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/c/CoordinatedClobbering.java diff --git a/Mage.Sets/src/mage/cards/c/CoordinatedClobbering.java b/Mage.Sets/src/mage/cards/c/CoordinatedClobbering.java new file mode 100644 index 00000000000..01b66f18355 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CoordinatedClobbering.java @@ -0,0 +1,89 @@ +package mage.cards.c; + +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.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetOpponentsCreaturePermanent; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class CoordinatedClobbering extends CardImpl { + + public CoordinatedClobbering(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{G}"); + + // Tap one or two target untapped creatures you control. They each deal damage equal to their power to target creature an opponent controls. + this.getSpellAbility().addEffect(new CoordinatedClobberingEffect()); + this.getSpellAbility().addTarget(new TargetPermanent( + 1, 2, StaticFilters.FILTER_CONTROLLED_UNTAPPED_CREATURES + )); + this.getSpellAbility().addTarget(new TargetOpponentsCreaturePermanent()); + } + + private CoordinatedClobbering(final CoordinatedClobbering card) { + super(card); + } + + @Override + public CoordinatedClobbering copy() { + return new CoordinatedClobbering(this); + } +} + +class CoordinatedClobberingEffect extends OneShotEffect { + + CoordinatedClobberingEffect() { + super(Outcome.Benefit); + staticText = "tap one or two target untapped creatures you control. " + + "They each deal damage equal to their power to target creature an opponent controls"; + } + + private CoordinatedClobberingEffect(final CoordinatedClobberingEffect effect) { + super(effect); + } + + @Override + public CoordinatedClobberingEffect copy() { + return new CoordinatedClobberingEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + List permanents = this + .getTargetPointer() + .getTargets(game, source) + .stream() + .map(game::getPermanent) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (permanents.isEmpty()) { + return false; + } + for (Permanent permanent : permanents) { + permanent.tap(source, game); + } + Permanent targetPermanent = game.getPermanent(source.getTargets().get(1).getFirstTarget()); + if (targetPermanent == null) { + return true; + } + game.processAction(); + for (Permanent permanent : permanents) { + targetPermanent.damage(permanent.getPower().getValue(), permanent.getId(), source, game); + } + return true; + } +} +// flame on! diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 3dabfb7552b..f27c18e2e06 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -48,6 +48,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Clockwork Percussionist", 130, Rarity.COMMON, mage.cards.c.ClockworkPercussionist.class)); cards.add(new SetCardInfo("Commune with Evil", 87, Rarity.UNCOMMON, mage.cards.c.CommuneWithEvil.class)); cards.add(new SetCardInfo("Conductive Machete", 244, Rarity.UNCOMMON, mage.cards.c.ConductiveMachete.class)); + cards.add(new SetCardInfo("Coordinated Clobbering", 173, Rarity.UNCOMMON, mage.cards.c.CoordinatedClobbering.class)); cards.add(new SetCardInfo("Cult Healer", 2, Rarity.COMMON, mage.cards.c.CultHealer.class)); cards.add(new SetCardInfo("Cursed Recording", 131, Rarity.RARE, mage.cards.c.CursedRecording.class)); cards.add(new SetCardInfo("Cursed Windbreaker", 47, Rarity.UNCOMMON, mage.cards.c.CursedWindbreaker.class)); From 918f52ba4012f70b8d9cf50210708192acda9695 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 11:06:17 -0400 Subject: [PATCH 23/62] [DSK] Implement Sporogenic Infection --- .../src/mage/cards/s/SporogenicInfection.java | 80 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 81 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/s/SporogenicInfection.java diff --git a/Mage.Sets/src/mage/cards/s/SporogenicInfection.java b/Mage.Sets/src/mage/cards/s/SporogenicInfection.java new file mode 100644 index 00000000000..b1002ed7f2a --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/SporogenicInfection.java @@ -0,0 +1,80 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.common.DealtDamageAttachedTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.SacrificeEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.ObjectSourcePlayer; +import mage.filter.predicate.ObjectSourcePlayerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.target.TargetPermanent; +import mage.target.TargetPlayer; +import mage.target.common.TargetCreaturePermanent; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class SporogenicInfection extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("a creature other than enchanted creature"); + + static { + filter.add(SporogenicInfectionPredicate.instance); + } + + public SporogenicInfection(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{1}{B}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When Sporogenic Infection enters, target player sacrifices a creature other than enchanted creature. + Ability ability = new EntersBattlefieldTriggeredAbility(new SacrificeEffect(filter, 1, "target player")); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + + // When enchanted creature is dealt damage, destroy it. + this.addAbility(new DealtDamageAttachedTriggeredAbility( + Zone.BATTLEFIELD, new DestroyTargetEffect().setText("destroy it"), + false, SetTargetPointer.PERMANENT + ).setTriggerPhrase("When enchanted creature is dealt damage, ")); + } + + private SporogenicInfection(final SporogenicInfection card) { + super(card); + } + + @Override + public SporogenicInfection copy() { + return new SporogenicInfection(this); + } +} + +enum SporogenicInfectionPredicate implements ObjectSourcePlayerPredicate { + instance; + + @Override + public boolean apply(ObjectSourcePlayer input, Game game) { + return !Optional + .ofNullable(input.getSource().getSourcePermanentOrLKI(game)) + .map(Permanent::getAttachedTo) + .equals(input.getObject().getId()); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index f27c18e2e06..4b49b07291d 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -187,6 +187,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Spineseeker Centipede", 199, Rarity.COMMON, mage.cards.s.SpineseekerCentipede.class)); cards.add(new SetCardInfo("Split Up", 32, Rarity.RARE, mage.cards.s.SplitUp.class)); cards.add(new SetCardInfo("Splitskin Doll", 33, Rarity.UNCOMMON, mage.cards.s.SplitskinDoll.class)); + cards.add(new SetCardInfo("Sporogenic Infection", 117, Rarity.UNCOMMON, mage.cards.s.SporogenicInfection.class)); cards.add(new SetCardInfo("Stalked Researcher", 73, Rarity.COMMON, mage.cards.s.StalkedResearcher.class)); cards.add(new SetCardInfo("Stay Hidden, Stay Silent", 74, Rarity.UNCOMMON, mage.cards.s.StayHiddenStaySilent.class)); cards.add(new SetCardInfo("Strangled Cemetery", 268, Rarity.COMMON, mage.cards.s.StrangledCemetery.class)); From 9f3d19bde29d93e8ef376652dd91a11b65bd0651 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 11:14:53 -0400 Subject: [PATCH 24/62] [DSK] Implement Waltz of Rage --- Mage.Sets/src/mage/cards/w/WaltzOfRage.java | 119 ++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 120 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/w/WaltzOfRage.java diff --git a/Mage.Sets/src/mage/cards/w/WaltzOfRage.java b/Mage.Sets/src/mage/cards/w/WaltzOfRage.java new file mode 100644 index 00000000000..11c66830da9 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WaltzOfRage.java @@ -0,0 +1,119 @@ +package mage.cards.w; + +import mage.abilities.Ability; +import mage.abilities.DelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateDelayedTriggeredAbilityEffect; +import mage.abilities.effects.common.ExileTopXMayPlayUntilEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.permanent.Permanent; +import mage.target.common.TargetControlledCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class WaltzOfRage extends CardImpl { + + public WaltzOfRage(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{3}{R}{R}"); + + // Target creature you control deals damage equal to its power to each other creature. Until end of turn, whenever a creature you control dies, exile the top card of your library. You may play it until the end of your next turn. + this.getSpellAbility().addEffect(new WaltzOfRageEffect()); + this.getSpellAbility().addTarget(new TargetControlledCreaturePermanent()); + this.getSpellAbility().addEffect(new CreateDelayedTriggeredAbilityEffect(new WaltzOfRageTriggeredAbility())); + } + + private WaltzOfRage(final WaltzOfRage card) { + super(card); + } + + @Override + public WaltzOfRage copy() { + return new WaltzOfRage(this); + } +} + +class WaltzOfRageEffect extends OneShotEffect { + + WaltzOfRageEffect() { + super(Outcome.Benefit); + staticText = "target creature you control deals damage equal to its power to each other creature"; + } + + private WaltzOfRageEffect(final WaltzOfRageEffect effect) { + super(effect); + } + + @Override + public WaltzOfRageEffect copy() { + return new WaltzOfRageEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + int power = permanent.getPower().getValue(); + if (power < 1) { + return false; + } + for (Permanent creature : game.getBattlefield().getActivePermanents( + StaticFilters.FILTER_PERMANENT_CREATURE, + source.getControllerId(), source, game + )) { + if (!creature.getId().equals(permanent.getId())) { + creature.damage(power, permanent.getId(), source, game); + } + } + return true; + } +} + +class WaltzOfRageTriggeredAbility extends DelayedTriggeredAbility { + + WaltzOfRageTriggeredAbility() { + super(new ExileTopXMayPlayUntilEffect(1, Duration.UntilEndOfYourNextTurn), Duration.EndOfTurn, false, false); + } + + private WaltzOfRageTriggeredAbility(final WaltzOfRageTriggeredAbility ability) { + super(ability); + } + + @Override + public WaltzOfRageTriggeredAbility copy() { + return new WaltzOfRageTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.ZONE_CHANGE; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + ZoneChangeEvent zEvent = (ZoneChangeEvent) event; + if (!zEvent.isDiesEvent()) { + return false; + } + Permanent permanent = zEvent.getTarget(); + return permanent != null && permanent.isCreature(game) && permanent.isControlledBy(getControllerId()); + } + + @Override + public String getRule() { + return "Until end of turn, whenever a creature you control dies, " + + "exile the top card of your library. You may play it until the end of your next turn."; + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 4b49b07291d..4e7c70337ee 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -222,6 +222,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Victor, Valgavoth's Seneschal", 238, Rarity.RARE, mage.cards.v.VictorValgavothsSeneschal.class)); cards.add(new SetCardInfo("Vile Mutilator", 122, Rarity.UNCOMMON, mage.cards.v.VileMutilator.class)); cards.add(new SetCardInfo("Violent Urge", 164, Rarity.UNCOMMON, mage.cards.v.ViolentUrge.class)); + cards.add(new SetCardInfo("Waltz of Rage", 165, Rarity.RARE, mage.cards.w.WaltzOfRage.class)); cards.add(new SetCardInfo("Wary Watchdog", 206, Rarity.COMMON, mage.cards.w.WaryWatchdog.class)); cards.add(new SetCardInfo("Wickerfolk Thresher", 207, Rarity.UNCOMMON, mage.cards.w.WickerfolkThresher.class)); cards.add(new SetCardInfo("Wildfire Wickerfolk", 239, Rarity.UNCOMMON, mage.cards.w.WildfireWickerfolk.class)); From 3c4266ae6e5a2999123c6e643423d1af9b975486 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 11:26:01 -0400 Subject: [PATCH 25/62] [DSK] Implement Don't Make a Sound --- .../src/mage/cards/d/DontMakeASound.java | 78 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 79 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/d/DontMakeASound.java diff --git a/Mage.Sets/src/mage/cards/d/DontMakeASound.java b/Mage.Sets/src/mage/cards/d/DontMakeASound.java new file mode 100644 index 00000000000..4a40f281698 --- /dev/null +++ b/Mage.Sets/src/mage/cards/d/DontMakeASound.java @@ -0,0 +1,78 @@ +package mage.cards.d; + +import mage.abilities.Ability; +import mage.abilities.costs.Cost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.OneShotEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.game.Game; +import mage.game.stack.Spell; +import mage.players.Player; +import mage.target.TargetSpell; + +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class DontMakeASound extends CardImpl { + + public DontMakeASound(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{1}{U}"); + + // Counter target spell unless its controller pays {2}. If they do, surveil 2. + this.getSpellAbility().addEffect(new DontMakeASoundEffect()); + this.getSpellAbility().addTarget(new TargetSpell()); + } + + private DontMakeASound(final DontMakeASound card) { + super(card); + } + + @Override + public DontMakeASound copy() { + return new DontMakeASound(this); + } +} + +class DontMakeASoundEffect extends OneShotEffect { + + DontMakeASoundEffect() { + super(Outcome.Benefit); + staticText = "counter target spell unless its controller pays {2}. If they do, surveil 2"; + } + + private DontMakeASoundEffect(final DontMakeASoundEffect effect) { + super(effect); + } + + @Override + public DontMakeASoundEffect copy() { + return new DontMakeASoundEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Spell spell = game.getSpell(getTargetPointer().getFirst(game, source)); + if (spell == null) { + return false; + } + Player player = game.getPlayer(spell.getControllerId()); + if (player == null) { + return game.getStack().counter(spell.getId(), source, game); + } + Cost cost = new GenericManaCost(2); + if (!cost.canPay(source, source, player.getId(), game) + || !player.chooseUse(outcome, "Pay {2}?", source, game) + || !cost.pay(source, game, source, player.getId(), false)) { + return game.getStack().counter(spell.getId(), source, game); + } + Optional.ofNullable(game.getPlayer(source.getControllerId())) + .ifPresent(p -> p.surveil(2, source, game)); + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 4e7c70337ee..ef483f9e0d3 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -60,6 +60,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Dissection Tools", 245, Rarity.RARE, mage.cards.d.DissectionTools.class)); cards.add(new SetCardInfo("Disturbing Mirth", 212, Rarity.UNCOMMON, mage.cards.d.DisturbingMirth.class)); cards.add(new SetCardInfo("Diversion Specialist", 132, Rarity.UNCOMMON, mage.cards.d.DiversionSpecialist.class)); + cards.add(new SetCardInfo("Don't Make a Sound", 49, Rarity.COMMON, mage.cards.d.DontMakeASound.class)); cards.add(new SetCardInfo("Doomsday Excruciator", 94, Rarity.RARE, mage.cards.d.DoomsdayExcruciator.class)); cards.add(new SetCardInfo("Drag to the Roots", 213, Rarity.UNCOMMON, mage.cards.d.DragToTheRoots.class)); cards.add(new SetCardInfo("Duskmourn's Domination", 50, Rarity.UNCOMMON, mage.cards.d.DuskmournsDomination.class)); From 69c08002a580b07b64935bb5aabe31db6f9c8dce Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 11:41:57 -0400 Subject: [PATCH 26/62] [DSK] Implement Norin, Swift Survivalist --- .../mage/cards/n/NorinSwiftSurvivalist.java | 87 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 88 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/n/NorinSwiftSurvivalist.java diff --git a/Mage.Sets/src/mage/cards/n/NorinSwiftSurvivalist.java b/Mage.Sets/src/mage/cards/n/NorinSwiftSurvivalist.java new file mode 100644 index 00000000000..bf61b2ada3b --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NorinSwiftSurvivalist.java @@ -0,0 +1,87 @@ +package mage.cards.n; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.BecomesBlockedAllTriggeredAbility; +import mage.abilities.common.CantBlockAbility; +import mage.abilities.effects.OneShotEffect; +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.game.permanent.Permanent; +import mage.players.Player; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class NorinSwiftSurvivalist extends CardImpl { + + public NorinSwiftSurvivalist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{R}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.COWARD); + this.power = new MageInt(2); + this.toughness = new MageInt(1); + + // Norin, Swift Survivalist can't block. + this.addAbility(new CantBlockAbility()); + + // Whenever a creature you control becomes blocked, you may exile it. You may play that card from exile this turn. + this.addAbility(new BecomesBlockedAllTriggeredAbility( + new NorinSwiftSurvivalistEffect(), true, + StaticFilters.FILTER_CONTROLLED_A_CREATURE, true + )); + } + + private NorinSwiftSurvivalist(final NorinSwiftSurvivalist card) { + super(card); + } + + @Override + public NorinSwiftSurvivalist copy() { + return new NorinSwiftSurvivalist(this); + } +} + +class NorinSwiftSurvivalistEffect extends OneShotEffect { + + NorinSwiftSurvivalistEffect() { + super(Outcome.Benefit); + staticText = "exile it. You may play that card from exile this turn"; + } + + private NorinSwiftSurvivalistEffect(final NorinSwiftSurvivalistEffect effect) { + super(effect); + } + + @Override + public NorinSwiftSurvivalistEffect copy() { + return new NorinSwiftSurvivalistEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (player == null || permanent == null) { + return false; + } + Card card = permanent.getMainCard(); + player.moveCards(permanent, Zone.EXILED, source, game); + if (card != null) { + CardUtil.makeCardPlayable( + game, source, card, false, + Duration.EndOfTurn, false, player.getId(), null + ); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index ef483f9e0d3..56cadc0b09b 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -142,6 +142,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Murky Sewer", 263, Rarity.COMMON, mage.cards.m.MurkySewer.class)); cards.add(new SetCardInfo("Nashi, Searcher in the Dark", 223, Rarity.RARE, mage.cards.n.NashiSearcherInTheDark.class)); cards.add(new SetCardInfo("Neglected Manor", 264, Rarity.COMMON, mage.cards.n.NeglectedManor.class)); + cards.add(new SetCardInfo("Norin, Swift Survivalist", 145, Rarity.UNCOMMON, mage.cards.n.NorinSwiftSurvivalist.class)); cards.add(new SetCardInfo("Oblivious Bookworm", 225, Rarity.UNCOMMON, mage.cards.o.ObliviousBookworm.class)); cards.add(new SetCardInfo("Optimistic Scavenger", 21, Rarity.UNCOMMON, mage.cards.o.OptimisticScavenger.class)); cards.add(new SetCardInfo("Orphans of the Wheat", 22, Rarity.UNCOMMON, mage.cards.o.OrphansOfTheWheat.class)); From 5e288deea638337aa404f7f09a9ef97bf16387b0 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 11:53:12 -0400 Subject: [PATCH 27/62] [DSK] Implement Rip, Spawn Hunter --- .../src/mage/cards/r/RipSpawnHunter.java | 135 ++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 136 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/r/RipSpawnHunter.java diff --git a/Mage.Sets/src/mage/cards/r/RipSpawnHunter.java b/Mage.Sets/src/mage/cards/r/RipSpawnHunter.java new file mode 100644 index 00000000000..c95f9c8e505 --- /dev/null +++ b/Mage.Sets/src/mage/cards/r/RipSpawnHunter.java @@ -0,0 +1,135 @@ +package mage.cards.r; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.abilityword.SurvivalAbility; +import mage.abilities.effects.OneShotEffect; +import mage.cards.*; +import mage.constants.*; +import mage.filter.FilterCard; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInLibrary; + +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class RipSpawnHunter extends CardImpl { + + public RipSpawnHunter(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{W}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.SURVIVOR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Survival -- At the beginning of your second main phase, if Rip, Spawn Hunter is tapped, reveal the top X cards of your library, where X is its power. Put any number of creature and/or Vehicle cards with different powers from among them into your hand. Put the rest on the bottom of your library in a random order. + this.addAbility(new SurvivalAbility(new RipSpawnHunterEffect())); + } + + private RipSpawnHunter(final RipSpawnHunter card) { + super(card); + } + + @Override + public RipSpawnHunter copy() { + return new RipSpawnHunter(this); + } +} + +class RipSpawnHunterEffect extends OneShotEffect { + + RipSpawnHunterEffect() { + super(Outcome.Benefit); + staticText = "reveal the top X cards of your library, where X is its power. " + + "Put any number of creature and/or Vehicle cards with different powers from among them into your hand. " + + "Put the rest on the bottom of your library in a random order"; + } + + private RipSpawnHunterEffect(final RipSpawnHunterEffect effect) { + super(effect); + } + + @Override + public RipSpawnHunterEffect copy() { + return new RipSpawnHunterEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + int power = Optional + .ofNullable(source.getSourcePermanentOrLKI(game)) + .map(MageObject::getPower) + .map(MageInt::getValue) + .orElse(0); + if (player == null || power < 1) { + return false; + } + Cards cards = new CardsImpl(player.getLibrary().getTopCards(game, power)); + if (cards.isEmpty()) { + return false; + } + player.revealCards(source, cards, game); + TargetCard target = new RipSpawnHunterTarget(); + player.choose(Outcome.ReturnToHand, cards, target, source, game); + player.moveCards(new CardsImpl(target.getTargets()), Zone.HAND, source, game); + cards.retainZone(Zone.LIBRARY, game); + player.putCardsOnBottomOfLibrary(cards, game, source, false); + return true; + } +} + +class RipSpawnHunterTarget extends TargetCardInLibrary { + + private static final FilterCard filter = new FilterCard("creature and/or Vehicle cards with different powers"); + + static { + filter.add(Predicates.or( + CardType.CREATURE.getPredicate(), + SubType.VEHICLE.getPredicate() + )); + } + + public RipSpawnHunterTarget() { + super(0, Integer.MAX_VALUE, filter); + } + + private RipSpawnHunterTarget(final RipSpawnHunterTarget target) { + super(target); + } + + @Override + public RipSpawnHunterTarget copy() { + return new RipSpawnHunterTarget(this); + } + + @Override + public boolean canTarget(UUID playerId, UUID id, Ability source, Game game) { + if (!super.canTarget(playerId, id, source, game)) { + return false; + } + Card card = game.getCard(id); + if (card == null) { + return false; + } + int power = card.getPower().getValue(); + return this + .getTargets() + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .map(MageObject::getPower) + .mapToInt(MageInt::getValue) + .noneMatch(x -> x == power); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 56cadc0b09b..fcadca863fa 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -167,6 +167,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Razorkin Needlehead", 153, Rarity.RARE, mage.cards.r.RazorkinNeedlehead.class)); cards.add(new SetCardInfo("Razortrap Gorge", 267, Rarity.COMMON, mage.cards.r.RazortrapGorge.class)); cards.add(new SetCardInfo("Resurrected Cultist", 115, Rarity.COMMON, mage.cards.r.ResurrectedCultist.class)); + cards.add(new SetCardInfo("Rip, Spawn Hunter", 228, Rarity.RARE, mage.cards.r.RipSpawnHunter.class)); cards.add(new SetCardInfo("Ripchain Razorkin", 154, Rarity.COMMON, mage.cards.r.RipchainRazorkin.class)); cards.add(new SetCardInfo("Rite of the Moth", 229, Rarity.UNCOMMON, mage.cards.r.RiteOfTheMoth.class)); cards.add(new SetCardInfo("Rootwise Survivor", 196, Rarity.UNCOMMON, mage.cards.r.RootwiseSurvivor.class)); From 719391a0714d3cf61d7ed1e8b4134b77e1a3a461 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 11:58:20 -0400 Subject: [PATCH 28/62] [DSK] Implement Jump Scare --- Mage.Sets/src/mage/cards/j/JumpScare.java | 47 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 48 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/j/JumpScare.java diff --git a/Mage.Sets/src/mage/cards/j/JumpScare.java b/Mage.Sets/src/mage/cards/j/JumpScare.java new file mode 100644 index 00000000000..7e0690f69ee --- /dev/null +++ b/Mage.Sets/src/mage/cards/j/JumpScare.java @@ -0,0 +1,47 @@ +package mage.cards.j; + +import mage.abilities.effects.common.continuous.AddCardSubTypeTargetEffect; +import mage.abilities.effects.common.continuous.AddCardTypeTargetEffect; +import mage.abilities.effects.common.continuous.BoostTargetEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +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.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class JumpScare extends CardImpl { + + public JumpScare(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{W}"); + + // Until end of turn, target creature gets +2/+2, gains flying, and becomes a Horror enchantment creature in addition to its other types. + this.getSpellAbility().addEffect(new BoostTargetEffect(2, 2) + .setText("until end of turn, target creature gets +2/+2")); + this.getSpellAbility().addEffect(new GainAbilityTargetEffect(FlyingAbility.getInstance()) + .setText(", gains flying")); + this.getSpellAbility().addEffect(new AddCardSubTypeTargetEffect( + SubType.HORROR, Duration.EndOfTurn + ).setText(", and becomes ")); + this.getSpellAbility().addEffect(new AddCardTypeTargetEffect( + Duration.EndOfTurn, CardType.ENCHANTMENT, CardType.CREATURE + ).setText("a Horror enchantment creature in addition to its other types")); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private JumpScare(final JumpScare card) { + super(card); + } + + @Override + public JumpScare copy() { + return new JumpScare(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index fcadca863fa..3769d8109a6 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -120,6 +120,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Insidious Fungus", 186, Rarity.UNCOMMON, mage.cards.i.InsidiousFungus.class)); cards.add(new SetCardInfo("Intruding Soulrager", 218, Rarity.UNCOMMON, mage.cards.i.IntrudingSoulrager.class)); cards.add(new SetCardInfo("Island", 273, Rarity.LAND, mage.cards.basiclands.Island.class, FULL_ART_BFZ_VARIOUS)); + cards.add(new SetCardInfo("Jump Scare", 17, Rarity.COMMON, mage.cards.j.JumpScare.class)); cards.add(new SetCardInfo("Killer's Mask", 104, Rarity.UNCOMMON, mage.cards.k.KillersMask.class)); cards.add(new SetCardInfo("Kona, Rescue Beastie", 187, Rarity.RARE, mage.cards.k.KonaRescueBeastie.class)); cards.add(new SetCardInfo("Lakeside Shack", 262, Rarity.COMMON, mage.cards.l.LakesideShack.class)); From 9f1797a76b459c7627dae093d1c31dedf671c32d Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 13:06:22 -0400 Subject: [PATCH 29/62] fix verify failure --- Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index 7202d3d882d..eeee3d91ce1 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -142,14 +142,12 @@ public class VerifyCardDataTest { skipListAddName(SKIP_LIST_TYPE, "UNH", "Old Fogey"); // uses summon word as a joke card skipListAddName(SKIP_LIST_TYPE, "UND", "Old Fogey"); skipListAddName(SKIP_LIST_TYPE, "UST", "capital offense"); // uses "instant" instead "Instant" as a joke card - skipListAddName(SKIP_LIST_TYPE, "DSK", "Balemurk Leech"); // temporary // subtype // skipListAddName(SKIP_LIST_SUBTYPE, set, cardName); skipListAddName(SKIP_LIST_SUBTYPE, "UGL", "Miss Demeanor"); // uses multiple types as a joke card: Lady, of, Proper, Etiquette skipListAddName(SKIP_LIST_SUBTYPE, "UGL", "Elvish Impersonators"); // subtype is "Elves" pun skipListAddName(SKIP_LIST_SUBTYPE, "UND", "Elvish Impersonators"); - skipListAddName(SKIP_LIST_SUBTYPE, "DSK", "Balemurk Leech"); // temporary // number // skipListAddName(SKIP_LIST_NUMBER, set, cardName); @@ -157,6 +155,7 @@ public class VerifyCardDataTest { // rarity // skipListAddName(SKIP_LIST_RARITY, set, cardName); skipListAddName(SKIP_LIST_RARITY, "CMR", "The Prismatic Piper"); // Collation is not yet set up for CMR https://www.lethe.xyz/mtg/collation/cmr.html + skipListAddName(SKIP_LIST_RARITY, "DSC", "Suspicious Bookcase"); // temporary // missing abilities // skipListAddName(SKIP_LIST_MISSING_ABILITIES, set, cardName); From 493ef6445423e68ecab1a1ab32b4b39b31ef2fcc Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 15:48:32 -0400 Subject: [PATCH 30/62] [DSK] Implement Haunted Screen --- Mage.Sets/src/mage/cards/h/HauntedScreen.java | 61 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + .../ActivateOncePerGameActivatedAbility.java | 5 +- 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/h/HauntedScreen.java diff --git a/Mage.Sets/src/mage/cards/h/HauntedScreen.java b/Mage.Sets/src/mage/cards/h/HauntedScreen.java new file mode 100644 index 00000000000..0e66a65662c --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HauntedScreen.java @@ -0,0 +1,61 @@ +package mage.cards.h; + +import mage.abilities.Ability; +import mage.abilities.common.ActivateOncePerGameActivatedAbility; +import mage.abilities.costs.common.PayLifeCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.common.continuous.BecomesCreatureSourceEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.mana.*; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.SubType; +import mage.counters.CounterType; +import mage.game.permanent.token.custom.CreatureToken; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class HauntedScreen extends CardImpl { + + public HauntedScreen(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{3}"); + + // {T}: Add {W} or {B}. + this.addAbility(new WhiteManaAbility()); + this.addAbility(new BlackManaAbility()); + + // {T}, Pay 1 life: Add {G}, {U}, or {R}. + Ability ability = new GreenManaAbility(); + ability.addCost(new PayLifeCost(1)); + this.addAbility(ability); + ability = new BlueManaAbility(); + ability.addCost(new PayLifeCost(1)); + this.addAbility(ability); + ability = new RedManaAbility(); + ability.addCost(new PayLifeCost(1)); + this.addAbility(ability); + + // {7}: Put seven +1/+1 counters on Haunted Screen. It becomes a 0/0 Spirit creature in addition to its other types. Activate only once. + ability = new ActivateOncePerGameActivatedAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(7)), new GenericManaCost(7) + ); + ability.addEffect(new BecomesCreatureSourceEffect(new CreatureToken( + 0, 0, "0/0 Spirit creature", SubType.SPIRIT + ), CardType.ARTIFACT, Duration.Custom)); + this.addAbility(ability); + } + + private HauntedScreen(final HauntedScreen card) { + super(card); + } + + @Override + public HauntedScreen copy() { + return new HauntedScreen(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 3769d8109a6..7ee149a7ecc 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -110,6 +110,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Growing Dread", 216, Rarity.UNCOMMON, mage.cards.g.GrowingDread.class)); cards.add(new SetCardInfo("Hand That Feeds", 139, Rarity.COMMON, mage.cards.h.HandThatFeeds.class)); cards.add(new SetCardInfo("Hardened Escort", 16, Rarity.COMMON, mage.cards.h.HardenedEscort.class)); + cards.add(new SetCardInfo("Haunted Screen", 250, Rarity.UNCOMMON, mage.cards.h.HauntedScreen.class)); cards.add(new SetCardInfo("Hedge Shredder", 183, Rarity.RARE, mage.cards.h.HedgeShredder.class)); cards.add(new SetCardInfo("Horrid Vigor", 184, Rarity.COMMON, mage.cards.h.HorridVigor.class)); cards.add(new SetCardInfo("House Cartographer", 185, Rarity.UNCOMMON, mage.cards.h.HouseCartographer.class)); diff --git a/Mage/src/main/java/mage/abilities/common/ActivateOncePerGameActivatedAbility.java b/Mage/src/main/java/mage/abilities/common/ActivateOncePerGameActivatedAbility.java index a1c9992a76a..0391eefea0a 100644 --- a/Mage/src/main/java/mage/abilities/common/ActivateOncePerGameActivatedAbility.java +++ b/Mage/src/main/java/mage/abilities/common/ActivateOncePerGameActivatedAbility.java @@ -7,11 +7,14 @@ import mage.constants.TimingRule; import mage.constants.Zone; /** - * * @author weirddan455 */ public class ActivateOncePerGameActivatedAbility extends ActivatedAbilityImpl { + public ActivateOncePerGameActivatedAbility(Effect effect, Cost cost) { + this(Zone.BATTLEFIELD, effect, cost, TimingRule.INSTANT); + } + public ActivateOncePerGameActivatedAbility(Zone zone, Effect effect, Cost cost, TimingRule timingRule) { super(zone, effect, cost); this.timing = timingRule; From 49866bc59cc313836aacbfb3014beceb67386f32 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 15:57:52 -0400 Subject: [PATCH 31/62] [DSK] Implement Possessed Goat --- Mage.Sets/src/mage/cards/p/PossessedGoat.java | 98 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 99 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/p/PossessedGoat.java diff --git a/Mage.Sets/src/mage/cards/p/PossessedGoat.java b/Mage.Sets/src/mage/cards/p/PossessedGoat.java new file mode 100644 index 00000000000..41dc118d446 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PossessedGoat.java @@ -0,0 +1,98 @@ +package mage.cards.p; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateOncePerGameActivatedAbility; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.costs.mana.GenericManaCost; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.counters.CounterType; +import mage.game.Game; +import mage.game.permanent.Permanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class PossessedGoat extends CardImpl { + + public PossessedGoat(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{W}"); + + this.subtype.add(SubType.GOAT); + this.power = new MageInt(1); + this.toughness = new MageInt(1); + + // {3}, Discard a card: Put three +1/+1 counters on Possessed Goat and it becomes a black Demon in addition to its other colors and types. Activate only once. + Ability ability = new ActivateOncePerGameActivatedAbility( + new AddCountersSourceEffect(CounterType.P1P1.createInstance(3)), new GenericManaCost(3) + ); + ability.addCost(new DiscardCardCost()); + ability.addEffect(new PossessedGoatEffect()); + this.addAbility(ability); + } + + private PossessedGoat(final PossessedGoat card) { + super(card); + } + + @Override + public PossessedGoat copy() { + return new PossessedGoat(this); + } +} + +class PossessedGoatEffect extends ContinuousEffectImpl { + + PossessedGoatEffect() { + super(Duration.Custom, Outcome.Benefit); + staticText = "and it becomes a black Demon in addition to its other colors and types"; + } + + private PossessedGoatEffect(final PossessedGoatEffect effect) { + super(effect); + } + + @Override + public PossessedGoatEffect copy() { + return new PossessedGoatEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + return false; + } + + @Override + public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + discard(); + return false; + } + switch (layer) { + case TypeChangingEffects_4: + permanent.addSubType(game, SubType.DEMON); + return true; + case ColorChangingEffects_5: + permanent.getColor(game).setBlack(true); + return true; + } + return false; + } + + @Override + public boolean hasLayer(Layer layer) { + switch (layer) { + case TypeChangingEffects_4: + case ColorChangingEffects_5: + return true; + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 7ee149a7ecc..7b3c636aa80 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -162,6 +162,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Piranha Fly", 70, Rarity.COMMON, mage.cards.p.PiranhaFly.class)); cards.add(new SetCardInfo("Plains", 272, Rarity.LAND, mage.cards.basiclands.Plains.class, FULL_ART_BFZ_VARIOUS)); cards.add(new SetCardInfo("Popular Egotist", 114, Rarity.UNCOMMON, mage.cards.p.PopularEgotist.class)); + cards.add(new SetCardInfo("Possessed Goat", 25, Rarity.COMMON, mage.cards.p.PossessedGoat.class)); cards.add(new SetCardInfo("Pyroclasm", 149, Rarity.UNCOMMON, mage.cards.p.Pyroclasm.class)); cards.add(new SetCardInfo("Ragged Playmate", 150, Rarity.COMMON, mage.cards.r.RaggedPlaymate.class)); cards.add(new SetCardInfo("Raucous Carnival", 266, Rarity.COMMON, mage.cards.r.RaucousCarnival.class)); From a1729c6a0de76c8b7e496fef9b152884c8b48080 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 16:11:56 -0400 Subject: [PATCH 32/62] [DSK] Implement Come Back Wrong --- Mage.Sets/src/mage/cards/c/ComeBackWrong.java | 87 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 88 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/c/ComeBackWrong.java diff --git a/Mage.Sets/src/mage/cards/c/ComeBackWrong.java b/Mage.Sets/src/mage/cards/c/ComeBackWrong.java new file mode 100644 index 00000000000..cd7d5569031 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/ComeBackWrong.java @@ -0,0 +1,87 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.delayed.AtTheBeginOfPlayersNextEndStepDelayedTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.SacrificeTargetEffect; +import mage.cards.Card; +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.TargetCreaturePermanent; +import mage.target.targetpointer.FixedTarget; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class ComeBackWrong extends CardImpl { + + public ComeBackWrong(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{2}{B}"); + + // Destroy target creature. If a creature card is put into a graveyard this way, return it to the battlefield under your control. Sacrifice it at the beginning of your next end step. + this.getSpellAbility().addEffect(new ComeBackWrongEffect()); + this.getSpellAbility().addTarget(new TargetCreaturePermanent()); + } + + private ComeBackWrong(final ComeBackWrong card) { + super(card); + } + + @Override + public ComeBackWrong copy() { + return new ComeBackWrong(this); + } +} + +class ComeBackWrongEffect extends OneShotEffect { + + ComeBackWrongEffect() { + super(Outcome.Benefit); + staticText = "destroy target creature. If a creature card is put into a graveyard this way, " + + "return it to the battlefield under your control. Sacrifice it at the beginning of your next end step"; + } + + private ComeBackWrongEffect(final ComeBackWrongEffect effect) { + super(effect); + } + + @Override + public ComeBackWrongEffect copy() { + return new ComeBackWrongEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + if (permanent == null) { + return false; + } + permanent.destroy(source, game); + Card card = permanent.getMainCard(); + if (card == null || !card.isCreature(game) || !Zone.GRAVEYARD.match(game.getState().getZone(card.getId()))) { + return true; + } + Player player = game.getPlayer(source.getControllerId()); + if (player == null) { + return false; + } + player.moveCards(card, Zone.BATTLEFIELD, source, game); + Permanent creature = game.getPermanent(card.getId()); + if (permanent != null) { + game.addDelayedTriggeredAbility(new AtTheBeginOfPlayersNextEndStepDelayedTriggeredAbility( + new SacrificeTargetEffect("sacrifice it") + .setTargetPointer(new FixedTarget(creature, game)), + player.getId() + ).setTriggerPhrase("At the beginning of your next end step, "), source); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 7b3c636aa80..c4261d7d46c 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -46,6 +46,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Chainsaw", 128, Rarity.RARE, mage.cards.c.Chainsaw.class)); cards.add(new SetCardInfo("Clammy Prowler", 45, Rarity.COMMON, mage.cards.c.ClammyProwler.class)); cards.add(new SetCardInfo("Clockwork Percussionist", 130, Rarity.COMMON, mage.cards.c.ClockworkPercussionist.class)); + cards.add(new SetCardInfo("Come Back Wrong", 86, Rarity.RARE, mage.cards.c.ComeBackWrong.class)); cards.add(new SetCardInfo("Commune with Evil", 87, Rarity.UNCOMMON, mage.cards.c.CommuneWithEvil.class)); cards.add(new SetCardInfo("Conductive Machete", 244, Rarity.UNCOMMON, mage.cards.c.ConductiveMachete.class)); cards.add(new SetCardInfo("Coordinated Clobbering", 173, Rarity.UNCOMMON, mage.cards.c.CoordinatedClobbering.class)); From dbcac49b4a38f99db9d236e30c35f5e248631a6e Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 16:16:09 -0400 Subject: [PATCH 33/62] [DSK] Implement Cracked Skull --- Mage.Sets/src/mage/cards/c/CrackedSkull.java | 58 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 59 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/c/CrackedSkull.java diff --git a/Mage.Sets/src/mage/cards/c/CrackedSkull.java b/Mage.Sets/src/mage/cards/c/CrackedSkull.java new file mode 100644 index 00000000000..32eafc76f61 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CrackedSkull.java @@ -0,0 +1,58 @@ +package mage.cards.c; + +import mage.abilities.Ability; +import mage.abilities.common.DealtDamageAttachedTriggeredAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.effects.common.AttachEffect; +import mage.abilities.effects.common.DestroyTargetEffect; +import mage.abilities.effects.common.discard.DiscardCardYouChooseTargetEffect; +import mage.abilities.keyword.EnchantAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.target.TargetPermanent; +import mage.target.TargetPlayer; +import mage.target.common.TargetCreaturePermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CrackedSkull extends CardImpl { + + public CrackedSkull(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT}, "{2}{B}"); + + this.subtype.add(SubType.AURA); + + // Enchant creature + TargetPermanent auraTarget = new TargetCreaturePermanent(); + this.getSpellAbility().addTarget(auraTarget); + this.getSpellAbility().addEffect(new AttachEffect(Outcome.BoostCreature)); + this.addAbility(new EnchantAbility(auraTarget)); + + // When Cracked Skull enters, look at target player's hand. You may choose a nonland card from it. That player discards that card. + Ability ability = new EntersBattlefieldTriggeredAbility( + new DiscardCardYouChooseTargetEffect(StaticFilters.FILTER_CARD_NON_LAND).setOptional(true) + ); + ability.addTarget(new TargetPlayer()); + this.addAbility(ability); + + // When enchanted creature is dealt damage, destroy it. + this.addAbility(new DealtDamageAttachedTriggeredAbility( + Zone.BATTLEFIELD, new DestroyTargetEffect("destroy it"), + false, SetTargetPointer.PERMANENT + )); + } + + private CrackedSkull(final CrackedSkull card) { + super(card); + } + + @Override + public CrackedSkull copy() { + return new CrackedSkull(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index c4261d7d46c..9fba5211edd 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -50,6 +50,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Commune with Evil", 87, Rarity.UNCOMMON, mage.cards.c.CommuneWithEvil.class)); cards.add(new SetCardInfo("Conductive Machete", 244, Rarity.UNCOMMON, mage.cards.c.ConductiveMachete.class)); cards.add(new SetCardInfo("Coordinated Clobbering", 173, Rarity.UNCOMMON, mage.cards.c.CoordinatedClobbering.class)); + cards.add(new SetCardInfo("Cracked Skull", 88, Rarity.COMMON, mage.cards.c.CrackedSkull.class)); cards.add(new SetCardInfo("Cult Healer", 2, Rarity.COMMON, mage.cards.c.CultHealer.class)); cards.add(new SetCardInfo("Cursed Recording", 131, Rarity.RARE, mage.cards.c.CursedRecording.class)); cards.add(new SetCardInfo("Cursed Windbreaker", 47, Rarity.UNCOMMON, mage.cards.c.CursedWindbreaker.class)); From d2dc70555ff9a1d190e528a976e2cd9043541b08 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sun, 15 Sep 2024 00:33:56 +0400 Subject: [PATCH 34/62] Cursed Recording - fixed that trigger is optional; --- Mage.Sets/src/mage/cards/c/CursedRecording.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/c/CursedRecording.java b/Mage.Sets/src/mage/cards/c/CursedRecording.java index 88940a3b06c..aced16673f4 100644 --- a/Mage.Sets/src/mage/cards/c/CursedRecording.java +++ b/Mage.Sets/src/mage/cards/c/CursedRecording.java @@ -33,7 +33,7 @@ public final class CursedRecording extends CardImpl { // Whenever you cast an instant or sorcery spell, put a time counter on Cursed Recording. Then if there are seven or more time counters on it, remove those counters and it deals 20 damage to you. Ability ability = new SpellCastControllerTriggeredAbility( new AddCountersSourceEffect(CounterType.TIME.createInstance()), - StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, true + StaticFilters.FILTER_SPELL_AN_INSTANT_OR_SORCERY, false ); ability.addEffect(new ConditionalOneShotEffect( new RemoveAllCountersSourceEffect(CounterType.TIME), From a045afbd5bc5ff7b568147c8599253d36f519cb9 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 16:26:29 -0400 Subject: [PATCH 35/62] [DSK] Implement Unidentified Hovership --- .../mage/cards/u/UnidentifiedHovership.java | 103 ++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 104 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/u/UnidentifiedHovership.java diff --git a/Mage.Sets/src/mage/cards/u/UnidentifiedHovership.java b/Mage.Sets/src/mage/cards/u/UnidentifiedHovership.java new file mode 100644 index 00000000000..bd8dc5aa691 --- /dev/null +++ b/Mage.Sets/src/mage/cards/u/UnidentifiedHovership.java @@ -0,0 +1,103 @@ +package mage.cards.u; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.LeavesBattlefieldTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ExileTargetForSourceEffect; +import mage.abilities.effects.keyword.ManifestDreadEffect; +import mage.abilities.keyword.CrewAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.Card; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.ToughnessPredicate; +import mage.game.ExileZone; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.util.CardUtil; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class UnidentifiedHovership extends CardImpl { + + private static final FilterPermanent filter = new FilterCreaturePermanent("creature with toughness 5 or less"); + + static { + filter.add(new ToughnessPredicate(ComparisonType.FEWER_THAN, 6)); + } + + public UnidentifiedHovership(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}{W}{W}"); + + this.subtype.add(SubType.VEHICLE); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // When Unidentified Hovership enters, exile up to one target creature with toughness 5 or less. + Ability ability = new EntersBattlefieldTriggeredAbility(new ExileTargetForSourceEffect()); + ability.addTarget(new TargetPermanent(0, 1, filter)); + this.addAbility(ability); + + // When Unidentified Hovership leaves the battlefield, the exiled card's owner manifests dread. + this.addAbility(new LeavesBattlefieldTriggeredAbility(new UnidentifiedHovershipEffect())); + + // Crew 1 + this.addAbility(new CrewAbility(1)); + } + + private UnidentifiedHovership(final UnidentifiedHovership card) { + super(card); + } + + @Override + public UnidentifiedHovership copy() { + return new UnidentifiedHovership(this); + } +} + +class UnidentifiedHovershipEffect extends OneShotEffect { + + UnidentifiedHovershipEffect() { + super(Outcome.Benefit); + staticText = "the exiled card's owner manifests dread"; + } + + private UnidentifiedHovershipEffect(final UnidentifiedHovershipEffect effect) { + super(effect); + } + + @Override + public UnidentifiedHovershipEffect copy() { + return new UnidentifiedHovershipEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + ExileZone exileZone = game.getExile().getExileZone(CardUtil.getExileZoneId(game, source)); + if (exileZone == null || exileZone.isEmpty()) { + return false; + } + for (Card card : exileZone.getCards(game)) { + Player player = game.getPlayer(card.getOwnerId()); + if (player != null) { + ManifestDreadEffect.doManifestDread(player, source, game); + } + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 9fba5211edd..cd39ac7ef96 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -215,6 +215,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Twitching Doll", 201, Rarity.RARE, mage.cards.t.TwitchingDoll.class)); cards.add(new SetCardInfo("Tyvar, the Pummeler", 202, Rarity.MYTHIC, mage.cards.t.TyvarThePummeler.class)); cards.add(new SetCardInfo("Under the Skin", 203, Rarity.UNCOMMON, mage.cards.u.UnderTheSkin.class)); + cards.add(new SetCardInfo("Unidentified Hovership", 37, Rarity.RARE, mage.cards.u.UnidentifiedHovership.class)); cards.add(new SetCardInfo("Unnerving Grasp", 80, Rarity.UNCOMMON, mage.cards.u.UnnervingGrasp.class)); cards.add(new SetCardInfo("Unsettling Twins", 38, Rarity.COMMON, mage.cards.u.UnsettlingTwins.class)); cards.add(new SetCardInfo("Untimely Malfunction", 161, Rarity.UNCOMMON, mage.cards.u.UntimelyMalfunction.class)); From 5b9ad550e7d84bb0ad30a2bc989e086aab58b941 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 16:31:08 -0400 Subject: [PATCH 36/62] [DSK] Implement Cathartic Parting --- .../src/mage/cards/c/CatharticParting.java | 46 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 47 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/c/CatharticParting.java diff --git a/Mage.Sets/src/mage/cards/c/CatharticParting.java b/Mage.Sets/src/mage/cards/c/CatharticParting.java new file mode 100644 index 00000000000..53f0d5c41f5 --- /dev/null +++ b/Mage.Sets/src/mage/cards/c/CatharticParting.java @@ -0,0 +1,46 @@ +package mage.cards.c; + +import mage.abilities.effects.common.ShuffleIntoLibraryTargetEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.TargetController; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterArtifactOrEnchantmentPermanent; +import mage.target.TargetPermanent; +import mage.target.common.TargetCardInYourGraveyard; +import mage.target.targetpointer.SecondTargetPointer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class CatharticParting extends CardImpl { + + private static final FilterPermanent filter = new FilterArtifactOrEnchantmentPermanent("artifact or enchantment an opponent controls"); + + static { + filter.add(TargetController.OPPONENT.getControllerPredicate()); + } + + public CatharticParting(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}"); + + // The owner of target artifact or enchantment an opponent controls shuffles it into their library. You may shuffle up to four target cards from your graveyard into your library. + this.getSpellAbility().addEffect(new ShuffleIntoLibraryTargetEffect()); + this.getSpellAbility().addTarget(new TargetPermanent(filter)); + this.getSpellAbility().addEffect(new ShuffleIntoLibraryTargetEffect(true) + .setTargetPointer(new SecondTargetPointer())); + this.getSpellAbility().addTarget(new TargetCardInYourGraveyard(0, 4)); + } + + private CatharticParting(final CatharticParting card) { + super(card); + } + + @Override + public CatharticParting copy() { + return new CatharticParting(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index cd39ac7ef96..7463b76c668 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -42,6 +42,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Break Down the Door", 170, Rarity.UNCOMMON, mage.cards.b.BreakDownTheDoor.class)); cards.add(new SetCardInfo("Broodspinner", 211, Rarity.UNCOMMON, mage.cards.b.Broodspinner.class)); cards.add(new SetCardInfo("Cackling Slasher", 85, Rarity.COMMON, mage.cards.c.CacklingSlasher.class)); + cards.add(new SetCardInfo("Cathartic Parting", 171, Rarity.UNCOMMON, mage.cards.c.CatharticParting.class)); cards.add(new SetCardInfo("Cautious Survivor", 172, Rarity.COMMON, mage.cards.c.CautiousSurvivor.class)); cards.add(new SetCardInfo("Chainsaw", 128, Rarity.RARE, mage.cards.c.Chainsaw.class)); cards.add(new SetCardInfo("Clammy Prowler", 45, Rarity.COMMON, mage.cards.c.ClammyProwler.class)); From 40c9ae3e82f6ee81a6e455c0d48fe9fbb5a59dbb Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 16:43:53 -0400 Subject: [PATCH 37/62] [DSK] Implement Fear of Burning Alive --- .../src/mage/cards/f/FearOfBurningAlive.java | 99 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 100 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/f/FearOfBurningAlive.java diff --git a/Mage.Sets/src/mage/cards/f/FearOfBurningAlive.java b/Mage.Sets/src/mage/cards/f/FearOfBurningAlive.java new file mode 100644 index 00000000000..11c737ebbf4 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/FearOfBurningAlive.java @@ -0,0 +1,99 @@ +package mage.cards.f; + +import mage.MageInt; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.dynamicvalue.common.SavedDamageValue; +import mage.abilities.effects.common.DamagePlayersEffect; +import mage.abilities.effects.common.DamageTargetEffect; +import mage.abilities.hint.common.CardTypesInGraveyardHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.FilterPermanent; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.permanent.ControllerIdPredicate; +import mage.game.Game; +import mage.game.events.DamagedEvent; +import mage.game.events.GameEvent; +import mage.target.TargetPermanent; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class FearOfBurningAlive extends CardImpl { + + public FearOfBurningAlive(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ENCHANTMENT, CardType.CREATURE}, "{4}{R}{R}"); + + this.subtype.add(SubType.NIGHTMARE); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // When Fear of Burning Alive enters, it deals 4 damage to each opponent. + this.addAbility(new EntersBattlefieldTriggeredAbility( + new DamagePlayersEffect(4, TargetController.OPPONENT, "it") + )); + + // Delirium -- Whenever a source you control deals noncombat damage to an opponent, if there are four or more card types among cards in your graveyard, Fear of Burning Alive deals that amount of damage to target creature that player controls. + this.addAbility(new FearOfBurningAliveTriggeredAbility()); + } + + private FearOfBurningAlive(final FearOfBurningAlive card) { + super(card); + } + + @Override + public FearOfBurningAlive copy() { + return new FearOfBurningAlive(this); + } +} + +class FearOfBurningAliveTriggeredAbility extends TriggeredAbilityImpl { + + FearOfBurningAliveTriggeredAbility() { + super(Zone.BATTLEFIELD, new DamageTargetEffect(SavedDamageValue.MANY) + .setText("{this} deals that amount of damage to target creature that player controls")); + this.setTriggerPhrase("Whenever a source you control deals noncombat damage to an opponent, " + + "if there are four or more card types among cards in your graveyard, "); + this.setAbilityWord(AbilityWord.DELIRIUM); + this.addHint(CardTypesInGraveyardHint.YOU); + } + + private FearOfBurningAliveTriggeredAbility(final FearOfBurningAliveTriggeredAbility ability) { + super(ability); + } + + @Override + public FearOfBurningAliveTriggeredAbility copy() { + return new FearOfBurningAliveTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DAMAGED_PLAYER; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (((DamagedEvent) event).isCombatDamage() + || !isControlledBy(game.getControllerId(event.getSourceId())) + || !game.getOpponents(getControllerId()).contains(event.getTargetId())) { + return false; + } + this.getEffects().setValue("damage", event.getAmount()); + FilterPermanent filter = new FilterCreaturePermanent(); + filter.add(new ControllerIdPredicate(event.getTargetId())); + this.getTargets().clear(); + this.addTarget(new TargetPermanent(filter)); + return true; + } + + @Override + public boolean checkInterveningIfClause(Game game) { + return DeliriumCondition.instance.apply(game, this); + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index 7463b76c668..e0bfe8971d1 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -81,6 +81,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Exorcise", 8, Rarity.UNCOMMON, mage.cards.e.Exorcise.class)); cards.add(new SetCardInfo("Fanatic of the Harrowing", 96, Rarity.COMMON, mage.cards.f.FanaticOfTheHarrowing.class)); cards.add(new SetCardInfo("Fear of Being Hunted", 134, Rarity.UNCOMMON, mage.cards.f.FearOfBeingHunted.class)); + cards.add(new SetCardInfo("Fear of Burning Alive", 135, Rarity.UNCOMMON, mage.cards.f.FearOfBurningAlive.class)); cards.add(new SetCardInfo("Fear of Exposure", 177, Rarity.UNCOMMON, mage.cards.f.FearOfExposure.class)); cards.add(new SetCardInfo("Fear of Failed Tests", 55, Rarity.UNCOMMON, mage.cards.f.FearOfFailedTests.class)); cards.add(new SetCardInfo("Fear of Falling", 56, Rarity.UNCOMMON, mage.cards.f.FearOfFalling.class)); From 2f0559fda3cffab4070b8945e692ced4f9cc0725 Mon Sep 17 00:00:00 2001 From: theelk801 Date: Sat, 14 Sep 2024 17:01:39 -0400 Subject: [PATCH 38/62] [DSK] Implement Marvin, Murderous Mimic --- .../mage/cards/m/MarvinMurderousMimic.java | 87 +++++++++++++++++++ .../src/mage/sets/DuskmournHouseOfHorror.java | 1 + 2 files changed, 88 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/m/MarvinMurderousMimic.java diff --git a/Mage.Sets/src/mage/cards/m/MarvinMurderousMimic.java b/Mage.Sets/src/mage/cards/m/MarvinMurderousMimic.java new file mode 100644 index 00000000000..0cb39a828ce --- /dev/null +++ b/Mage.Sets/src/mage/cards/m/MarvinMurderousMimic.java @@ -0,0 +1,87 @@ +package mage.cards.m; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.ContinuousEffectImpl; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.util.CardUtil; + +import java.util.Collection; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * @author TheElk801 + */ +public final class MarvinMurderousMimic extends CardImpl { + + public MarvinMurderousMimic(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{2}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.TOY); + this.power = new MageInt(2); + this.toughness = new MageInt(2); + + // Marvin, Murderous Mimic has all activated abilities of creatures you control that don't have the same name as this creature. + this.addAbility(new SimpleStaticAbility(new MarvinMurderousMimicEffect())); + } + + private MarvinMurderousMimic(final MarvinMurderousMimic card) { + super(card); + } + + @Override + public MarvinMurderousMimic copy() { + return new MarvinMurderousMimic(this); + } +} + +class MarvinMurderousMimicEffect extends ContinuousEffectImpl { + + MarvinMurderousMimicEffect() { + super(Duration.WhileOnBattlefield, Layer.AbilityAddingRemovingEffects_6, SubLayer.NA, Outcome.AddAbility); + staticText = "{this} has all activated abilities of creatures " + + "you control that don't have the same name as this creature"; + } + + private MarvinMurderousMimicEffect(final MarvinMurderousMimicEffect effect) { + super(effect); + } + + @Override + public MarvinMurderousMimicEffect copy() { + return new MarvinMurderousMimicEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + return false; + } + List abilities = game + .getBattlefield() + .getActivePermanents( + StaticFilters.FILTER_CONTROLLED_CREATURE, + source.getControllerId(), source, game + ) + .stream() + .filter(p -> !CardUtil.haveSameNames(p, permanent)) + .map(p -> p.getAbilities(game)) + .flatMap(Collection::stream) + .filter(Ability::isActivatedAbility) + .collect(Collectors.toList()); + for (Ability ability : abilities) { + permanent.addAbility(ability, source.getSourceId(), game); + } + return true; + } +} diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java index e0bfe8971d1..1b304b8f2f6 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorror.java @@ -140,6 +140,7 @@ public final class DuskmournHouseOfHorror extends ExpansionSet { cards.add(new SetCardInfo("Malevolent Chandelier", 252, Rarity.COMMON, mage.cards.m.MalevolentChandelier.class)); cards.add(new SetCardInfo("Manifest Dread", 189, Rarity.COMMON, mage.cards.m.ManifestDread.class)); cards.add(new SetCardInfo("Marina Vendrell's Grimoire", 64, Rarity.RARE, mage.cards.m.MarinaVendrellsGrimoire.class)); + cards.add(new SetCardInfo("Marvin, Murderous Mimic", 253, Rarity.RARE, mage.cards.m.MarvinMurderousMimic.class)); cards.add(new SetCardInfo("Miasma Demon", 109, Rarity.UNCOMMON, mage.cards.m.MiasmaDemon.class)); cards.add(new SetCardInfo("Midnight Mayhem", 222, Rarity.UNCOMMON, mage.cards.m.MidnightMayhem.class)); cards.add(new SetCardInfo("Most Valuable Slayer", 144, Rarity.COMMON, mage.cards.m.MostValuableSlayer.class)); From 7eee2c7ef26fff5b3b2d922310758e4e7651e7a0 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sun, 15 Sep 2024 18:03:26 -0400 Subject: [PATCH 39/62] refactor: properly support "in addition to its other types" in BecomesCreatureSourceEffect, rather than always relying on the original card type to infer --- Mage.Sets/src/mage/cards/h/HauntedScreen.java | 2 +- Mage.Sets/src/mage/cards/r/RestlessSpire.java | 4 +- .../src/mage/cards/r/RestlessVinestalk.java | 4 +- .../BecomesCreatureSourceEffect.java | 43 ++++++++++--------- 4 files changed, 28 insertions(+), 25 deletions(-) diff --git a/Mage.Sets/src/mage/cards/h/HauntedScreen.java b/Mage.Sets/src/mage/cards/h/HauntedScreen.java index 0e66a65662c..773a04ee2f7 100644 --- a/Mage.Sets/src/mage/cards/h/HauntedScreen.java +++ b/Mage.Sets/src/mage/cards/h/HauntedScreen.java @@ -46,7 +46,7 @@ public final class HauntedScreen extends CardImpl { ); ability.addEffect(new BecomesCreatureSourceEffect(new CreatureToken( 0, 0, "0/0 Spirit creature", SubType.SPIRIT - ), CardType.ARTIFACT, Duration.Custom)); + ), CardType.ARTIFACT, Duration.Custom).withKeepCreatureSubtypes(true)); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/r/RestlessSpire.java b/Mage.Sets/src/mage/cards/r/RestlessSpire.java index e2855d726b0..f2b13bba9b0 100644 --- a/Mage.Sets/src/mage/cards/r/RestlessSpire.java +++ b/Mage.Sets/src/mage/cards/r/RestlessSpire.java @@ -46,8 +46,8 @@ public final class RestlessSpire extends CardImpl { new GainAbilitySourceEffect(FirstStrikeAbility.getInstance(), Duration.WhileOnBattlefield), MyTurnCondition.instance, "As long as it's your turn, this creature has first strike." )).addHint(MyTurnHint.instance)), - CardType.LAND, Duration.EndOfTurn, true - ), new ManaCostsImpl<>("{U}{R}"))); + CardType.LAND, Duration.EndOfTurn + ).withDurationRuleAtStart(true), new ManaCostsImpl<>("{U}{R}"))); // Whenever Restless Spire attacks, scry 1. this.addAbility(new AttacksTriggeredAbility(new ScryEffect(1, false), false)); diff --git a/Mage.Sets/src/mage/cards/r/RestlessVinestalk.java b/Mage.Sets/src/mage/cards/r/RestlessVinestalk.java index e89b56d5999..8bc24640ea7 100644 --- a/Mage.Sets/src/mage/cards/r/RestlessVinestalk.java +++ b/Mage.Sets/src/mage/cards/r/RestlessVinestalk.java @@ -48,8 +48,8 @@ public final class RestlessVinestalk extends CardImpl { new CreatureToken(5, 5, "5/5 green and blue Plant creature with trample") .withColor("GU").withSubType(SubType.PLANT) .withAbility(TrampleAbility.getInstance()), - CardType.LAND, Duration.EndOfTurn, true - ), new ManaCostsImpl<>("{3}{G}{U}"))); + CardType.LAND, Duration.EndOfTurn + ).withDurationRuleAtStart(true), new ManaCostsImpl<>("{3}{G}{U}"))); // Whenever Restless Vinestalk attacks, up to one other target creature has base power and toughness 3/3 until end of turn. Ability ability = new AttacksTriggeredAbility(new SetBasePowerToughnessTargetEffect(3, 3, Duration.EndOfTurn), false); diff --git a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureSourceEffect.java b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureSourceEffect.java index 353ad5236c8..7f3ef03a30f 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureSourceEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/continuous/BecomesCreatureSourceEffect.java @@ -35,13 +35,14 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl { * existing creature types. */ - protected Token token; - protected CardType retainType; // if null, loses previous types - protected boolean loseAbilities = false; - protected boolean loseEquipmentType = false; - protected DynamicValue power = null; - protected DynamicValue toughness = null; - protected boolean durationRuleAtStart; // put duration rule at the start of the rules text rather than the end + private final Token token; + private final CardType retainType; // if null, loses previous types + private boolean loseAbilities = false; + private boolean loseEquipmentType = false; + private boolean keepCreatureSubtypes; + private DynamicValue power = null; + private DynamicValue toughness = null; + private boolean durationRuleAtStart; // put duration rule at the start of the rules text rather than the end /** * @param token Token as blueprint for creature to become @@ -49,20 +50,11 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl { * @param duration Duration for the effect */ public BecomesCreatureSourceEffect(Token token, CardType retainType, Duration duration) { - this(token, retainType, duration, (retainType == CardType.PLANESWALKER || retainType == CardType.CREATURE)); - } - - /** - * @param token Token as blueprint for creature to become - * @param retainType If null, permanent loses its previous types, otherwise retains types with appropriate text - * @param duration Duration for the effect - * @param durationRuleAtStart for text rule generation - */ - public BecomesCreatureSourceEffect(Token token, CardType retainType, Duration duration, boolean durationRuleAtStart) { super(duration, Outcome.BecomeCreature); this.token = token; this.retainType = retainType; - this.durationRuleAtStart = durationRuleAtStart; + this.keepCreatureSubtypes = (retainType == CardType.ENCHANTMENT); // default usage, override if needed + this.durationRuleAtStart = (retainType == CardType.PLANESWALKER || retainType == CardType.CREATURE); setText(); this.addDependencyType(DependencyType.BecomeCreature); } @@ -73,6 +65,7 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl { this.retainType = effect.retainType; this.loseAbilities = effect.loseAbilities; this.loseEquipmentType = effect.loseEquipmentType; + this.keepCreatureSubtypes = effect.keepCreatureSubtypes; if (effect.power != null) { this.power = effect.power.copy(); } @@ -124,7 +117,7 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl { if (loseEquipmentType) { permanent.removeSubType(game, SubType.EQUIPMENT); } - if (retainType == CardType.CREATURE || retainType == CardType.ARTIFACT) { + if (!keepCreatureSubtypes) { permanent.removeAllCreatureTypes(game); } permanent.copySubTypesFrom(game, token); @@ -191,6 +184,16 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl { return this; } + /** + * Source becomes a creature "in addition to its other types". + * Not needed when retainType is ENCHANTMENT, which sets this true by default. + */ + public BecomesCreatureSourceEffect withKeepCreatureSubtypes(boolean keepCreatureSubtypes) { + this.keepCreatureSubtypes = keepCreatureSubtypes; + setText(); + return this; + } + public BecomesCreatureSourceEffect withDurationRuleAtStart(boolean durationRuleAtStart) { this.durationRuleAtStart = durationRuleAtStart; setText(); @@ -205,7 +208,7 @@ public class BecomesCreatureSourceEffect extends ContinuousEffectImpl { } sb.append("{this} becomes a "); sb.append(token.getDescription()); - if (retainType == CardType.ENCHANTMENT) { + if (keepCreatureSubtypes) { sb.append(" in addition to its other types"); } if (!duration.toString().isEmpty() && !durationRuleAtStart) { From 282eb49364944221a8407de408a0b753c61d389e Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sun, 15 Sep 2024 18:04:08 -0400 Subject: [PATCH 40/62] a couple text fixes --- Mage.Sets/src/mage/cards/c/CatharticParting.java | 3 ++- Mage.Sets/src/mage/cards/c/CrackedSkull.java | 2 +- Mage.Sets/src/mage/cards/c/CursedRecording.java | 2 +- Mage.Sets/src/mage/cards/j/JumpScare.java | 4 ++-- Mage.Sets/src/mage/cards/n/NecropolisRegent.java | 3 ++- .../common/DealtDamageAttachedTriggeredAbility.java | 8 +++----- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/CatharticParting.java b/Mage.Sets/src/mage/cards/c/CatharticParting.java index 53f0d5c41f5..2c68791046e 100644 --- a/Mage.Sets/src/mage/cards/c/CatharticParting.java +++ b/Mage.Sets/src/mage/cards/c/CatharticParting.java @@ -28,7 +28,8 @@ public final class CatharticParting extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{1}{G}"); // The owner of target artifact or enchantment an opponent controls shuffles it into their library. You may shuffle up to four target cards from your graveyard into your library. - this.getSpellAbility().addEffect(new ShuffleIntoLibraryTargetEffect()); + this.getSpellAbility().addEffect(new ShuffleIntoLibraryTargetEffect() + .setText("the owner of target artifact or enchantment an opponent controls shuffles it into their library")); this.getSpellAbility().addTarget(new TargetPermanent(filter)); this.getSpellAbility().addEffect(new ShuffleIntoLibraryTargetEffect(true) .setTargetPointer(new SecondTargetPointer())); diff --git a/Mage.Sets/src/mage/cards/c/CrackedSkull.java b/Mage.Sets/src/mage/cards/c/CrackedSkull.java index 32eafc76f61..3ebc091b072 100644 --- a/Mage.Sets/src/mage/cards/c/CrackedSkull.java +++ b/Mage.Sets/src/mage/cards/c/CrackedSkull.java @@ -44,7 +44,7 @@ public final class CrackedSkull extends CardImpl { this.addAbility(new DealtDamageAttachedTriggeredAbility( Zone.BATTLEFIELD, new DestroyTargetEffect("destroy it"), false, SetTargetPointer.PERMANENT - )); + ).setTriggerPhrase("When enchanted creature is dealt damage, ")); } private CrackedSkull(final CrackedSkull card) { diff --git a/Mage.Sets/src/mage/cards/c/CursedRecording.java b/Mage.Sets/src/mage/cards/c/CursedRecording.java index aced16673f4..9f1ef03f51b 100644 --- a/Mage.Sets/src/mage/cards/c/CursedRecording.java +++ b/Mage.Sets/src/mage/cards/c/CursedRecording.java @@ -37,7 +37,7 @@ public final class CursedRecording extends CardImpl { ); ability.addEffect(new ConditionalOneShotEffect( new RemoveAllCountersSourceEffect(CounterType.TIME), - condition, "then if there are seven or more time counters on it, " + + condition, "Then if there are seven or more time counters on it, " + "remove those counters and it deals 20 damage to you" ).addEffect(new DamageControllerEffect(20))); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/j/JumpScare.java b/Mage.Sets/src/mage/cards/j/JumpScare.java index 7e0690f69ee..1856358be54 100644 --- a/Mage.Sets/src/mage/cards/j/JumpScare.java +++ b/Mage.Sets/src/mage/cards/j/JumpScare.java @@ -29,10 +29,10 @@ public final class JumpScare extends CardImpl { .setText(", gains flying")); this.getSpellAbility().addEffect(new AddCardSubTypeTargetEffect( SubType.HORROR, Duration.EndOfTurn - ).setText(", and becomes ")); + ).setText(", and becomes")); this.getSpellAbility().addEffect(new AddCardTypeTargetEffect( Duration.EndOfTurn, CardType.ENCHANTMENT, CardType.CREATURE - ).setText("a Horror enchantment creature in addition to its other types")); + ).setText(" a Horror enchantment creature in addition to its other types")); this.getSpellAbility().addTarget(new TargetCreaturePermanent()); } diff --git a/Mage.Sets/src/mage/cards/n/NecropolisRegent.java b/Mage.Sets/src/mage/cards/n/NecropolisRegent.java index f64f314f3fb..9eaf9838154 100644 --- a/Mage.Sets/src/mage/cards/n/NecropolisRegent.java +++ b/Mage.Sets/src/mage/cards/n/NecropolisRegent.java @@ -32,7 +32,8 @@ public final class NecropolisRegent extends CardImpl { // Whenever a creature you control deals combat damage to a player, put that many +1/+1 counters on it. this.addAbility(new DealsDamageToAPlayerAllTriggeredAbility( - new AddCountersTargetEffect(CounterType.P1P1.createInstance(), SavedDamageValue.MANY), + new AddCountersTargetEffect(CounterType.P1P1.createInstance(), SavedDamageValue.MANY) + .setText("put that many +1/+1 counters on it"), StaticFilters.FILTER_CONTROLLED_A_CREATURE, false, SetTargetPointer.PERMANENT, true )); diff --git a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedTriggeredAbility.java index 48227613ddd..91abbc177c2 100644 --- a/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/DealtDamageAttachedTriggeredAbility.java @@ -1,18 +1,16 @@ - package mage.abilities.common; -import java.util.UUID; - import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.Effect; import mage.constants.SetTargetPointer; import mage.constants.Zone; import mage.game.Game; -import mage.game.events.GameEvent.EventType; import mage.game.events.GameEvent; import mage.game.permanent.Permanent; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * @author LoneFox */ @@ -27,7 +25,7 @@ public class DealtDamageAttachedTriggeredAbility extends TriggeredAbilityImpl { public DealtDamageAttachedTriggeredAbility(Zone zone, Effect effect, boolean optional, SetTargetPointer setTargetPointer) { super(zone, effect, optional); this.setTargetPointer = setTargetPointer; - setTriggerPhrase("Whenever enchanted creature is dealt damage, "); + setTriggerPhrase(getWhen() + "enchanted creature is dealt damage, "); } protected DealtDamageAttachedTriggeredAbility(final DealtDamageAttachedTriggeredAbility ability) { From 497b4321b33dab284a5dcb17743b60f0ad3e8829 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sun, 15 Sep 2024 18:10:18 -0400 Subject: [PATCH 41/62] text gen fix from recent errata --- Mage.Sets/src/mage/cards/b/BrutalCathar.java | 2 +- .../abilities/common/TransformsOrEntersTriggeredAbility.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BrutalCathar.java b/Mage.Sets/src/mage/cards/b/BrutalCathar.java index a52ad602d22..bf58c854a03 100644 --- a/Mage.Sets/src/mage/cards/b/BrutalCathar.java +++ b/Mage.Sets/src/mage/cards/b/BrutalCathar.java @@ -32,7 +32,7 @@ public final class BrutalCathar extends CardImpl { Ability ability = new TransformsOrEntersTriggeredAbility( new ExileUntilSourceLeavesEffect() .setText("exile target creature an opponent controls until this creature leaves the battlefield"), false - ).setTriggerPhrase("When this creature enters the battlefield or transforms into {this}, "); + ).setTriggerPhrase("When this creature enters or transforms into {this}, "); ability.addTarget(new TargetOpponentsCreaturePermanent()); this.addAbility(ability); diff --git a/Mage/src/main/java/mage/abilities/common/TransformsOrEntersTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/TransformsOrEntersTriggeredAbility.java index c68b3d17559..43635f5246c 100644 --- a/Mage/src/main/java/mage/abilities/common/TransformsOrEntersTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/TransformsOrEntersTriggeredAbility.java @@ -14,7 +14,7 @@ public class TransformsOrEntersTriggeredAbility extends TriggeredAbilityImpl { public TransformsOrEntersTriggeredAbility(Effect effect, boolean optional) { super(Zone.BATTLEFIELD, effect, optional); - setTriggerPhrase("Whenever this creature enters the battlefield or transforms into {this}, "); + setTriggerPhrase("Whenever this creature enters or transforms into {this}, "); } private TransformsOrEntersTriggeredAbility(final TransformsOrEntersTriggeredAbility ability) { From e2531a4da587f3b34f80eb4a02afef53889d67d4 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sun, 15 Sep 2024 18:26:20 -0400 Subject: [PATCH 42/62] fix Cracked Skull, expand LookTargetHandChooseDiscardEffect to support FilterCard --- Mage.Sets/src/mage/cards/a/AbandonHope.java | 2 +- Mage.Sets/src/mage/cards/c/CrackedSkull.java | 6 ++++-- Mage.Sets/src/mage/cards/m/MindWarp.java | 3 ++- .../discard/LookTargetHandChooseDiscardEffect.java | 10 +++++++--- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AbandonHope.java b/Mage.Sets/src/mage/cards/a/AbandonHope.java index a36a92f52d0..16365813b9d 100644 --- a/Mage.Sets/src/mage/cards/a/AbandonHope.java +++ b/Mage.Sets/src/mage/cards/a/AbandonHope.java @@ -35,7 +35,7 @@ public final class AbandonHope extends CardImpl { this.addAbility(ability); // Look at target opponent's hand and choose X cards from it. That player discards those cards. - this.getSpellAbility().addEffect(new LookTargetHandChooseDiscardEffect(false, GetXValue.instance)); + this.getSpellAbility().addEffect(new LookTargetHandChooseDiscardEffect(false, GetXValue.instance, StaticFilters.FILTER_CARD_CARDS)); this.getSpellAbility().addTarget(new TargetOpponent()); this.getSpellAbility().setCostAdjuster(AbandonHopeAdjuster.instance); } diff --git a/Mage.Sets/src/mage/cards/c/CrackedSkull.java b/Mage.Sets/src/mage/cards/c/CrackedSkull.java index 3ebc091b072..979da861bb4 100644 --- a/Mage.Sets/src/mage/cards/c/CrackedSkull.java +++ b/Mage.Sets/src/mage/cards/c/CrackedSkull.java @@ -3,9 +3,10 @@ package mage.cards.c; import mage.abilities.Ability; import mage.abilities.common.DealtDamageAttachedTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.DestroyTargetEffect; -import mage.abilities.effects.common.discard.DiscardCardYouChooseTargetEffect; +import mage.abilities.effects.common.discard.LookTargetHandChooseDiscardEffect; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -35,7 +36,8 @@ public final class CrackedSkull extends CardImpl { // When Cracked Skull enters, look at target player's hand. You may choose a nonland card from it. That player discards that card. Ability ability = new EntersBattlefieldTriggeredAbility( - new DiscardCardYouChooseTargetEffect(StaticFilters.FILTER_CARD_NON_LAND).setOptional(true) + new LookTargetHandChooseDiscardEffect(true, StaticValue.get(1), StaticFilters.FILTER_CARD_NON_LAND) + .setText("look at target player's hand. You may choose a nonland card from it. That player discards that card") ); ability.addTarget(new TargetPlayer()); this.addAbility(ability); diff --git a/Mage.Sets/src/mage/cards/m/MindWarp.java b/Mage.Sets/src/mage/cards/m/MindWarp.java index 8be54dd06d4..bdd02b05fe9 100644 --- a/Mage.Sets/src/mage/cards/m/MindWarp.java +++ b/Mage.Sets/src/mage/cards/m/MindWarp.java @@ -5,6 +5,7 @@ import mage.abilities.effects.common.discard.LookTargetHandChooseDiscardEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; +import mage.filter.StaticFilters; import mage.target.TargetPlayer; import java.util.UUID; @@ -18,7 +19,7 @@ public final class MindWarp extends CardImpl { super(ownerId, setInfo, new CardType[]{CardType.SORCERY}, "{X}{3}{B}"); // Look at target player's hand and choose X cards from it. That player discards those cards. - this.getSpellAbility().addEffect(new LookTargetHandChooseDiscardEffect(false, GetXValue.instance)); + this.getSpellAbility().addEffect(new LookTargetHandChooseDiscardEffect(false, GetXValue.instance, StaticFilters.FILTER_CARD_CARDS)); this.getSpellAbility().addTarget(new TargetPlayer()); } diff --git a/Mage/src/main/java/mage/abilities/effects/common/discard/LookTargetHandChooseDiscardEffect.java b/Mage/src/main/java/mage/abilities/effects/common/discard/LookTargetHandChooseDiscardEffect.java index 23e11e36107..e8dc09bd813 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/discard/LookTargetHandChooseDiscardEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/discard/LookTargetHandChooseDiscardEffect.java @@ -7,6 +7,7 @@ import mage.abilities.dynamicvalue.common.StaticValue; import mage.abilities.effects.OneShotEffect; import mage.cards.CardsImpl; import mage.constants.Outcome; +import mage.filter.FilterCard; import mage.filter.StaticFilters; import mage.game.Game; import mage.players.Player; @@ -21,25 +22,28 @@ public class LookTargetHandChooseDiscardEffect extends OneShotEffect { private final boolean upTo; private final DynamicValue numberToDiscard; + private final FilterCard filter; public LookTargetHandChooseDiscardEffect() { this(false, 1); } public LookTargetHandChooseDiscardEffect(boolean upTo, int numberToDiscard) { - this(upTo, StaticValue.get(numberToDiscard)); + this(upTo, StaticValue.get(numberToDiscard), numberToDiscard == 1 ? StaticFilters.FILTER_CARD : StaticFilters.FILTER_CARD_CARDS); } - public LookTargetHandChooseDiscardEffect(boolean upTo, DynamicValue numberToDiscard) { + public LookTargetHandChooseDiscardEffect(boolean upTo, DynamicValue numberToDiscard, FilterCard filter) { super(Outcome.Discard); this.upTo = upTo; this.numberToDiscard = numberToDiscard; + this.filter = filter; } protected LookTargetHandChooseDiscardEffect(final LookTargetHandChooseDiscardEffect effect) { super(effect); this.upTo = effect.upTo; this.numberToDiscard = effect.numberToDiscard; + this.filter = effect.filter; } @Override @@ -56,7 +60,7 @@ public class LookTargetHandChooseDiscardEffect extends OneShotEffect { } return true; } - TargetCard target = new TargetCardInHand(upTo ? 0 : num, num, num > 1 ? StaticFilters.FILTER_CARD_CARDS : StaticFilters.FILTER_CARD); + TargetCard target = new TargetCardInHand(upTo ? 0 : num, num, filter); if (controller.choose(Outcome.Discard, player.getHand(), target, source, game)) { player.discard(new CardsImpl(target.getTargets()), false, source, game); } From 1af081436bc72486069e96eb08fa50c48894aa3a Mon Sep 17 00:00:00 2001 From: Sidorovich77 Date: Mon, 16 Sep 2024 01:27:34 +0300 Subject: [PATCH 43/62] [UNF] Implement Strength-Testing Hammer; fix Six-Sided Die (#12756) --- Mage.Sets/src/mage/cards/s/SixSidedDie.java | 62 +++++++---- .../mage/cards/s/StrengthTestingHammer.java | 105 ++++++++++++++++++ Mage.Sets/src/mage/sets/Unfinity.java | 1 + 3 files changed, 145 insertions(+), 23 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/s/StrengthTestingHammer.java diff --git a/Mage.Sets/src/mage/cards/s/SixSidedDie.java b/Mage.Sets/src/mage/cards/s/SixSidedDie.java index 83d1fe6912d..9f1e16cee85 100644 --- a/Mage.Sets/src/mage/cards/s/SixSidedDie.java +++ b/Mage.Sets/src/mage/cards/s/SixSidedDie.java @@ -1,15 +1,12 @@ package mage.cards.s; import mage.abilities.Ability; +import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.continuous.BoostTargetEffect; -import mage.abilities.effects.common.continuous.SetBasePowerToughnessTargetEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Duration; -import mage.constants.Outcome; -import mage.constants.Zone; +import mage.constants.*; import mage.counters.CounterType; import mage.game.Game; import mage.game.permanent.Permanent; @@ -25,20 +22,13 @@ public final class SixSidedDie extends CardImpl { public SixSidedDie(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.INSTANT}, "{2}{B}"); - // Choose target creature. Roll a six-sided die. - // - //1 — It has base toughness 1 until end of turn. - // - //2 — Put two -1/-1 counters on it. - // - //3 — Six-Sided Die deals 3 damage to it, and you gain 3 life. - // - //4 — It gets -4/-4 until end of turn. - // - //5 — Destroy it. - // - //6 — Exile it. + // 1 — It has base toughness 1 until end of turn. + // 2 — Put two -1/-1 counters on it. + // 3 — Six-Sided Die deals 3 damage to it, and you gain 3 life. + // 4 — It gets -4/-4 until end of turn. + // 5 — Destroy it. + // 6 — Exile it. this.getSpellAbility().addTarget(new TargetCreaturePermanent()); this.getSpellAbility().addEffect(new SixSidedDieEffect()); } @@ -56,7 +46,8 @@ public final class SixSidedDie extends CardImpl { class SixSidedDieEffect extends OneShotEffect { SixSidedDieEffect() { - super(Outcome.Benefit); + super(Outcome.Detriment); + setText("choose target creature. Roll a six-sided die." + "
1 — It has base toughness 1 until end of turn." + "
2 — Put two -1/-1 counters on it." + @@ -82,10 +73,11 @@ class SixSidedDieEffect extends OneShotEffect { if (player == null || permanent == null) { return false; } - int result = player.rollDice(outcome, source, game, 1); + int result = player.rollDice(outcome, source, game, 6); switch (result) { - case 6: - game.addEffect(new SetBasePowerToughnessTargetEffect(1, 1, Duration.EndOfTurn), source); + case 1: + //Based on Chariot of the Sun + game.addEffect(new SixSidedDieToughnessEffect(), source); break; case 2: permanent.addCounters(CounterType.M1M1.createInstance(2), source, game); @@ -100,10 +92,34 @@ class SixSidedDieEffect extends OneShotEffect { case 5: permanent.destroy(source, game); break; - case 1: + case 6: + player.moveCards(permanent, Zone.EXILED, source, game); break; } return true; } } + +class SixSidedDieToughnessEffect extends ContinuousEffectImpl { + + SixSidedDieToughnessEffect() { + super(Duration.EndOfTurn, Layer.PTChangingEffects_7, SubLayer.SetPT_7b, Outcome.UnboostCreature); + } + + private SixSidedDieToughnessEffect(final SixSidedDieToughnessEffect effect) { + super(effect); + } + + @Override + public SixSidedDieToughnessEffect copy() { + return new SixSidedDieToughnessEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Permanent permanent = game.getPermanent(getTargetPointer().getFirst(game, source)); + permanent.getToughness().setModifiedBaseValue(1); + return true; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/s/StrengthTestingHammer.java b/Mage.Sets/src/mage/cards/s/StrengthTestingHammer.java new file mode 100644 index 00000000000..a3c56ecdb5b --- /dev/null +++ b/Mage.Sets/src/mage/cards/s/StrengthTestingHammer.java @@ -0,0 +1,105 @@ +package mage.cards.s; + +import mage.abilities.Ability; +import mage.abilities.TriggeredAbility; +import mage.abilities.common.AttacksAttachedTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.decorator.ConditionalOneShotEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawCardSourceControllerEffect; +import mage.abilities.effects.common.continuous.BoostEquippedEffect; +import mage.abilities.keyword.EquipAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.mageobject.PowerPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; + +import java.util.UUID; + +/** + * @author Sidorovich77 + */ +public final class StrengthTestingHammer extends CardImpl { + + + public StrengthTestingHammer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{1}"); + + this.subtype.add(SubType.EQUIPMENT); + + //Whenever equipped creature attacks, roll a six-sided die. + // That creature gets +X/+0 until end of turn, where X is the result. + // Then, if it has the greatest power or is tied for greatest power among creatures on the battlefield, draw a card. + + + TriggeredAbility ability = new AttacksAttachedTriggeredAbility(new StrengthTestingHammerEffect()); + ability.addEffect(new ConditionalOneShotEffect(new DrawCardSourceControllerEffect(1), StrengthTestingHammerCondition.instance).setText("Then if it has the greatest power or is tied for greatest power among creatures on the battlefield, draw a card.")); + this.addAbility(ability); + + // Equip {3} + this.addAbility(new EquipAbility(3)); + } + + private StrengthTestingHammer(final StrengthTestingHammer card) { + super(card); + } + + @Override + public StrengthTestingHammer copy() { + return new StrengthTestingHammer(this); + } +} + +//Based on Historian's Wisdom +enum StrengthTestingHammerCondition implements Condition { + instance; + + @Override + public boolean apply(Game game, Ability source) { + Permanent hammer = source.getSourcePermanentIfItStillExists(game); + if (hammer == null) { + return false; + } + Permanent creature = game.getPermanent(hammer.getAttachedTo()); + if (creature == null) { + return false; + } + FilterCreaturePermanent filter = new FilterCreaturePermanent(); + filter.add(new PowerPredicate(ComparisonType.MORE_THAN, creature.getPower().getValue())); + return game.getBattlefield().getActivePermanents(filter, source.getControllerId(), source, game).isEmpty(); + } +} + +class StrengthTestingHammerEffect extends OneShotEffect { + + StrengthTestingHammerEffect() { + super(Outcome.Benefit); + staticText = "roll a six-sided die. That creature gets +X/+0 until end of turn, where X is the result."; + } + + private StrengthTestingHammerEffect(final StrengthTestingHammerEffect effect) { + super(effect); + } + + @Override + public StrengthTestingHammerEffect copy() { + return new StrengthTestingHammerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(source.getControllerId()); + if (player != null) { + int result = player.rollDice(outcome, source, game, 6); + game.addEffect(new BoostEquippedEffect(result, 0, Duration.EndOfTurn), source); + } + return false; + } +} + + + diff --git a/Mage.Sets/src/mage/sets/Unfinity.java b/Mage.Sets/src/mage/sets/Unfinity.java index b6afc3459e9..cf31e282786 100644 --- a/Mage.Sets/src/mage/sets/Unfinity.java +++ b/Mage.Sets/src/mage/sets/Unfinity.java @@ -56,6 +56,7 @@ public final class Unfinity extends ExpansionSet { cards.add(new SetCardInfo("Slight Malfunction", 123, Rarity.COMMON, mage.cards.s.SlightMalfunction.class)); cards.add(new SetCardInfo("Starlight Spectacular", 28, Rarity.RARE, mage.cards.s.StarlightSpectacular.class)); cards.add(new SetCardInfo("Steam Vents", 283, Rarity.RARE, mage.cards.s.SteamVents.class)); + cards.add(new SetCardInfo("Strength-Testing Hammer", 193, Rarity.UNCOMMON, mage.cards.s.StrengthTestingHammer.class)); cards.add(new SetCardInfo("Stomping Ground", 280, Rarity.RARE, mage.cards.s.StompingGround.class)); cards.add(new SetCardInfo("Swamp", 237, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_UST_VARIOUS)); cards.add(new SetCardInfo("Swamp", 242, Rarity.LAND, mage.cards.basiclands.Swamp.class, FULL_ART_UST_VARIOUS)); From 261554fdca79b5f0a647584ec4c11f311a9b3a33 Mon Sep 17 00:00:00 2001 From: Cameron Merkel <44722506+Cguy7777@users.noreply.github.com> Date: Sun, 15 Sep 2024 17:27:44 -0500 Subject: [PATCH 44/62] [MB2] Implement Wowzer, the Aspirational (#12835) --- .../mage/cards/w/WowzerTheAspirational.java | 124 ++++++++++++++++++ Mage.Sets/src/mage/sets/MysteryBooster2.java | 1 + 2 files changed, 125 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/w/WowzerTheAspirational.java diff --git a/Mage.Sets/src/mage/cards/w/WowzerTheAspirational.java b/Mage.Sets/src/mage/cards/w/WowzerTheAspirational.java new file mode 100644 index 00000000000..55cfdf03022 --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WowzerTheAspirational.java @@ -0,0 +1,124 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.condition.CompoundCondition; +import mage.abilities.condition.Condition; +import mage.abilities.condition.IntCompareCondition; +import mage.abilities.condition.common.CitysBlessingCondition; +import mage.abilities.condition.common.HaveInitiativeCondition; +import mage.abilities.condition.common.MonarchIsSourceControllerCondition; +import mage.abilities.condition.common.PermanentsOnTheBattlefieldCondition; +import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; +import mage.abilities.dynamicvalue.common.CountersControllerCount; +import mage.abilities.effects.common.WinGameSourceControllerEffect; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.abilities.hint.common.CitysBlessingHint; +import mage.abilities.hint.common.InitiativeHint; +import mage.abilities.hint.common.MonarchHint; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.ComparisonType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.counters.CounterType; +import mage.filter.common.FilterControlledPermanent; +import mage.game.Game; + +import java.util.UUID; + +/** + * @author Cguy7777 + */ +public class WowzerTheAspirational extends CardImpl { + + private static final Condition energyCondition = new WowzerTheAspirationalCondition(); + private static final Condition bloodCondition + = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.BLOOD)); + private static final Condition clueCondition + = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.CLUE)); + private static final Condition foodCondition + = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.FOOD)); + private static final Condition mapCondition + = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.MAP)); + private static final Condition powerstoneCondition + = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.POWERSTONE)); + private static final Condition treasureCondition + = new PermanentsOnTheBattlefieldCondition(new FilterControlledPermanent(SubType.TREASURE)); + + private static final Condition winCondition = new CompoundCondition( + energyCondition, + bloodCondition, + clueCondition, + foodCondition, + mapCondition, + powerstoneCondition, + treasureCondition, + MonarchIsSourceControllerCondition.instance, + CitysBlessingCondition.instance, + HaveInitiativeCondition.instance); + + private static final Hint energyHint = new ConditionHint(energyCondition, "You have an {E}"); + private static final Hint bloodHint = new ConditionHint(bloodCondition, "You control a Blood"); + private static final Hint clueHint = new ConditionHint(clueCondition, "You control a Clue"); + private static final Hint foodHint = new ConditionHint(foodCondition, "You control a Food"); + private static final Hint mapHint = new ConditionHint(mapCondition, "You control a Map"); + private static final Hint powerstoneHint + = new ConditionHint(powerstoneCondition, "You control a Powerstone"); + private static final Hint treasureHint + = new ConditionHint(treasureCondition, "You control a Treasure"); + + public WowzerTheAspirational(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{C}{W}{U}{B}{R}{G}{S}"); + + this.supertype.add(SuperType.LEGENDARY); + this.supertype.add(SuperType.SNOW); + this.subtype.add(SubType.WURM); + this.power = new MageInt(10); + this.toughness = new MageInt(10); + + // Whenever Wowzer, the Aspirational attacks, + // if you have an {E}, control a Blood, a Clue, a Food, a Map, a Powerstone, and a Treasure, + // are the monarch, and have the city's blessing and the initiative, you win the game. + this.addAbility(new ConditionalInterveningIfTriggeredAbility( + new AttacksTriggeredAbility(new WinGameSourceControllerEffect()), + winCondition, + "Whenever {this} attacks, " + + "if you have an {E}, control a Blood, a Clue, a Food, a Map, a Powerstone, and a Treasure, " + + "are the monarch, and have the city's blessing and the initiative, you win the game." + ).addHint(energyHint) + .addHint(bloodHint) + .addHint(clueHint) + .addHint(foodHint) + .addHint(mapHint) + .addHint(powerstoneHint) + .addHint(treasureHint) + .addHint(MonarchHint.instance) + .addHint(CitysBlessingHint.instance) + .addHint(InitiativeHint.instance)); + } + + private WowzerTheAspirational(final WowzerTheAspirational card) { + super(card); + } + + @Override + public WowzerTheAspirational copy() { + return new WowzerTheAspirational(this); + } +} + +class WowzerTheAspirationalCondition extends IntCompareCondition { + + WowzerTheAspirationalCondition() { + super(ComparisonType.MORE_THAN, 0); + } + + @Override + protected int getInputValue(Game game, Ability source) { + return new CountersControllerCount(CounterType.ENERGY).calculate(game, source, null); + } +} diff --git a/Mage.Sets/src/mage/sets/MysteryBooster2.java b/Mage.Sets/src/mage/sets/MysteryBooster2.java index 0fdc0fda870..b8cdfde2de6 100644 --- a/Mage.Sets/src/mage/sets/MysteryBooster2.java +++ b/Mage.Sets/src/mage/sets/MysteryBooster2.java @@ -278,6 +278,7 @@ public class MysteryBooster2 extends ExpansionSet { cards.add(new SetCardInfo("Wish", 64, Rarity.RARE, mage.cards.w.Wish.class)); cards.add(new SetCardInfo("Wishclaw Talisman", 51, Rarity.RARE, mage.cards.w.WishclawTalisman.class)); cards.add(new SetCardInfo("Worst Fears", 52, Rarity.MYTHIC, mage.cards.w.WorstFears.class)); + cards.add(new SetCardInfo("Wowzer, the Aspirational", 365, Rarity.RARE, mage.cards.w.WowzerTheAspirational.class)); cards.add(new SetCardInfo("Xantcha, Sleeper Agent", 253, Rarity.RARE, mage.cards.x.XantchaSleeperAgent.class)); cards.add(new SetCardInfo("Yorion, Sky Nomad", 94, Rarity.RARE, mage.cards.y.YorionSkyNomad.class)); cards.add(new SetCardInfo("Zombie Master", 188, Rarity.RARE, mage.cards.z.ZombieMaster.class)); From 7bf02e591e868a69f27d75356ef27ef017084106 Mon Sep 17 00:00:00 2001 From: Dunkoro Date: Mon, 16 Sep 2024 00:28:03 +0200 Subject: [PATCH 45/62] Update painlands to use better mana abilities (#12871) --- Mage.Sets/src/mage/cards/a/AdarkarWastes.java | 10 +++------- Mage.Sets/src/mage/cards/b/BattlefieldForge.java | 10 +++------- Mage.Sets/src/mage/cards/b/Brushland.java | 10 +++------- Mage.Sets/src/mage/cards/c/CavesOfKoilos.java | 10 +++------- Mage.Sets/src/mage/cards/k/KarplusanForest.java | 10 +++------- Mage.Sets/src/mage/cards/l/LlanowarWastes.java | 10 +++------- Mage.Sets/src/mage/cards/s/ShivanReef.java | 10 +++------- Mage.Sets/src/mage/cards/s/SulfurousSprings.java | 10 +++------- Mage.Sets/src/mage/cards/u/UndergroundRiver.java | 10 +++------- Mage.Sets/src/mage/cards/y/YavimayaCoast.java | 10 +++------- 10 files changed, 30 insertions(+), 70 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/AdarkarWastes.java b/Mage.Sets/src/mage/cards/a/AdarkarWastes.java index 0c13c73880f..d9cf3d1c2c1 100644 --- a/Mage.Sets/src/mage/cards/a/AdarkarWastes.java +++ b/Mage.Sets/src/mage/cards/a/AdarkarWastes.java @@ -2,16 +2,12 @@ package mage.cards.a; import java.util.UUID; -import mage.Mana; import mage.abilities.Ability; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageControllerEffect; -import mage.abilities.mana.ColorlessManaAbility; -import mage.abilities.mana.SimpleManaAbility; +import mage.abilities.mana.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; /** * @@ -24,10 +20,10 @@ public final class AdarkarWastes extends CardImpl { this.addAbility(new ColorlessManaAbility()); - Ability whiteManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.WhiteMana(1), new TapSourceCost()); + Ability whiteManaAbility = new WhiteManaAbility(); whiteManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(whiteManaAbility); - Ability blueManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.BlueMana(1), new TapSourceCost()); + Ability blueManaAbility = new BlueManaAbility(); blueManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(blueManaAbility); } diff --git a/Mage.Sets/src/mage/cards/b/BattlefieldForge.java b/Mage.Sets/src/mage/cards/b/BattlefieldForge.java index 7584beb7072..0dd56484708 100644 --- a/Mage.Sets/src/mage/cards/b/BattlefieldForge.java +++ b/Mage.Sets/src/mage/cards/b/BattlefieldForge.java @@ -2,16 +2,12 @@ package mage.cards.b; import java.util.UUID; -import mage.Mana; import mage.abilities.Ability; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageControllerEffect; -import mage.abilities.mana.ColorlessManaAbility; -import mage.abilities.mana.SimpleManaAbility; +import mage.abilities.mana.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; /** * @@ -26,10 +22,10 @@ public final class BattlefieldForge extends CardImpl { this.addAbility(new ColorlessManaAbility()); // Tap: Add Red or White. Battlefield Forge deals 1 damage to you. - Ability redManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.RedMana(1), new TapSourceCost()); + Ability redManaAbility = new RedManaAbility(); redManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(redManaAbility); - Ability whiteManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.WhiteMana(1), new TapSourceCost()); + Ability whiteManaAbility = new WhiteManaAbility(); whiteManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(whiteManaAbility); } diff --git a/Mage.Sets/src/mage/cards/b/Brushland.java b/Mage.Sets/src/mage/cards/b/Brushland.java index 2ab08216ab7..52665bc928d 100644 --- a/Mage.Sets/src/mage/cards/b/Brushland.java +++ b/Mage.Sets/src/mage/cards/b/Brushland.java @@ -2,16 +2,12 @@ package mage.cards.b; import java.util.UUID; -import mage.Mana; import mage.abilities.Ability; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageControllerEffect; -import mage.abilities.mana.ColorlessManaAbility; -import mage.abilities.mana.SimpleManaAbility; +import mage.abilities.mana.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; /** * @@ -23,10 +19,10 @@ public final class Brushland extends CardImpl { super(ownerId,setInfo,new CardType[]{CardType.LAND},""); this.addAbility(new ColorlessManaAbility()); - Ability greenManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.GreenMana(1), new TapSourceCost()); + Ability greenManaAbility = new GreenManaAbility(); greenManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(greenManaAbility); - Ability whiteManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.WhiteMana(1), new TapSourceCost()); + Ability whiteManaAbility = new WhiteManaAbility(); whiteManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(whiteManaAbility); } diff --git a/Mage.Sets/src/mage/cards/c/CavesOfKoilos.java b/Mage.Sets/src/mage/cards/c/CavesOfKoilos.java index 6f01d71e28f..98f8f1b573d 100644 --- a/Mage.Sets/src/mage/cards/c/CavesOfKoilos.java +++ b/Mage.Sets/src/mage/cards/c/CavesOfKoilos.java @@ -2,16 +2,12 @@ package mage.cards.c; import java.util.UUID; -import mage.Mana; import mage.abilities.Ability; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageControllerEffect; -import mage.abilities.mana.ColorlessManaAbility; -import mage.abilities.mana.SimpleManaAbility; +import mage.abilities.mana.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; /** * @@ -27,10 +23,10 @@ public final class CavesOfKoilos extends CardImpl { this.addAbility(new ColorlessManaAbility()); // Tap: Add White or Black. Caves of Koilos deals 1 damage to you. - Ability whiteManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.WhiteMana(1), new TapSourceCost()); + Ability whiteManaAbility = new WhiteManaAbility(); whiteManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(whiteManaAbility); - Ability blackManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.BlackMana(1), new TapSourceCost()); + Ability blackManaAbility = new BlackManaAbility(); blackManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(blackManaAbility); } diff --git a/Mage.Sets/src/mage/cards/k/KarplusanForest.java b/Mage.Sets/src/mage/cards/k/KarplusanForest.java index 83d208f6ee4..5bd70c249c9 100644 --- a/Mage.Sets/src/mage/cards/k/KarplusanForest.java +++ b/Mage.Sets/src/mage/cards/k/KarplusanForest.java @@ -2,16 +2,12 @@ package mage.cards.k; import java.util.UUID; -import mage.Mana; import mage.abilities.Ability; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageControllerEffect; -import mage.abilities.mana.ColorlessManaAbility; -import mage.abilities.mana.SimpleManaAbility; +import mage.abilities.mana.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; /** * @@ -24,10 +20,10 @@ public final class KarplusanForest extends CardImpl { this.addAbility(new ColorlessManaAbility()); - Ability redManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.RedMana(1), new TapSourceCost()); + Ability redManaAbility = new RedManaAbility(); redManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(redManaAbility); - Ability greenManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.GreenMana(1), new TapSourceCost()); + Ability greenManaAbility = new GreenManaAbility(); greenManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(greenManaAbility); } diff --git a/Mage.Sets/src/mage/cards/l/LlanowarWastes.java b/Mage.Sets/src/mage/cards/l/LlanowarWastes.java index 04144789282..ee849bea349 100644 --- a/Mage.Sets/src/mage/cards/l/LlanowarWastes.java +++ b/Mage.Sets/src/mage/cards/l/LlanowarWastes.java @@ -2,16 +2,12 @@ package mage.cards.l; import java.util.UUID; -import mage.Mana; import mage.abilities.Ability; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageControllerEffect; -import mage.abilities.mana.ColorlessManaAbility; -import mage.abilities.mana.SimpleManaAbility; +import mage.abilities.mana.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; /** * @@ -26,10 +22,10 @@ public final class LlanowarWastes extends CardImpl { this.addAbility(new ColorlessManaAbility()); // Tap: Add Black or Green. Llanowar Wastes deals 1 damage to you. - Ability blackManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.BlackMana(1), new TapSourceCost()); + Ability blackManaAbility = new BlackManaAbility(); blackManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(blackManaAbility); - Ability greenManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.GreenMana(1), new TapSourceCost()); + Ability greenManaAbility = new GreenManaAbility(); greenManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(greenManaAbility); } diff --git a/Mage.Sets/src/mage/cards/s/ShivanReef.java b/Mage.Sets/src/mage/cards/s/ShivanReef.java index 11fde3e7365..b3c014c37c2 100644 --- a/Mage.Sets/src/mage/cards/s/ShivanReef.java +++ b/Mage.Sets/src/mage/cards/s/ShivanReef.java @@ -2,16 +2,12 @@ package mage.cards.s; import java.util.UUID; -import mage.Mana; import mage.abilities.Ability; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageControllerEffect; -import mage.abilities.mana.ColorlessManaAbility; -import mage.abilities.mana.SimpleManaAbility; +import mage.abilities.mana.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; /** * @@ -26,10 +22,10 @@ public final class ShivanReef extends CardImpl { this.addAbility(new ColorlessManaAbility()); //Tap: Add Blue or Red. Shivan Reef deals 1 damage to you. - Ability blueManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.BlueMana(1), new TapSourceCost()); + Ability blueManaAbility = new BlueManaAbility(); blueManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(blueManaAbility); - Ability redManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.RedMana(1), new TapSourceCost()); + Ability redManaAbility = new RedManaAbility(); redManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(redManaAbility); } diff --git a/Mage.Sets/src/mage/cards/s/SulfurousSprings.java b/Mage.Sets/src/mage/cards/s/SulfurousSprings.java index 6041c5e6d95..65f7b051395 100644 --- a/Mage.Sets/src/mage/cards/s/SulfurousSprings.java +++ b/Mage.Sets/src/mage/cards/s/SulfurousSprings.java @@ -2,16 +2,12 @@ package mage.cards.s; import java.util.UUID; -import mage.Mana; import mage.abilities.Ability; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageControllerEffect; -import mage.abilities.mana.ColorlessManaAbility; -import mage.abilities.mana.SimpleManaAbility; +import mage.abilities.mana.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; /** * @@ -24,10 +20,10 @@ public final class SulfurousSprings extends CardImpl { this.addAbility(new ColorlessManaAbility()); - Ability blackManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.BlackMana(1), new TapSourceCost()); + Ability blackManaAbility = new BlackManaAbility(); blackManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(blackManaAbility); - Ability redManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.RedMana(1), new TapSourceCost()); + Ability redManaAbility = new RedManaAbility(); redManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(redManaAbility); } diff --git a/Mage.Sets/src/mage/cards/u/UndergroundRiver.java b/Mage.Sets/src/mage/cards/u/UndergroundRiver.java index 850cc79b3b3..d0067b0b09e 100644 --- a/Mage.Sets/src/mage/cards/u/UndergroundRiver.java +++ b/Mage.Sets/src/mage/cards/u/UndergroundRiver.java @@ -2,16 +2,12 @@ package mage.cards.u; import java.util.UUID; -import mage.Mana; import mage.abilities.Ability; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageControllerEffect; -import mage.abilities.mana.ColorlessManaAbility; -import mage.abilities.mana.SimpleManaAbility; +import mage.abilities.mana.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; /** * @@ -24,10 +20,10 @@ public final class UndergroundRiver extends CardImpl { this.addAbility(new ColorlessManaAbility()); - Ability blueManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.BlueMana(1), new TapSourceCost()); + Ability blueManaAbility = new BlueManaAbility(); blueManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(blueManaAbility); - Ability blackManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.BlackMana(1), new TapSourceCost()); + Ability blackManaAbility = new BlackManaAbility(); blackManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(blackManaAbility); } diff --git a/Mage.Sets/src/mage/cards/y/YavimayaCoast.java b/Mage.Sets/src/mage/cards/y/YavimayaCoast.java index 9ce66236ce0..d44530df603 100644 --- a/Mage.Sets/src/mage/cards/y/YavimayaCoast.java +++ b/Mage.Sets/src/mage/cards/y/YavimayaCoast.java @@ -2,16 +2,12 @@ package mage.cards.y; import java.util.UUID; -import mage.Mana; import mage.abilities.Ability; -import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.DamageControllerEffect; -import mage.abilities.mana.ColorlessManaAbility; -import mage.abilities.mana.SimpleManaAbility; +import mage.abilities.mana.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Zone; /** * @@ -26,10 +22,10 @@ public final class YavimayaCoast extends CardImpl { this.addAbility(new ColorlessManaAbility()); // Tap: Add Green or Blue. Yavimaya Coast deals 1 damage to you. - Ability greenManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.GreenMana(1), new TapSourceCost()); + Ability greenManaAbility = new GreenManaAbility(); greenManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(greenManaAbility); - Ability blueManaAbility = new SimpleManaAbility(Zone.BATTLEFIELD, Mana.BlueMana(1), new TapSourceCost()); + Ability blueManaAbility = new BlueManaAbility(); blueManaAbility.addEffect(new DamageControllerEffect(1)); this.addAbility(blueManaAbility); } From 52fdca7cba64b4a7e11ba06304003b8d2cd10446 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sun, 15 Sep 2024 18:42:30 -0400 Subject: [PATCH 46/62] fix #12840 (Impostor Mech ability optional) --- Mage.Sets/src/mage/cards/i/ImposterMech.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mage.Sets/src/mage/cards/i/ImposterMech.java b/Mage.Sets/src/mage/cards/i/ImposterMech.java index e9f72d35ea8..032a5d3ae73 100644 --- a/Mage.Sets/src/mage/cards/i/ImposterMech.java +++ b/Mage.Sets/src/mage/cards/i/ImposterMech.java @@ -41,7 +41,7 @@ public final class ImposterMech extends CardImpl { // You may have Imposter Mech enter the battlefield as a copy of a creature an opponent controls, except its a Vehicle artifact with crew 3 and it loses all other card types. this.addAbility(new EntersBattlefieldAbility( - new CopyPermanentEffect(StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE, applier), + new CopyPermanentEffect(StaticFilters.FILTER_OPPONENTS_PERMANENT_A_CREATURE, applier), true, null, "You may have {this} enter the battlefield as a copy of a creature " + "an opponent controls, except it's a Vehicle artifact with crew 3 and it loses all other card types.", null )); From d052ab45c61645cd204afecb11cd1c09a1eef48a Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sun, 15 Sep 2024 18:51:43 -0400 Subject: [PATCH 47/62] fix #12774 (wrong predicate in Leonardo Da Vinci) --- Mage.Sets/src/mage/cards/l/LeonardoDaVinci.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Mage.Sets/src/mage/cards/l/LeonardoDaVinci.java b/Mage.Sets/src/mage/cards/l/LeonardoDaVinci.java index 9efd6d8f5b0..e29391f7e08 100644 --- a/Mage.Sets/src/mage/cards/l/LeonardoDaVinci.java +++ b/Mage.Sets/src/mage/cards/l/LeonardoDaVinci.java @@ -1,8 +1,6 @@ package mage.cards.l; -import java.util.UUID; import mage.MageInt; -import mage.ObjectColor; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.common.TapSourceCost; @@ -14,14 +12,16 @@ import mage.abilities.effects.common.CreateTokenCopyTargetEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.Card; -import mage.constants.*; import mage.cards.CardImpl; import mage.cards.CardSetInfo; +import mage.constants.*; import mage.filter.common.FilterCreaturePermanent; import mage.game.Game; import mage.players.Player; import mage.target.targetpointer.FixedTarget; +import java.util.UUID; + /** * @author grimreap124 */ @@ -31,7 +31,7 @@ public final class LeonardoDaVinci extends CardImpl { static { filter.add(SubType.THOPTER.getPredicate()); - filter.add(TargetController.YOU.getPlayerPredicate()); + filter.add(TargetController.YOU.getControllerPredicate()); } public LeonardoDaVinci(UUID ownerId, CardSetInfo setInfo) { @@ -112,4 +112,4 @@ class LeonardoDaVinciEffect extends OneShotEffect { return true; } -} \ No newline at end of file +} From 842fa90e7ecbf0e421ab6d9ef913db4c184b4251 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sun, 15 Sep 2024 20:58:43 -0400 Subject: [PATCH 48/62] fix #12867 (Devouring Hellion) by refactoring to use DevourEffect test added --- Mage.Sets/src/mage/cards/c/Caprichrome.java | 4 +- .../src/mage/cards/d/DevouringHellion.java | 71 ++------- .../src/mage/cards/f/FeastingHobbit.java | 1 - .../mage/cards/t/ThromokTheInsatiable.java | 3 +- .../cards/abilities/keywords/DevourTest.java | 144 +++++++++++------- .../effects/common/DevourEffect.java | 18 +-- .../mage/abilities/keyword/DevourAbility.java | 9 +- 7 files changed, 116 insertions(+), 134 deletions(-) diff --git a/Mage.Sets/src/mage/cards/c/Caprichrome.java b/Mage.Sets/src/mage/cards/c/Caprichrome.java index c520ee2b8fc..c615d62baf4 100644 --- a/Mage.Sets/src/mage/cards/c/Caprichrome.java +++ b/Mage.Sets/src/mage/cards/c/Caprichrome.java @@ -8,8 +8,8 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; +import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledArtifactPermanent; -import mage.filter.common.FilterControlledPermanent; import java.util.UUID; @@ -18,7 +18,7 @@ import java.util.UUID; */ public final class Caprichrome extends CardImpl { - private static final FilterControlledPermanent filter = new FilterControlledArtifactPermanent("artifact"); + private static final FilterPermanent filter = new FilterControlledArtifactPermanent("artifact"); public Caprichrome(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{3}{W}"); diff --git a/Mage.Sets/src/mage/cards/d/DevouringHellion.java b/Mage.Sets/src/mage/cards/d/DevouringHellion.java index b9eae227c87..dfe1f3087f7 100644 --- a/Mage.Sets/src/mage/cards/d/DevouringHellion.java +++ b/Mage.Sets/src/mage/cards/d/DevouringHellion.java @@ -1,24 +1,14 @@ package mage.cards.d; import mage.MageInt; -import mage.abilities.Ability; -import mage.abilities.common.AsEntersBattlefieldAbility; -import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.abilities.common.SimpleStaticAbility; +import mage.abilities.effects.common.DevourEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; -import mage.constants.Outcome; import mage.constants.SubType; -import mage.counters.CounterType; -import mage.filter.FilterPermanent; -import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.Predicates; -import mage.game.Game; -import mage.game.permanent.Permanent; -import mage.players.Player; -import mage.target.Target; -import mage.target.common.TargetSacrifice; +import mage.constants.Zone; +import mage.filter.StaticFilters; import java.util.UUID; @@ -35,7 +25,11 @@ public final class DevouringHellion extends CardImpl { this.toughness = new MageInt(2); // As Devouring Hellion enters the battlefield, you may sacrifice any number of creatures and/or planeswalkers. If you do, it enters with twice that many +1/+1 counters on it. - this.addAbility(new AsEntersBattlefieldAbility(new DevouringHellionEffect())); + this.addAbility(new SimpleStaticAbility(Zone.ALL, + new DevourEffect(2, StaticFilters.FILTER_CONTROLLED_PERMANENT_CREATURE_OR_PLANESWALKER) + .setText("As {this} enters the battlefield, you may sacrifice any number of creatures and/or planeswalkers." + + " If you do, it enters with twice that many +1/+1 counters on it") + )); } private DevouringHellion(final DevouringHellion card) { @@ -47,50 +41,3 @@ public final class DevouringHellion extends CardImpl { return new DevouringHellion(this); } } - -class DevouringHellionEffect extends OneShotEffect { - - private static final FilterPermanent filter = new FilterControlledPermanent("creatures and/or planeswalkers"); - - static { - filter.add(Predicates.or( - CardType.CREATURE.getPredicate(), - CardType.PLANESWALKER.getPredicate() - )); - } - - DevouringHellionEffect() { - super(Outcome.Benefit); - staticText = "you may sacrifice any number of creatures and/or planeswalkers. " + - "If you do, it enters with twice that many +1/+1 counters on it."; - } - - private DevouringHellionEffect(final DevouringHellionEffect effect) { - super(effect); - } - - @Override - public DevouringHellionEffect copy() { - return new DevouringHellionEffect(this); - } - - @Override - public boolean apply(Game game, Ability source) { - Player player = game.getPlayer(source.getControllerId()); - if (player == null) { - return false; - } - Target target = new TargetSacrifice(0, Integer.MAX_VALUE, filter); - if (!player.choose(Outcome.Sacrifice, target, source, game)) { - return false; - } - int xValue = 0; - for (UUID targetId : target.getTargets()) { - Permanent permanent = game.getPermanent(targetId); - if (permanent != null && permanent.sacrifice(source, game)) { - xValue++; - } - } - return new AddCountersSourceEffect(CounterType.P1P1.createInstance(2 * xValue)).apply(game, source); - } -} diff --git a/Mage.Sets/src/mage/cards/f/FeastingHobbit.java b/Mage.Sets/src/mage/cards/f/FeastingHobbit.java index c909795dfe7..a326576dbd2 100644 --- a/Mage.Sets/src/mage/cards/f/FeastingHobbit.java +++ b/Mage.Sets/src/mage/cards/f/FeastingHobbit.java @@ -14,7 +14,6 @@ import mage.filter.StaticFilters; import java.util.UUID; /** - * * @author Susucr */ public final class FeastingHobbit extends CardImpl { diff --git a/Mage.Sets/src/mage/cards/t/ThromokTheInsatiable.java b/Mage.Sets/src/mage/cards/t/ThromokTheInsatiable.java index b7a13b6d3f6..87373defd90 100644 --- a/Mage.Sets/src/mage/cards/t/ThromokTheInsatiable.java +++ b/Mage.Sets/src/mage/cards/t/ThromokTheInsatiable.java @@ -1,4 +1,3 @@ - package mage.cards.t; import mage.MageInt; @@ -26,7 +25,7 @@ public final class ThromokTheInsatiable extends CardImpl { this.toughness = new MageInt(0); // Devour X, where X is the number of creatures devoured this way (As this enters the battlefield, you may sacrifice any number of creatures. This creature enters the battlefield with X +1/+1 counters on it for each of those creatures.) - this.addAbility(DevourAbility.DevourX()); + this.addAbility(DevourAbility.devourX()); } private ThromokTheInsatiable(final ThromokTheInsatiable card) { diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DevourTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DevourTest.java index c2687ac93ed..664549309c9 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DevourTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/abilities/keywords/DevourTest.java @@ -1,4 +1,3 @@ - package org.mage.test.cards.abilities.keywords; import mage.abilities.keyword.FlyingAbility; @@ -9,6 +8,8 @@ import mage.counters.CounterType; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; +import java.util.Objects; + /** * 702.82. Devour *

@@ -27,9 +28,11 @@ public class DevourTest extends CardTestPlayerBase { String devourTargets, int assertCounter, boolean assertLion, - boolean assertMyr, + boolean assertGolem, boolean assertGinger, - boolean assertRelic + boolean assertRelic, + boolean assertAngrath, + int life ) { setStrictChooseMode(true); @@ -46,17 +49,21 @@ public class DevourTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, devourer); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); // Creature - addCard(Zone.BATTLEFIELD, playerA, "Alpha Myr"); // Creature Artifact + addCard(Zone.BATTLEFIELD, playerA, "Enatu Golem"); // Artifact Creature - gain 4 life on death addCard(Zone.BATTLEFIELD, playerA, "Gingerbrute"); // Artifact Creature — Food Golem addCard(Zone.BATTLEFIELD, playerA, "Darksteel Relic"); // Artifact + addCard(Zone.BATTLEFIELD, playerA, "Angrath, Captain of Chaos"); // Planeswalker castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, devourer); - if (devourTargets == "") { + if (Objects.equals(devourTargets, "")) { setChoice(playerA, false); // no to devour } else { setChoice(playerA, true); // yes to devour addTarget(playerA, devourTargets); // devour targets. } + if (!assertGolem && devourer.equals("Marrow Chomper")) { + setChoice(playerA, "When {this} dies, you gain 4 life"); // order triggers + } setStopAt(1, PhaseStep.BEGIN_COMBAT); execute(); @@ -70,12 +77,15 @@ public class DevourTest extends CardTestPlayerBase { assertPermanentCount(playerA, "Silvercoat Lion", assertLion ? 1 : 0); assertGraveyardCount(playerA, "Silvercoat Lion", assertLion ? 0 : 1); - assertPermanentCount(playerA, "Alpha Myr", assertMyr ? 1 : 0); - assertGraveyardCount(playerA, "Alpha Myr", assertMyr ? 0 : 1); + assertPermanentCount(playerA, "Enatu Golem", assertGolem ? 1 : 0); + assertGraveyardCount(playerA, "Enatu Golem", assertGolem ? 0 : 1); assertPermanentCount(playerA, "Gingerbrute", assertGinger ? 1 : 0); assertGraveyardCount(playerA, "Gingerbrute", assertGinger ? 0 : 1); assertPermanentCount(playerA, "Darksteel Relic", assertRelic ? 1 : 0); assertGraveyardCount(playerA, "Darksteel Relic", assertRelic ? 0 : 1); + assertPermanentCount(playerA, "Angrath, Captain of Chaos", assertAngrath ? 1 : 0); + assertGraveyardCount(playerA, "Angrath, Captain of Chaos", assertAngrath ? 0 : 1); + assertLife(playerA, life); } private void expectedIllegalTest( @@ -97,7 +107,7 @@ public class DevourTest extends CardTestPlayerBase { addCard(Zone.HAND, playerA, devourer); addCard(Zone.BATTLEFIELD, playerA, "Silvercoat Lion"); // Creature - addCard(Zone.BATTLEFIELD, playerA, "Alpha Myr"); // Creature Artifact + addCard(Zone.BATTLEFIELD, playerA, "Enatu Golem"); // Creature Artifact addCard(Zone.BATTLEFIELD, playerA, "Gingerbrute"); // Artifact Creature — Food Golem addCard(Zone.BATTLEFIELD, playerA, "Darksteel Relic"); // Artifact @@ -130,25 +140,25 @@ public class DevourTest extends CardTestPlayerBase { @Test public void Wurm_NoDevour() { expectedPossibleTest(gorgerWurm, "", - 1 * 0, true, true, true, true); + 1 * 0, true, true, true, true, true, 20); } @Test public void Wurm_OneDevour() { - expectedPossibleTest(gorgerWurm, "Alpha Myr", - 1 * 1, true, false, true, true); + expectedPossibleTest(gorgerWurm, "Enatu Golem", + 1 * 1, true, false, true, true, true, 24); } @Test public void Wurm_TwoDevour() { - expectedPossibleTest(gorgerWurm, "Alpha Myr^Gingerbrute", - 1 * 2, true, false, false, true); + expectedPossibleTest(gorgerWurm, "Enatu Golem^Gingerbrute", + 1 * 2, true, false, false, true, true, 24); } @Test public void Wurm_ThreeDevour() { - expectedPossibleTest(gorgerWurm, "Alpha Myr^Gingerbrute^Silvercoat Lion", - 1 * 3, false, false, false, true); + expectedPossibleTest(gorgerWurm, "Enatu Golem^Gingerbrute^Silvercoat Lion", + 1 * 3, false, false, false, true, true, 24); } @Test @@ -168,25 +178,25 @@ public class DevourTest extends CardTestPlayerBase { @Test public void Thromok_NoDevour() { expectedPossibleTest(thromok, "", - 0 * 0, true, true, true, true); + 0 * 0, true, true, true, true, true, 20); } @Test public void Thromok_OneDevour() { - expectedPossibleTest(thromok, "Alpha Myr", - 1 * 1, true, false, true, true); + expectedPossibleTest(thromok, "Enatu Golem", + 1 * 1, true, false, true, true, true, 24); } @Test public void Thromok_TwoDevour() { - expectedPossibleTest(thromok, "Alpha Myr^Gingerbrute", - 2 * 2, true, false, false, true); + expectedPossibleTest(thromok, "Enatu Golem^Gingerbrute", + 2 * 2, true, false, false, true, true, 24); } @Test public void Thromok_ThreeDevour() { - expectedPossibleTest(thromok, "Alpha Myr^Gingerbrute^Silvercoat Lion", - 3 * 3, false, false, false, true); + expectedPossibleTest(thromok, "Enatu Golem^Gingerbrute^Silvercoat Lion", + 3 * 3, false, false, false, true, true, 24); } @Test @@ -206,18 +216,18 @@ public class DevourTest extends CardTestPlayerBase { @Test public void Hobbit_NoDevour() { expectedPossibleTest(hobbit, "", - 3 * 0, true, true, true, true); + 3 * 0, true, true, true, true, true, 20); } @Test public void Hobbit_OneDevour() { expectedPossibleTest(hobbit, "Gingerbrute", - 3 * 1, true, true, false, true); + 3 * 1, true, true, false, true, true, 20); } @Test public void Hobbit_IllegalDevour() { - expectedIllegalTest(hobbit, "Alpha Myr"); + expectedIllegalTest(hobbit, "Enatu Golem"); } // Caprichrome @@ -236,25 +246,25 @@ public class DevourTest extends CardTestPlayerBase { @Test public void Caprichrome_NoDevour() { expectedPossibleTest(caprichrome, "", - 1 * 0, true, true, true, true); + 1 * 0, true, true, true, true, true, 20); } @Test public void Caprichrome_OneDevour() { - expectedPossibleTest(caprichrome, "Alpha Myr", - 1 * 1, true, false, true, true); + expectedPossibleTest(caprichrome, "Enatu Golem", + 1 * 1, true, false, true, true, true, 24); } @Test public void Caprichrome_TwoDevour() { - expectedPossibleTest(caprichrome, "Alpha Myr^Gingerbrute", - 1 * 2, true, false, false, true); + expectedPossibleTest(caprichrome, "Enatu Golem^Gingerbrute", + 1 * 2, true, false, false, true, true, 24); } @Test public void Caprichrome_ThreeDevour() { - expectedPossibleTest(caprichrome, "Alpha Myr^Gingerbrute^Darksteel Relic", - 1 * 3, true, false, false, false); + expectedPossibleTest(caprichrome, "Enatu Golem^Gingerbrute^Darksteel Relic", + 1 * 3, true, false, false, false, true, 24); } @Test @@ -276,31 +286,31 @@ public class DevourTest extends CardTestPlayerBase { @Test public void Hatchling_NoDevour() { expectedPossibleTest(hatchling, "", - 1 * 0, true, true, true, true); + 1 * 0, true, true, true, true, true, 20); assertAbility(playerA, hatchling, FlyingAbility.getInstance(), false); assertAbility(playerA, hatchling, TrampleAbility.getInstance(), false); } @Test public void Hatchling_OneDevour() { - expectedPossibleTest(hatchling, "Alpha Myr", - 1 * 1, true, false, true, true); + expectedPossibleTest(hatchling, "Enatu Golem", + 1 * 1, true, false, true, true, true, 24); assertAbility(playerA, hatchling, FlyingAbility.getInstance(), true); assertAbility(playerA, hatchling, TrampleAbility.getInstance(), true); } @Test public void Hatchling_TwoDevour() { - expectedPossibleTest(hatchling, "Alpha Myr^Gingerbrute", - 1 * 2, true, false, false, true); + expectedPossibleTest(hatchling, "Enatu Golem^Gingerbrute", + 1 * 2, true, false, false, true, true, 24); assertAbility(playerA, hatchling, FlyingAbility.getInstance(), true); assertAbility(playerA, hatchling, TrampleAbility.getInstance(), true); } @Test public void Hatchling_ThreeDevour() { - expectedPossibleTest(hatchling, "Alpha Myr^Gingerbrute^Silvercoat Lion", - 1 * 3, false, false, false, true); + expectedPossibleTest(hatchling, "Enatu Golem^Gingerbrute^Silvercoat Lion", + 1 * 3, false, false, false, true, true, 24); assertAbility(playerA, hatchling, FlyingAbility.getInstance(), true); assertAbility(playerA, hatchling, TrampleAbility.getInstance(), true); } @@ -322,33 +332,65 @@ public class DevourTest extends CardTestPlayerBase { @Test public void Chomper_NoDevour() { expectedPossibleTest(chomper, "", - 2 * 0, true, true, true, true); - assertLife(playerA, 20 + 2 * 0); + 2 * 0, true, true, true, true, true, 20); } @Test public void Chomper_OneDevour() { - expectedPossibleTest(chomper, "Alpha Myr", - 2 * 1, true, false, true, true); - assertLife(playerA, 20 + 2 * 1); + expectedPossibleTest(chomper, "Enatu Golem", + 2 * 1, true, false, true, true, true, 26); } @Test public void Chomper_TwoDevour() { - expectedPossibleTest(chomper, "Alpha Myr^Gingerbrute", - 2 * 2, true, false, false, true); - assertLife(playerA, 20 + 2 * 2); + expectedPossibleTest(chomper, "Enatu Golem^Gingerbrute", + 2 * 2, true, false, false, true, true, 28); } @Test public void Chomper_ThreeDevour() { - expectedPossibleTest(chomper, "Alpha Myr^Gingerbrute^Silvercoat Lion", - 2 * 3, false, false, false, true); - assertLife(playerA, 20 + 2 * 3); + expectedPossibleTest(chomper, "Enatu Golem^Gingerbrute^Silvercoat Lion", + 2 * 3, false, false, false, true, true, 30); } @Test public void Chomper_IllegalDevour() { expectedIllegalTest(chomper, "Darksteel Relic"); } -} \ No newline at end of file + + // Devouring Hellion {2}{R} + // Creature — Hellion + // As Devouring Hellion enters, you may sacrifice any number of creatures and/or planeswalkers. + // If you do, it enters with twice that many +1/+1 counters on it. + private static final String hellion = "Devouring Hellion"; + + @Test + public void hellionNoDevour() { + expectedPossibleTest(hellion, "", + 0, true, true, true, true, true, 20); + } + + @Test + public void hellionOneDevour() { + expectedPossibleTest(hellion, "Angrath, Captain of Chaos", + 2, true, true, true, true, false, 20); + } + + @Test + public void hellionTwoDevour() { + expectedPossibleTest(hellion, "Enatu Golem^Angrath, Captain of Chaos", + 4, true, false, true, true, false, 24); + } + + @Test + public void hellionThreeDevour() { + expectedPossibleTest(hellion, "Enatu Golem^Gingerbrute^Angrath, Captain of Chaos", + 6, true, false, false, true, false, 24); + } + + @Test + public void hellionIllegalDevour() { + expectedIllegalTest(hellion, "Darksteel Relic"); + } + +} diff --git a/Mage/src/main/java/mage/abilities/effects/common/DevourEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DevourEffect.java index b2171b96d90..800c54452b5 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DevourEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DevourEffect.java @@ -6,8 +6,7 @@ import mage.abilities.effects.ReplacementEffectImpl; import mage.constants.Duration; import mage.constants.Outcome; import mage.counters.CounterType; -import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.Predicate; +import mage.filter.FilterPermanent; import mage.filter.predicate.mageobject.AnotherPredicate; import mage.game.Game; import mage.game.events.EntersTheBattlefieldEvent; @@ -45,9 +44,9 @@ public class DevourEffect extends ReplacementEffectImpl { // "creature" is a special case as the rule will not mention it. // // 's' will be added to pluralize, so far so good with the current text generation. - private final FilterControlledPermanent filterDevoured; + private final FilterPermanent filterDevoured; - public DevourEffect(int devourFactor, FilterControlledPermanent filterDevoured) { + public DevourEffect(int devourFactor, FilterPermanent filterDevoured) { super(Duration.EndOfGame, Outcome.Detriment); this.devourFactor = devourFactor; this.filterDevoured = filterDevoured; @@ -81,11 +80,8 @@ public class DevourEffect extends ReplacementEffectImpl { if (creature == null || controller == null) { return false; } - - FilterControlledPermanent filter = new FilterControlledPermanent(filterDevoured.getMessage() + "s to devour"); - for (Predicate predicate : filterDevoured.getPredicates()) { - filter.add(predicate); - } + FilterPermanent filter = filterDevoured.copy(); + filter.setMessage(filterDevoured.getMessage() + "s (to devour)"); filter.add(AnotherPredicate.instance); Target target = new TargetSacrifice(1, Integer.MAX_VALUE, filter); @@ -142,9 +138,9 @@ public class DevourEffect extends ReplacementEffectImpl { text += devourFactor; } - text += " (As this enters the battlefield, you may sacrifice any number of " + text += " (As this enters, you may sacrifice any number of " + filterMessage + "s. " - + "This creature enters the battlefield with "; + + "This creature enters with "; if (devourFactor == Integer.MAX_VALUE) { text += "X +1/+1 counters on it for each of those creatures"; diff --git a/Mage/src/main/java/mage/abilities/keyword/DevourAbility.java b/Mage/src/main/java/mage/abilities/keyword/DevourAbility.java index a3b42f8807a..7f6648bc98b 100644 --- a/Mage/src/main/java/mage/abilities/keyword/DevourAbility.java +++ b/Mage/src/main/java/mage/abilities/keyword/DevourAbility.java @@ -1,11 +1,10 @@ - package mage.abilities.keyword; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.DevourEffect; import mage.constants.Zone; +import mage.filter.FilterPermanent; import mage.filter.common.FilterControlledCreaturePermanent; -import mage.filter.common.FilterControlledPermanent; /** * 502.82. Devour @@ -45,12 +44,12 @@ import mage.filter.common.FilterControlledPermanent; */ public class DevourAbility extends SimpleStaticAbility { - private static final FilterControlledPermanent filterCreature = new FilterControlledCreaturePermanent("creature"); + private static final FilterPermanent filterCreature = new FilterControlledCreaturePermanent("creature"); // Integer.MAX_VALUE is a special value // for "devour X, where X is the number of devored permanents" // see DevourEffect for the full details. - public static DevourAbility DevourX() { + public static DevourAbility devourX() { return new DevourAbility(Integer.MAX_VALUE); } @@ -58,7 +57,7 @@ public class DevourAbility extends SimpleStaticAbility { this(devourFactor, filterCreature); } - public DevourAbility(int devourFactor, FilterControlledPermanent filterDevoured) { + public DevourAbility(int devourFactor, FilterPermanent filterDevoured) { super(Zone.ALL, new DevourEffect(devourFactor, filterDevoured)); } From 0668a9aebca5117cc51d358bea5bf541c986fd40 Mon Sep 17 00:00:00 2001 From: xenohedron Date: Sun, 15 Sep 2024 21:12:38 -0400 Subject: [PATCH 49/62] fix #4207 (Indomitable Creativity) rework to follow game rules for effect order --- .../mage/cards/i/IndomitableCreativity.java | 78 ++++++++++--------- 1 file changed, 43 insertions(+), 35 deletions(-) diff --git a/Mage.Sets/src/mage/cards/i/IndomitableCreativity.java b/Mage.Sets/src/mage/cards/i/IndomitableCreativity.java index 8d2360d22e5..d09f8b15156 100644 --- a/Mage.Sets/src/mage/cards/i/IndomitableCreativity.java +++ b/Mage.Sets/src/mage/cards/i/IndomitableCreativity.java @@ -1,4 +1,3 @@ - package mage.cards.i; import mage.abilities.Ability; @@ -15,9 +14,7 @@ import mage.players.Player; import mage.target.TargetPermanent; import mage.target.targetadjustment.XTargetsCountAdjuster; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import java.util.*; /** * @author LevelX2 @@ -67,37 +64,48 @@ class IndomitableCreativityEffect extends OneShotEffect { @Override public boolean apply(Game game, Ability source) { Player controller = game.getPlayer(source.getControllerId()); - if (controller != null) { - List destroyedPermanents = new ArrayList<>(); - for (UUID targetId : getTargetPointer().getTargets(game, source)) { - Permanent target = game.getPermanent(targetId); - if (target != null) { - if (target.destroy(source, game, false)) { - destroyedPermanents.add(target); - } - } - } - for (Permanent permanent : destroyedPermanents) { - Player controllerOfDestroyedCreature = game.getPlayer(permanent.getControllerId()); - if (controllerOfDestroyedCreature != null) { - Library library = controllerOfDestroyedCreature.getLibrary(); - if (library.hasCards()) { - Cards cardsToReaveal = new CardsImpl(); - for (Card card : library.getCards(game)) { - cardsToReaveal.add(card); - if (card.isCreature(game) || card.isArtifact(game)) { - controllerOfDestroyedCreature.moveCards(card, Zone.EXILED, source, game); - controllerOfDestroyedCreature.moveCards(card, Zone.BATTLEFIELD, source, game); - break; - } - } - controllerOfDestroyedCreature.revealCards(source, " for destroyed " + permanent.getIdName(), cardsToReaveal, game); - controllerOfDestroyedCreature.shuffleLibrary(source, game); - } - } - } - return true; + if (controller == null) { + return false; } - return false; + List destroyedPermanents = new ArrayList<>(); + for (UUID targetId : getTargetPointer().getTargets(game, source)) { + Permanent target = game.getPermanent(targetId); + if (target != null && target.destroy(source, game, false)) { + destroyedPermanents.add(target); + } + } + if (destroyedPermanents.isEmpty()) { + return false; + } + game.processAction(); + Map> playerToBattlefield = new HashMap<>(); + for (Permanent permanent : destroyedPermanents) { + Player controllerOfDestroyedCreature = game.getPlayer(permanent.getControllerId()); + if (controllerOfDestroyedCreature != null) { + Library library = controllerOfDestroyedCreature.getLibrary(); + if (library.hasCards()) { + playerToBattlefield.computeIfAbsent(controllerOfDestroyedCreature.getId(), x -> new HashSet<>()); + Cards cardsToReveal = new CardsImpl(); + for (Card card : library.getCards(game)) { + cardsToReveal.add(card); + if (card.isCreature(game) || card.isArtifact(game)) { + controllerOfDestroyedCreature.moveCards(card, Zone.EXILED, source, game); + playerToBattlefield.computeIfAbsent(controllerOfDestroyedCreature.getId(), x -> new HashSet<>()).add(card); + break; + } + } + controllerOfDestroyedCreature.revealCards(source, " for destroyed " + permanent.getIdName(), cardsToReveal, game); + } + } + } + game.processAction(); + for (Map.Entry> entry: playerToBattlefield.entrySet()) { + Player player = game.getPlayer(entry.getKey()); + if (player != null) { + player.moveCards(entry.getValue(), Zone.BATTLEFIELD, source, game); + player.shuffleLibrary(source, game); + } + } + return true; } } From 8fa44051253ee4c4a5d72998fe4621f791737470 Mon Sep 17 00:00:00 2001 From: Jeff Wadsworth Date: Tue, 17 Sep 2024 11:03:51 -0500 Subject: [PATCH 50/62] Fixed #12620 --- Mage.Sets/src/mage/cards/a/ApexObservatory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/ApexObservatory.java b/Mage.Sets/src/mage/cards/a/ApexObservatory.java index d1d5a0de0bc..6f64347cd7e 100644 --- a/Mage.Sets/src/mage/cards/a/ApexObservatory.java +++ b/Mage.Sets/src/mage/cards/a/ApexObservatory.java @@ -166,7 +166,7 @@ class ApexObservatoryEffect extends ContinuousEffectImpl { Card apexObservatory = game.getCard(source.getSourceId()); if (apexObservatory != null) { Boolean wasItUsed = (Boolean) game.getState().getValue( - apexObservatory.getId().toString()); + apexObservatory.getId().toString() + game.getTurnNum()); if (wasItUsed == null) { ApexObservatoryAlternativeCostAbility alternateCostAbility = new ApexObservatoryAlternativeCostAbility(chosenCardType); alternateCostAbility.setSourceId(source.getSourceId()); @@ -214,7 +214,7 @@ class ApexObservatoryAlternativeCostAbility extends AlternativeCostSourceAbility } Card apexObservatory = game.getCard(this.getSourceId()); if (apexObservatory != null) { - game.getState().setValue(apexObservatory.getId().toString(), true); + game.getState().setValue(apexObservatory.getId().toString() + game.getTurnNum(), true); } return true; } From 91c1d1dc72b6b7bfddead295311dbbbffb182314 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 18 Sep 2024 18:24:35 +0400 Subject: [PATCH 51/62] Target spell abilities - fixed that it was playable in some non-playable use cases (example: Goblin Artisans) --- Mage/src/main/java/mage/target/TargetSpell.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Mage/src/main/java/mage/target/TargetSpell.java b/Mage/src/main/java/mage/target/TargetSpell.java index 439df15989a..4ded8d97a8d 100644 --- a/Mage/src/main/java/mage/target/TargetSpell.java +++ b/Mage/src/main/java/mage/target/TargetSpell.java @@ -1,4 +1,3 @@ - package mage.target; import mage.abilities.Ability; @@ -110,7 +109,7 @@ public class TargetSpell extends TargetObject { private boolean canBeChosen(StackObject stackObject, UUID sourceControllerId, Ability source, Game game) { return stackObject instanceof Spell && game.getState().getPlayersInRange(sourceControllerId, game).contains(stackObject.getControllerId()) - && filter.match(stackObject, sourceControllerId, source, game); + && canTarget(sourceControllerId, stackObject.getId(), source, game); } @Override From e7dc75d5f6af9f86248f3ef9f519352a84600bc7 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Wed, 18 Sep 2024 18:27:17 +0400 Subject: [PATCH 52/62] Boxing Ring - fixed that it allow to select own permanents --- Mage.Sets/src/mage/cards/b/BoxingRing.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BoxingRing.java b/Mage.Sets/src/mage/cards/b/BoxingRing.java index a76732f4f82..84ffb7d9453 100644 --- a/Mage.Sets/src/mage/cards/b/BoxingRing.java +++ b/Mage.Sets/src/mage/cards/b/BoxingRing.java @@ -11,12 +11,10 @@ import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.CreateTokenEffect; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.CardType; -import mage.constants.Outcome; -import mage.constants.WatcherScope; -import mage.constants.Zone; +import mage.constants.*; import mage.filter.FilterPermanent; import mage.filter.StaticFilters; +import mage.filter.common.FilterControlledCreaturePermanent; import mage.filter.common.FilterCreaturePermanent; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; @@ -41,6 +39,7 @@ public final class BoxingRing extends CardImpl { = new FilterCreaturePermanent("creature you don't control with the same mana value"); static { + filter.add(TargetController.NOT_YOU.getControllerPredicate()); filter.add(BoxingRingPredicate.instance); } From 5abf295ba27c6d3ee0095ae98790e7ac449fbff4 Mon Sep 17 00:00:00 2001 From: Jeff Wadsworth Date: Wed, 18 Sep 2024 17:05:58 -0500 Subject: [PATCH 53/62] Refactored Apex Observatory to work correctly with blink effects. --- .../src/mage/cards/a/ApexObservatory.java | 140 +++++++++--------- 1 file changed, 68 insertions(+), 72 deletions(-) diff --git a/Mage.Sets/src/mage/cards/a/ApexObservatory.java b/Mage.Sets/src/mage/cards/a/ApexObservatory.java index 6f64347cd7e..2e6cbf27f24 100644 --- a/Mage.Sets/src/mage/cards/a/ApexObservatory.java +++ b/Mage.Sets/src/mage/cards/a/ApexObservatory.java @@ -5,19 +5,14 @@ import mage.abilities.Ability; import mage.abilities.common.AsEntersBattlefieldAbility; import mage.abilities.common.EntersBattlefieldTappedAbility; import mage.abilities.common.SimpleActivatedAbility; -import mage.abilities.condition.Condition; -import mage.abilities.costs.AlternativeCostSourceAbility; import mage.abilities.costs.common.TapSourceCost; -import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.OneShotEffect; -import mage.abilities.effects.common.AddContinuousEffectToGame; import mage.cards.Card; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.choices.Choice; import mage.choices.ChoiceCardType; import mage.constants.*; -import mage.filter.common.FilterOwnedCard; import mage.game.ExileZone; import mage.game.Game; import mage.game.permanent.Permanent; @@ -26,6 +21,8 @@ import mage.util.CardUtil; import java.util.*; import java.util.stream.Collectors; +import mage.abilities.SpellAbility; +import mage.abilities.effects.common.cost.CostModificationEffectImpl; /** * @author jeffwadsworth @@ -40,11 +37,11 @@ public class ApexObservatory extends CardImpl { // Apex Observatory enters the battlefield tapped. this.addAbility(new EntersBattlefieldTappedAbility()); - // Apex Observatory enters the battlefield tapped. As it enters, choose a card type shared among two exiled cards used to craft it. + // As it enters, choose a card type shared among two exiled cards used to craft it. this.addAbility(new AsEntersBattlefieldAbility(new ChooseCardTypeEffect())); // The next spell you cast this turn of the chosen type can be cast without paying its mana cost. - this.addAbility(new SimpleActivatedAbility(new AddContinuousEffectToGame(new ApexObservatoryEffect()), new TapSourceCost())); + this.addAbility(new SimpleActivatedAbility(new ApexObservatoryEffect(), new TapSourceCost())); } private ApexObservatory(final ApexObservatory card) { @@ -141,10 +138,10 @@ class ChooseCardTypeEffect extends OneShotEffect { } } -class ApexObservatoryEffect extends ContinuousEffectImpl { +class ApexObservatoryEffect extends OneShotEffect { ApexObservatoryEffect() { - super(Duration.EndOfTurn, Outcome.Benefit); + super(Outcome.Benefit); staticText = "The next spell you cast this turn of the chosen type can be cast without paying its mana cost."; } @@ -158,86 +155,85 @@ class ApexObservatoryEffect extends ContinuousEffectImpl { } @Override - public boolean apply(Layer layer, SubLayer sublayer, Ability source, Game game) { - Player controller = game.getPlayer(source.getControllerId()); + public boolean apply(Game game, Ability source) { String chosenCardType = (String) game.getState().getValue("ApexObservatoryType_" + source.getSourceId().toString()); - if (controller != null - && chosenCardType != null) { - Card apexObservatory = game.getCard(source.getSourceId()); - if (apexObservatory != null) { - Boolean wasItUsed = (Boolean) game.getState().getValue( - apexObservatory.getId().toString() + game.getTurnNum()); - if (wasItUsed == null) { - ApexObservatoryAlternativeCostAbility alternateCostAbility = new ApexObservatoryAlternativeCostAbility(chosenCardType); - alternateCostAbility.setSourceId(source.getSourceId()); - controller.getAlternativeSourceCosts().add(alternateCostAbility); + if (chosenCardType == null) { + return false; + } + game.addEffect(new ApexObservatoryCastWithoutManaEffect(chosenCardType, source.getControllerId()), source); + return true; + } +} + +class ApexObservatoryCastWithoutManaEffect extends CostModificationEffectImpl { + + private final String chosenCardType; + private final UUID playerId; + private boolean used = false; + + ApexObservatoryCastWithoutManaEffect(String chosenCardType, UUID playerId) { + super(Duration.EndOfTurn, Outcome.Benefit, CostModificationType.SET_COST); + this.chosenCardType = chosenCardType; + this.playerId = playerId; + staticText = "The next spell you cast this turn of the chosen type can be cast without paying its mana cost"; + } + + private ApexObservatoryCastWithoutManaEffect(final ApexObservatoryCastWithoutManaEffect effect) { + super(effect); + this.chosenCardType = effect.chosenCardType; + this.playerId = effect.playerId; + this.used = effect.used; + } + + @Override + public boolean apply(Game game, Ability source, Ability abilityToModify) { + // Ask the player if they want to use the effect + Player controller = game.getPlayer(playerId); + if (controller != null) { + MageObject spell = abilityToModify.getSourceObject(game); + if (spell != null && !game.isSimulation()) { + String message = "Cast " + spell.getIdName() + " without paying its mana cost?"; + if (controller.chooseUse(Outcome.Benefit, message, source, game)) { + // Set the cost to zero + abilityToModify.getManaCostsToPay().clear(); + // Mark as used + used = true; + game.informPlayers(controller.getLogName() + " casts " + spell.getIdName() + " without paying its mana cost."); + return true; + } else { + // Player chose not to use the effect + return false; } - return true; } } return false; } @Override - public boolean apply(Game game, Ability source) { - return false; + public ApexObservatoryCastWithoutManaEffect copy() { + return new ApexObservatoryCastWithoutManaEffect(this); } @Override - public boolean hasLayer(Layer layer) { - return layer == Layer.RulesEffects; - } -} - -class ApexObservatoryAlternativeCostAbility extends AlternativeCostSourceAbility { - - private boolean wasActivated; - - ApexObservatoryAlternativeCostAbility(String chosenCardType) { - super(new SpellMatchesChosenTypeCondition(chosenCardType), null, new FilterOwnedCard(), true, null); - } - - private ApexObservatoryAlternativeCostAbility(final ApexObservatoryAlternativeCostAbility ability) { - super(ability); - this.wasActivated = ability.wasActivated; + public boolean isInactive(Ability source, Game game) { + return used || super.isInactive(source, game); } @Override - public ApexObservatoryAlternativeCostAbility copy() { - return new ApexObservatoryAlternativeCostAbility(this); - } - - @Override - public boolean activateAlternativeCosts(Ability ability, Game game) { - if (!super.activateAlternativeCosts(ability, game)) { + public boolean applies(Ability ability, Ability source, Game game) { + if (used) { return false; } - Card apexObservatory = game.getCard(this.getSourceId()); - if (apexObservatory != null) { - game.getState().setValue(apexObservatory.getId().toString() + game.getTurnNum(), true); + if (!ability.isControlledBy(playerId)) { + return false; } - return true; - } -} - -class SpellMatchesChosenTypeCondition implements Condition { - - final private String chosenCardType; - - public SpellMatchesChosenTypeCondition(String chosenCardType) { - this.chosenCardType = chosenCardType; - } - - @Override - public boolean apply(Game game, Ability source) { - return checkSpell(game.getObject(source), game, chosenCardType); - } - - public static boolean checkSpell(MageObject spell, Game game, String chosenCardType) { - if (spell instanceof Card) { - Card card = (Card) spell; - return chosenCardType != null - && card.getCardType(game).toString().contains(chosenCardType); + if (!(ability instanceof SpellAbility)) { + return false; + } + MageObject object = game.getObject(ability.getSourceId()); + if (object != null && object.getCardType(game).stream() + .anyMatch(cardType -> cardType.toString().equals(chosenCardType))) { + return true; } return false; } From 3d05eb035b001ed233d78b28e153103c94e973d5 Mon Sep 17 00:00:00 2001 From: Svyatoslav28597 <105356842+Svyatoslav28597@users.noreply.github.com> Date: Thu, 19 Sep 2024 02:09:03 +0300 Subject: [PATCH 54/62] implement [YMID] Hollowhenge Wrangler; Forsaken Crossroads (#12793) --- .../src/mage/cards/f/ForsakenCrossroads.java | 90 +++++++++++++++++++ .../src/mage/cards/h/HollowhengeWrangler.java | 72 +++++++++++++++ Mage.Sets/src/mage/sets/AlchemyInnistrad.java | 2 + Mage.Sets/src/mage/sets/MysteryBooster2.java | 1 + .../ActivateIfConditionActivatedAbility.java | 6 +- 5 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 Mage.Sets/src/mage/cards/f/ForsakenCrossroads.java create mode 100644 Mage.Sets/src/mage/cards/h/HollowhengeWrangler.java diff --git a/Mage.Sets/src/mage/cards/f/ForsakenCrossroads.java b/Mage.Sets/src/mage/cards/f/ForsakenCrossroads.java new file mode 100644 index 00000000000..8d753b46921 --- /dev/null +++ b/Mage.Sets/src/mage/cards/f/ForsakenCrossroads.java @@ -0,0 +1,90 @@ +package mage.cards.f; + +import mage.abilities.Ability; +import mage.abilities.common.AsEntersBattlefieldAbility; +import mage.abilities.common.EntersBattlefieldTappedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.ChooseColorEffect; +import mage.abilities.effects.keyword.ScryEffect; +import mage.abilities.effects.mana.AddManaChosenColorEffect; +import mage.abilities.mana.SimpleManaAbility; +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 java.util.UUID; + +/** + * @author Svyatoslav28 + */ +public final class ForsakenCrossroads extends CardImpl { + + public ForsakenCrossroads(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.LAND}, ""); + + + // Forsaken Crossroads enters tapped. + this.addAbility(new EntersBattlefieldTappedAbility()); + + // As Forsaken Crossroads enters, choose a color. + this.addAbility(new AsEntersBattlefieldAbility(new ChooseColorEffect(Outcome.Neutral))); + + // When Forskaken Crossroads enters, scry 1. If you weren’t the starting player, you may untap Forsaken Crossroads instead. + this.addAbility(new EntersBattlefieldTriggeredAbility(new ForsakenCrossroadsEffect())); + + // {T}: Add one mana of the chosen color. + this.addAbility(new SimpleManaAbility(Zone.BATTLEFIELD, new AddManaChosenColorEffect(), new TapSourceCost())); + } + + private ForsakenCrossroads(final ForsakenCrossroads card) { + super(card); + } + + @Override + public ForsakenCrossroads copy() { + return new ForsakenCrossroads(this); + } +} + +class ForsakenCrossroadsEffect extends OneShotEffect { + + ForsakenCrossroadsEffect() { + super(Outcome.PutCardInPlay); + this.staticText = "scry 1. If you weren't the starting player, you may untap {this} instead."; + } + + private ForsakenCrossroadsEffect(final ForsakenCrossroadsEffect effect) { + super(effect); + } + + @Override + public ForsakenCrossroadsEffect copy() { + return new ForsakenCrossroadsEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller != null && !controller.getId().equals(game.getStartingPlayerId())) { + if (controller.chooseUse(Outcome.Untap, "Untap {this} instead of scrying 1?", "", "Untap", "Scry 1", source, game)) { + Permanent permanent = source.getSourcePermanentIfItStillExists(game); + if (permanent == null) { + return false; + } + permanent.untap(game); + return true; + } + } + OneShotEffect scryEffect = new ScryEffect(1); + scryEffect.apply(game, source); + return true; + } +} + diff --git a/Mage.Sets/src/mage/cards/h/HollowhengeWrangler.java b/Mage.Sets/src/mage/cards/h/HollowhengeWrangler.java new file mode 100644 index 00000000000..1da04370f34 --- /dev/null +++ b/Mage.Sets/src/mage/cards/h/HollowhengeWrangler.java @@ -0,0 +1,72 @@ +package mage.cards.h; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.common.ActivateIfConditionActivatedAbility; +import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.condition.Condition; +import mage.abilities.costs.common.DiscardCardCost; +import mage.abilities.effects.common.ConjureCardEffect; +import mage.abilities.effects.common.SeekCardEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.StaticFilters; +import mage.game.Game; + +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +/** + * @author Svyatoslav28 + */ + +public final class HollowhengeWrangler extends CardImpl { + + public HollowhengeWrangler(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{G}{G}"); + + this.subtype.add(SubType.ELEMENTAL); + this.power = new MageInt(6); + this.toughness = new MageInt(6); + + // When Hollowhenge Wrangler enters the battlefield, seek a land card. + this.addAbility(new EntersBattlefieldTriggeredAbility(new SeekCardEffect(StaticFilters.FILTER_CARD_LAND))); + + // Discard a land card: Conjure a card named Hollowhenge Beast into your hand. + // You may also activate this ability while Hollowhenge Wrangler is in your graveyard. + Ability ability = new ActivateIfConditionActivatedAbility( + Zone.ALL, new ConjureCardEffect("Hollowhenge Beast"), new DiscardCardCost(StaticFilters.FILTER_CARD_LAND), HollowhengeWranglerCondition.instance + ); + this.addAbility(ability); + + } + + + private HollowhengeWrangler(final HollowhengeWrangler card) { + super(card); + } + + @Override + public HollowhengeWrangler copy() { + return new HollowhengeWrangler(this); + } +} + +enum HollowhengeWranglerCondition implements Condition { + instance; + private static final List zones = Arrays.asList(Zone.BATTLEFIELD, Zone.GRAVEYARD); + + @Override + public boolean apply(Game game, Ability source) { + return zones.contains(game.getState().getZone(source.getSourceId())); + } + + @Override + public String toString() { + return "You may also activate this ability while {this} is in your graveyard"; + } +} diff --git a/Mage.Sets/src/mage/sets/AlchemyInnistrad.java b/Mage.Sets/src/mage/sets/AlchemyInnistrad.java index ca5812d8565..793c67a5263 100644 --- a/Mage.Sets/src/mage/sets/AlchemyInnistrad.java +++ b/Mage.Sets/src/mage/sets/AlchemyInnistrad.java @@ -25,6 +25,8 @@ public final class AlchemyInnistrad extends ExpansionSet { cards.add(new SetCardInfo("Cursebound Witch", 24, Rarity.UNCOMMON, mage.cards.c.CurseboundWitch.class)); cards.add(new SetCardInfo("Expedition Supplier", 6, Rarity.RARE, mage.cards.e.ExpeditionSupplier.class)); cards.add(new SetCardInfo("Faithful Disciple", 7, Rarity.UNCOMMON, mage.cards.f.FaithfulDisciple.class)); + cards.add(new SetCardInfo("Forsaken Crossroads", 63, Rarity.RARE, mage.cards.f.ForsakenCrossroads.class)); + cards.add(new SetCardInfo("Hollowhenge Wrangler", 51, Rarity.RARE, mage.cards.h.HollowhengeWrangler.class)); cards.add(new SetCardInfo("Ishkanah, Broodmother", 52, Rarity.MYTHIC, mage.cards.i.IshkanahBroodmother.class)); cards.add(new SetCardInfo("Key to the Archive", 59, Rarity.RARE, mage.cards.k.KeyToTheArchive.class)); cards.add(new SetCardInfo("Kindred Denial", 18, Rarity.UNCOMMON, mage.cards.k.KindredDenial.class)); diff --git a/Mage.Sets/src/mage/sets/MysteryBooster2.java b/Mage.Sets/src/mage/sets/MysteryBooster2.java index b8cdfde2de6..8b1778af764 100644 --- a/Mage.Sets/src/mage/sets/MysteryBooster2.java +++ b/Mage.Sets/src/mage/sets/MysteryBooster2.java @@ -100,6 +100,7 @@ public class MysteryBooster2 extends ExpansionSet { cards.add(new SetCardInfo("Flusterstorm", 163, Rarity.RARE, mage.cards.f.Flusterstorm.class)); cards.add(new SetCardInfo("Foil", 243, Rarity.COMMON, mage.cards.f.Foil.class)); cards.add(new SetCardInfo("Forest Bear", 206, Rarity.COMMON, mage.cards.f.ForestBear.class)); + cards.add(new SetCardInfo("Forsaken Crossroads", 264, Rarity.UNCOMMON, mage.cards.f.ForsakenCrossroads.class)); cards.add(new SetCardInfo("Future Sight", 122, Rarity.RARE, mage.cards.f.FutureSight.class)); cards.add(new SetCardInfo("Gerrard, Weatherlight Hero", 251, Rarity.RARE, mage.cards.g.GerrardWeatherlightHero.class)); cards.add(new SetCardInfo("Ghost Quarter", 109, Rarity.UNCOMMON, mage.cards.g.GhostQuarter.class)); diff --git a/Mage/src/main/java/mage/abilities/common/ActivateIfConditionActivatedAbility.java b/Mage/src/main/java/mage/abilities/common/ActivateIfConditionActivatedAbility.java index 9a07d574402..8d9804f7504 100644 --- a/Mage/src/main/java/mage/abilities/common/ActivateIfConditionActivatedAbility.java +++ b/Mage/src/main/java/mage/abilities/common/ActivateIfConditionActivatedAbility.java @@ -16,7 +16,7 @@ public class ActivateIfConditionActivatedAbility extends ActivatedAbilityImpl { public ActivateIfConditionActivatedAbility(Effect effect, Cost cost, Condition condition) { this(Zone.BATTLEFIELD, effect, cost, condition, TimingRule.INSTANT); } - + public ActivateIfConditionActivatedAbility(Zone zone, Effect effect, Cost cost, Condition condition) { this(zone, effect, cost, condition, TimingRule.INSTANT); } @@ -34,6 +34,10 @@ public class ActivateIfConditionActivatedAbility extends ActivatedAbilityImpl { @Override public String getRule() { StringBuilder sb = new StringBuilder(super.getRule()); + if (condition.toString().startsWith("You may also")) { + sb.append(' ').append(condition.toString()).append('.'); + return sb.toString(); + } if (condition instanceof InvertCondition) { sb.append(" You can't activate this ability "); } else { From 7c179bad5e95d798f1fc2453f9ed80fdd1d6c3f7 Mon Sep 17 00:00:00 2001 From: Cameron Merkel <44722506+Cguy7777@users.noreply.github.com> Date: Wed, 18 Sep 2024 18:09:15 -0500 Subject: [PATCH 55/62] [WHO] Implement Nardole, Resourceful Cyborg (#12886) --- .../cards/n/NardoleResourcefulCyborg.java | 104 ++++++++++++++++++ Mage.Sets/src/mage/sets/DoctorWho.java | 4 + 2 files changed, 108 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/n/NardoleResourcefulCyborg.java diff --git a/Mage.Sets/src/mage/cards/n/NardoleResourcefulCyborg.java b/Mage.Sets/src/mage/cards/n/NardoleResourcefulCyborg.java new file mode 100644 index 00000000000..9491cccf1fa --- /dev/null +++ b/Mage.Sets/src/mage/cards/n/NardoleResourcefulCyborg.java @@ -0,0 +1,104 @@ +package mage.cards.n; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import mage.MageInt; +import mage.Mana; +import mage.abilities.Ability; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.effects.mana.ManaEffect; +import mage.abilities.mana.SimpleManaAbility; +import mage.abilities.mana.builder.ConditionalManaBuilder; +import mage.abilities.mana.conditional.ConditionalSpellManaBuilder; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.abilities.keyword.UndyingAbility; +import mage.abilities.keyword.DoctorsCompanionAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.StaticFilters; +import mage.game.Game; +import mage.game.permanent.Permanent; + +/** + * @author Cguy7777 + */ +public final class NardoleResourcefulCyborg extends CardImpl { + + public NardoleResourcefulCyborg(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT, CardType.CREATURE}, "{1}{U}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.SCIENTIST); + this.power = new MageInt(1); + this.toughness = new MageInt(2); + + // {T}: Add {U} for each counter on Nardole. Spend this mana only to cast noncreature spells. + this.addAbility(new SimpleManaAbility(new NardoleResourcefulCyborgManaEffect(), new TapSourceCost())); + + // Undying + this.addAbility(new UndyingAbility()); + + // Doctor's companion + this.addAbility(DoctorsCompanionAbility.getInstance()); + } + + private NardoleResourcefulCyborg(final NardoleResourcefulCyborg card) { + super(card); + } + + @Override + public NardoleResourcefulCyborg copy() { + return new NardoleResourcefulCyborg(this); + } +} + +class NardoleResourcefulCyborgManaEffect extends ManaEffect { + + private final ConditionalManaBuilder manaBuilder + = new ConditionalSpellManaBuilder(StaticFilters.FILTER_SPELLS_NON_CREATURE); + + NardoleResourcefulCyborgManaEffect() { + this.staticText = "Add {U} for each counter on {this}. " + manaBuilder.getRule(); + } + + private NardoleResourcefulCyborgManaEffect(final NardoleResourcefulCyborgManaEffect effect) { + super(effect); + } + + @Override + public List getNetMana(Game game, Ability source) { + List netMana = new ArrayList<>(); + if (game == null) { + return netMana; + } + Permanent permanent = source.getSourcePermanentOrLKI(game); + if (permanent != null) { + netMana.add(manaBuilder.setMana( + Mana.BlueMana(permanent.getCounters(game).getTotalCount()), source, game).build()); + } + return netMana; + } + + @Override + public Mana produceMana(Game game, Ability source) { + Mana mana = new Mana(); + if (game == null) { + return mana; + } + Permanent permanent = source.getSourcePermanentOrLKI(game); + if (permanent == null) { + return mana; + } + return manaBuilder.setMana( + Mana.BlueMana(permanent.getCounters(game).getTotalCount()), source, game).build(); + } + + @Override + public NardoleResourcefulCyborgManaEffect copy() { + return new NardoleResourcefulCyborgManaEffect(this); + } +} diff --git a/Mage.Sets/src/mage/sets/DoctorWho.java b/Mage.Sets/src/mage/sets/DoctorWho.java index 79650578e0a..46f60e21e78 100644 --- a/Mage.Sets/src/mage/sets/DoctorWho.java +++ b/Mage.Sets/src/mage/sets/DoctorWho.java @@ -158,6 +158,10 @@ public final class DoctorWho extends ExpansionSet { cards.add(new SetCardInfo("Myriad Landscape", 290, Rarity.UNCOMMON, mage.cards.m.MyriadLandscape.class)); cards.add(new SetCardInfo("Mystic Monastery", 291, Rarity.UNCOMMON, mage.cards.m.MysticMonastery.class)); cards.add(new SetCardInfo("Nanogene Conversion", 49, Rarity.RARE, mage.cards.n.NanogeneConversion.class)); + cards.add(new SetCardInfo("Nardole, Resourceful Cyborg", 50, Rarity.RARE, mage.cards.n.NardoleResourcefulCyborg.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nardole, Resourceful Cyborg", 365, Rarity.RARE, mage.cards.n.NardoleResourcefulCyborg.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nardole, Resourceful Cyborg", 655, Rarity.RARE, mage.cards.n.NardoleResourcefulCyborg.class, NON_FULL_USE_VARIOUS)); + cards.add(new SetCardInfo("Nardole, Resourceful Cyborg", 956, Rarity.RARE, mage.cards.n.NardoleResourcefulCyborg.class, NON_FULL_USE_VARIOUS)); cards.add(new SetCardInfo("Ominous Cemetery", 189, Rarity.UNCOMMON, mage.cards.o.OminousCemetery.class)); cards.add(new SetCardInfo("Out of Time", 209, Rarity.RARE, mage.cards.o.OutOfTime.class)); cards.add(new SetCardInfo("Overgrown Farmland", 292, Rarity.RARE, mage.cards.o.OvergrownFarmland.class)); From e1f76c2b6ce7aa5e7960f14745904930e3c71abb Mon Sep 17 00:00:00 2001 From: jimga150 Date: Wed, 18 Sep 2024 19:09:39 -0400 Subject: [PATCH 56/62] Fix SacrificeTargetCost and SacrificeAllCost activator checks (#12809) * Fix Tergrid's Lantern and add test * Remove custom effect, fix SacrificeTargetCost to avoid checking for activated abilities and sidestepping the controllerID * Add test to verify change to SacrificeTargetCost * Add special action test * Fix canPay check for SacrificeTargetCost * Remove activated ability check in SacrificeAllCost * Remove cost-specific activator checks for special actions, as they are redundant * add null check for game.getPlayer --- .../src/mage/cards/t/TergridGodOfFright.java | 2 +- .../sacrifice/SacrificeTargetCostTest.java | 139 ++++++++++++++++++ .../cards/single/khm/TergridsLanternTest.java | 101 +++++++++++++ .../costs/common/SacrificeAllCost.java | 15 +- .../costs/common/SacrificeTargetCost.java | 27 +--- 5 files changed, 254 insertions(+), 30 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/cost/sacrifice/SacrificeTargetCostTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/khm/TergridsLanternTest.java diff --git a/Mage.Sets/src/mage/cards/t/TergridGodOfFright.java b/Mage.Sets/src/mage/cards/t/TergridGodOfFright.java index 5375eb36362..29e9210cbed 100644 --- a/Mage.Sets/src/mage/cards/t/TergridGodOfFright.java +++ b/Mage.Sets/src/mage/cards/t/TergridGodOfFright.java @@ -172,4 +172,4 @@ class TergridGodOfFrightEffect extends OneShotEffect { } return false; } -} \ No newline at end of file +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/cost/sacrifice/SacrificeTargetCostTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/cost/sacrifice/SacrificeTargetCostTest.java new file mode 100644 index 00000000000..4c88a4c194c --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/cost/sacrifice/SacrificeTargetCostTest.java @@ -0,0 +1,139 @@ +package org.mage.test.cards.cost.sacrifice; + +import mage.abilities.common.LicidAbility; +import mage.abilities.costs.mana.ColoredManaCost; +import mage.abilities.keyword.HasteAbility; +import mage.constants.CardType; +import mage.constants.ColoredManaSymbol; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Ignore; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author jimga150 + */ +public class SacrificeTargetCostTest extends CardTestPlayerBase { + + // Tests a variety of use cases with SacrificeTargetCost, making sure the right player pays the cost + + @Test + public void testSimpleCost() { + // All Rats have fear. + // {T}, Sacrifice a Rat: Create X 1/1 black Rat creature tokens, where X is the number of Rats you control. + addCard(Zone.BATTLEFIELD, playerA, "Marrow-Gnawer"); + addCard(Zone.BATTLEFIELD, playerA, "Karumonix, the Rat King"); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}"); + setChoice(playerA, "Karumonix, the Rat King"); // Target to sacrifice + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerA, "Marrow-Gnawer", 1); + assertPermanentCount(playerA, "Rat Token", 1); + assertGraveyardCount(playerA, "Karumonix, the Rat King", 1); + } + + @Test + public void testSimpleCostOtherPlayerActivate() { + // {1}, Sacrifice a land: Draw a card. Any player may activate this ability. + addCard(Zone.BATTLEFIELD, playerA, "Excavation"); + addCard(Zone.BATTLEFIELD, playerB, "Forest"); + + // Player B activates Player A's Excavate ability + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerB, "{1}"); + setChoice(playerB, "Forest"); // Target to sacrifice + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertHandCount(playerB, 1); + assertGraveyardCount(playerB, "Forest", 1); + } + + @Test + public void testDoUnlessSacrificeTrigger() { + // When Demanding Dragon enters, it deals 5 damage to target opponent unless that player sacrifices a creature. + addCard(Zone.HAND, playerA, "Demanding Dragon", 2); + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 5); + addCard(Zone.BATTLEFIELD, playerB, "Memnite"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Demanding Dragon"); + addTarget(playerA, playerB); + setChoice(playerB, "No"); // Sac a creature? + + castSpell(3, PhaseStep.PRECOMBAT_MAIN, playerA, "Demanding Dragon"); + addTarget(playerA, playerB); + setChoice(playerB, "Yes"); // Sac a creature? + setChoice(playerB, "Memnite"); // Sac Memnite + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerB, "Memnite", 1); + } + + @Test + public void testDoUnlessSacrificeActivated() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); + addCard(Zone.HAND, playerA, "Tergrid, God of Fright // Tergrid's Lantern"); + addCard(Zone.HAND, playerB, "Memnarch"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Tergrid's Lantern", true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player"); + addTarget(playerA, playerB); + setChoice(playerB, "No"); // Sac or discard to avoid life loss? + + activateAbility(3, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player"); + addTarget(playerA, playerB); + setChoice(playerB, "Yes"); // Sac or discard to avoid life loss? + setChoice(playerB, "Yes"); // Yes - Sacrifice, No - Discard + setChoice(playerB, "Memnite"); // To sacrifice + + setStrictChooseMode(true); + setStopAt(3, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, currentGame.getStartingLife() - 3); + assertPermanentCount(playerB, "Memnite", 0); + assertGraveyardCount(playerB, "Memnite", 1); + assertHandCount(playerB, "Memnarch", 1); + } + + /** + * Use special action that has opponent sac a permanent + */ + @Test + public void SpecialActionTest() { + // Enchanted creature can't attack or block, and its activated abilities can't be activated. + // That creature's controller may sacrifice a permanent for that player to ignore this effect until end of turn. + addCard(Zone.HAND, playerA, "Volrath's Curse"); + + addCard(Zone.BATTLEFIELD, playerA, "Island", 2); + addCard(Zone.BATTLEFIELD, playerB, "Memnite"); + addCard(Zone.BATTLEFIELD, playerB, "Memnarch"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "Volrath's Curse"); + addTarget(playerA, "Memnarch"); + + activateAbility(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Sacrifice a ", "Volrath's Curse"); + setChoice(playerB, "Memnite"); + + setStrictChooseMode(true); + setStopAt(2, PhaseStep.BEGIN_COMBAT); + execute(); + + assertPermanentCount(playerB, "Memnite", 0); + assertGraveyardCount(playerB, "Memnite", 1); + assertPermanentCount(playerB, "Memnarch", 1); + } + +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/khm/TergridsLanternTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/khm/TergridsLanternTest.java new file mode 100644 index 00000000000..23a6d62eedc --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/khm/TergridsLanternTest.java @@ -0,0 +1,101 @@ +package org.mage.test.cards.single.khm; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * {@link mage.cards.t.TergridGodOfFright Tergrid, God of Fright // Tergrid's Lantern} + * {3}{B}{B} + * Legendary Creature — God + * P/T 4/5 + * Menace + * Whenever an opponent sacrifices a nontoken permanent or discards a permanent card, you may put that card from a graveyard onto the battlefield under your control. + * + * {3}{B} + * Legendary Artifact + * {T}: Target player loses 3 life unless they sacrifice a nonland permanent or discard a card. + * {3}{B}: Untap Tergrid’s Lantern. + * + * @author jimga150 + */ +public class TergridsLanternTest extends CardTestPlayerBase { + + private static final String tergrid = "Tergrid, God of Fright // Tergrid's Lantern"; + private static final String tergridFirstSide = "Tergrid, God of Fright"; + private static final String tergridSecondSide = "Tergrid's Lantern"; + + @Test + public void testLoseLife() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); + addCard(Zone.HAND, playerA, tergrid); + addCard(Zone.HAND, playerB, "Memnarch"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, tergridSecondSide, true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player"); + addTarget(playerA, playerB); + setChoice(playerB, "No"); // Sac or discard to avoid life loss? + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, currentGame.getStartingLife() - 3); + assertPermanentCount(playerB, "Memnite", 1); + assertHandCount(playerB, "Memnarch", 1); + } + + @Test + public void testSacCreature() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); + addCard(Zone.HAND, playerA, tergrid); + addCard(Zone.HAND, playerB, "Memnarch"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, tergridSecondSide, true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player"); + addTarget(playerA, playerB); + setChoice(playerB, "Yes"); // Sac or discard to avoid life loss? + setChoice(playerB, "Yes"); // Yes - Sacrifice, No - Discard + setChoice(playerB, "Memnite"); // To sacrifice + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, currentGame.getStartingLife()); + assertPermanentCount(playerB, "Memnite", 0); + assertGraveyardCount(playerB, "Memnite", 1); + assertHandCount(playerB, "Memnarch", 1); + } + + @Test + public void testDiscard() { + addCard(Zone.BATTLEFIELD, playerA, "Swamp", 4); + addCard(Zone.BATTLEFIELD, playerB, "Memnite", 1); + addCard(Zone.HAND, playerA, tergrid); + addCard(Zone.HAND, playerB, "Memnarch"); + + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, tergridSecondSide, true); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{T}: Target player"); + addTarget(playerA, playerB); + setChoice(playerB, "Yes"); // Sac or discard to avoid life loss? + setChoice(playerB, "No"); // Yes - Sacrifice, No - Discard + setChoice(playerB, "Memnarch"); // To discard + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + + assertLife(playerB, currentGame.getStartingLife()); + assertPermanentCount(playerB, "Memnite", 1); + assertGraveyardCount(playerB, "Memnarch", 1); + assertHandCount(playerB, "Memnarch", 0); + } + +} diff --git a/Mage/src/main/java/mage/abilities/costs/common/SacrificeAllCost.java b/Mage/src/main/java/mage/abilities/costs/common/SacrificeAllCost.java index 5de46d6677d..4a4836b44e0 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/SacrificeAllCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/SacrificeAllCost.java @@ -1,14 +1,13 @@ package mage.abilities.costs.common; import mage.abilities.Ability; -import mage.abilities.ActivatedAbilityImpl; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; import mage.abilities.costs.SacrificeCost; -import mage.constants.AbilityType; import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.players.Player; import java.util.ArrayList; import java.util.List; @@ -46,19 +45,15 @@ public class SacrificeAllCost extends CostImpl implements SacrificeCost { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - UUID activator = controllerId; - if (ability.getAbilityType().isActivatedAbility() || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) { - if (((ActivatedAbilityImpl) ability).getActivatorId() != null) { - activator = ((ActivatedAbilityImpl) ability).getActivatorId(); - } // else, Activator not filled? + Player controller = game.getPlayer(controllerId); + if (controller == null){ + return false; } - for (Permanent permanent : game.getBattlefield().getAllActivePermanents(filter, controllerId, game)) { - if (!game.getPlayer(activator).canPaySacrificeCost(permanent, source, controllerId, game)) { + if (!controller.canPaySacrificeCost(permanent, source, controllerId, game)) { return false; } } - return true; } diff --git a/Mage/src/main/java/mage/abilities/costs/common/SacrificeTargetCost.java b/Mage/src/main/java/mage/abilities/costs/common/SacrificeTargetCost.java index 687bf53503d..4fa11cbffc4 100644 --- a/Mage/src/main/java/mage/abilities/costs/common/SacrificeTargetCost.java +++ b/Mage/src/main/java/mage/abilities/costs/common/SacrificeTargetCost.java @@ -1,15 +1,14 @@ package mage.abilities.costs.common; import mage.abilities.Ability; -import mage.abilities.ActivatedAbilityImpl; import mage.abilities.costs.Cost; import mage.abilities.costs.CostImpl; import mage.abilities.costs.SacrificeCost; -import mage.constants.AbilityType; import mage.constants.Outcome; import mage.filter.FilterPermanent; import mage.game.Game; import mage.game.permanent.Permanent; +import mage.players.Player; import mage.target.TargetPermanent; import mage.target.common.TargetSacrifice; import mage.util.CardUtil; @@ -58,12 +57,8 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost { @Override public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { - UUID activator = controllerId; - if (ability.getAbilityType().isActivatedAbility() || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) { - activator = ((ActivatedAbilityImpl) ability).getActivatorId(); - } - // can be cancel by user - if (this.getTargets().choose(Outcome.Sacrifice, activator, source.getSourceId(), source, game)) { + // can be cancelled by user + if (this.getTargets().choose(Outcome.Sacrifice, controllerId, source.getSourceId(), source, game)) { for (UUID targetId : this.getTargets().get(0).getTargets()) { Permanent permanent = game.getPermanent(targetId); if (permanent == null) { @@ -88,17 +83,14 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost { @Override public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { - UUID activator = controllerId; - if (ability.getAbilityType().isActivatedAbility() || ability.getAbilityType() == AbilityType.SPECIAL_ACTION) { - if (((ActivatedAbilityImpl) ability).getActivatorId() != null) { - activator = ((ActivatedAbilityImpl) ability).getActivatorId(); - } // else, Activator not filled? + Player controller = game.getPlayer(controllerId); + if (controller == null){ + return false; } - int validTargets = 0; int neededTargets = this.getTargets().get(0).getNumberOfTargets(); for (Permanent permanent : game.getBattlefield().getActivePermanents(((TargetPermanent) this.getTargets().get(0)).getFilter(), controllerId, source, game)) { - if (game.getPlayer(activator).canPaySacrificeCost(permanent, source, controllerId, game)) { + if (controller.canPaySacrificeCost(permanent, source, controllerId, game)) { validTargets++; if (validTargets >= neededTargets) { return true; @@ -106,10 +98,7 @@ public class SacrificeTargetCost extends CostImpl implements SacrificeCost { } } // solves issue #8097, if a sacrifice cost is optional and you don't have valid targets, then the cost can be paid - if (validTargets == 0 && this.getTargets().get(0).getMinNumberOfTargets() == 0) { - return true; - } - return false; + return validTargets == 0 && this.getTargets().get(0).getMinNumberOfTargets() == 0; } @Override From cd5195420803cf96eeca3c6f16af9f8e85ec5182 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 19 Sep 2024 04:32:21 +0400 Subject: [PATCH 57/62] tests: added verify check for wrong predicates usage in filters (ClassCastException errors like #12774) --- .../client/dialog/TestCardRenderDialog.java | 74 +------------------ Mage.Sets/src/mage/cards/c/ConduitOfRuin.java | 8 +- .../mage/cards/e/ElugeTheShorelessSea.java | 8 +- .../mage/cards/m/MelekReforgedResearcher.java | 8 +- .../src/mage/cards/s/ShadowInTheWarp.java | 8 +- .../java/mage/verify/VerifyCardDataTest.java | 7 +- .../src/main/java/mage/filter/FilterCard.java | 2 + .../java/mage/filter/FilterPermanent.java | 6 +- .../main/java/mage/filter/FilterPlayer.java | 5 ++ .../java/mage/filter/FilterStackObject.java | 8 ++ .../java/mage/filter/predicate/Predicate.java | 1 - .../mage/filter/predicate/Predicates.java | 48 +++++++++++- Mage/src/main/java/mage/game/FakeGame.java | 63 ++++++++++++++++ Mage/src/main/java/mage/game/FakeMatch.java | 21 ++++++ 14 files changed, 176 insertions(+), 91 deletions(-) create mode 100644 Mage/src/main/java/mage/game/FakeGame.java create mode 100644 Mage/src/main/java/mage/game/FakeMatch.java diff --git a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java index e209b2b57a2..dd171f36076 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/TestCardRenderDialog.java @@ -27,9 +27,7 @@ import mage.counters.Counter; import mage.counters.CounterType; import mage.designations.CitysBlessing; import mage.designations.Monarch; -import mage.game.Game; -import mage.game.GameException; -import mage.game.GameImpl; +import mage.game.*; import mage.game.command.Dungeon; import mage.game.command.Emblem; import mage.game.command.Plane; @@ -270,8 +268,8 @@ public class TestCardRenderDialog extends MageDialog { // prepare fake game and players without real match // it's a workaround with minimum code and data init - this.match = new TestMatch(); - this.game = new TestGame(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 20, 7); + this.match = new FakeMatch(); + this.game = new FakeGame(); Deck deck = new Deck(); Player playerYou = new StubPlayer("player1", RangeOfInfluence.ALL); playerYou.addDesignation(new CitysBlessing()); @@ -977,68 +975,4 @@ public class TestCardRenderDialog extends MageDialog { private javax.swing.JSpinner spinnerCardIconsAdditionalAmount; private javax.swing.JSpinner spinnerCardIconsMaxVisible; // End of variables declaration//GEN-END:variables -} - -class TestGame extends GameImpl { - - private int numPlayers; - - public TestGame(MultiplayerAttackOption attackOption, RangeOfInfluence range, Mulligan mulligan, int startLife, int startHandSize) { - super(attackOption, range, mulligan, 60, startLife, startHandSize); - } - - public TestGame(final TestGame game) { - super(game); - this.numPlayers = game.numPlayers; - } - - @Override - public MatchType getGameType() { - return new TestGameType(); - } - - @Override - public int getNumPlayers() { - return numPlayers; - } - - @Override - public TestGame copy() { - return new TestGame(this); - } - -} - -class TestGameType extends MatchType { - - public TestGameType() { - this.name = "Test Game Type"; - this.maxPlayers = 10; - this.minPlayers = 3; - this.numTeams = 0; - this.useAttackOption = true; - this.useRange = true; - this.sideboardingAllowed = true; - } - - protected TestGameType(final TestGameType matchType) { - super(matchType); - } - - @Override - public TestGameType copy() { - return new TestGameType(this); - } -} - -class TestMatch extends MatchImpl { - - public TestMatch() { - super(new MatchOptions("fake match", "fake game type", true, 2)); - } - - @Override - public void startGame() throws GameException { - throw new IllegalStateException("Can't start fake match"); - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/c/ConduitOfRuin.java b/Mage.Sets/src/mage/cards/c/ConduitOfRuin.java index 7e8ce759195..40bffb1b60d 100644 --- a/Mage.Sets/src/mage/cards/c/ConduitOfRuin.java +++ b/Mage.Sets/src/mage/cards/c/ConduitOfRuin.java @@ -99,12 +99,12 @@ class ConduitOfRuinWatcher extends Watcher { } } -class FirstCastCreatureSpellPredicate implements ObjectSourcePlayerPredicate { +class FirstCastCreatureSpellPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectSourcePlayer input, Game game) { - if (input.getObject() instanceof Card - && ((Card) input.getObject()).isCreature(game)) { + public boolean apply(ObjectSourcePlayer input, Game game) { + if (input.getObject() != null + && input.getObject().isCreature(game)) { ConduitOfRuinWatcher watcher = game.getState().getWatcher(ConduitOfRuinWatcher.class); return watcher != null && watcher.creatureSpellsCastThisTurn(input.getPlayerId()) == 0; } diff --git a/Mage.Sets/src/mage/cards/e/ElugeTheShorelessSea.java b/Mage.Sets/src/mage/cards/e/ElugeTheShorelessSea.java index 977ffa25e05..bfe5524a816 100644 --- a/Mage.Sets/src/mage/cards/e/ElugeTheShorelessSea.java +++ b/Mage.Sets/src/mage/cards/e/ElugeTheShorelessSea.java @@ -93,13 +93,13 @@ public final class ElugeTheShorelessSea extends CardImpl { } } -enum ElugeTheShorelessSeaPredicate implements ObjectSourcePlayerPredicate { +enum ElugeTheShorelessSeaPredicate implements ObjectSourcePlayerPredicate { instance; @Override - public boolean apply(ObjectSourcePlayer input, Game game) { - if (input.getObject() instanceof Card && - ((Card) input.getObject()).isInstantOrSorcery(game)) { + public boolean apply(ObjectSourcePlayer input, Game game) { + if (input.getObject() != null && + input.getObject().isInstantOrSorcery(game)) { ElugeTheShorelessSeaWatcher watcher = game.getState().getWatcher(ElugeTheShorelessSeaWatcher.class); return watcher != null && watcher.getInstantOrSorcerySpellsCastThisTurn(input.getPlayerId()) == 0; diff --git a/Mage.Sets/src/mage/cards/m/MelekReforgedResearcher.java b/Mage.Sets/src/mage/cards/m/MelekReforgedResearcher.java index 6dc069b4072..6fde3c71897 100644 --- a/Mage.Sets/src/mage/cards/m/MelekReforgedResearcher.java +++ b/Mage.Sets/src/mage/cards/m/MelekReforgedResearcher.java @@ -71,13 +71,13 @@ public final class MelekReforgedResearcher extends CardImpl { } } -enum MelekReforgedResearcherPredicate implements ObjectSourcePlayerPredicate { +enum MelekReforgedResearcherPredicate implements ObjectSourcePlayerPredicate { instance; @Override - public boolean apply(ObjectSourcePlayer input, Game game) { - if (input.getObject() instanceof Card && - ((Card) input.getObject()).isInstantOrSorcery(game)) { + public boolean apply(ObjectSourcePlayer input, Game game) { + if (input.getObject() != null && + input.getObject().isInstantOrSorcery(game)) { MelekReforgedResearcherWatcher watcher = game.getState().getWatcher(MelekReforgedResearcherWatcher.class); return watcher != null && watcher.getInstantOrSorcerySpellsCastThisTurn(input.getPlayerId()) == 0; diff --git a/Mage.Sets/src/mage/cards/s/ShadowInTheWarp.java b/Mage.Sets/src/mage/cards/s/ShadowInTheWarp.java index f237f78721f..adb6eecdab8 100644 --- a/Mage.Sets/src/mage/cards/s/ShadowInTheWarp.java +++ b/Mage.Sets/src/mage/cards/s/ShadowInTheWarp.java @@ -88,12 +88,12 @@ class ShadowInTheWarpWatcher extends Watcher { } } -class FirstCastCreatureSpellPredicate implements ObjectSourcePlayerPredicate { +class FirstCastCreatureSpellPredicate implements ObjectSourcePlayerPredicate { @Override - public boolean apply(ObjectSourcePlayer input, Game game) { - if (input.getObject() instanceof Card - && ((Card) input.getObject()).isCreature(game)) { + public boolean apply(ObjectSourcePlayer input, Game game) { + if (input.getObject() != null + && input.getObject().isCreature(game)) { ShadowInTheWarpWatcher watcher = game.getState().getWatcher(ShadowInTheWarpWatcher.class); return watcher != null && watcher.creatureSpellsCastThisTurn(input.getPlayerId()) == 0; } diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index eeee3d91ce1..d7d8717eb27 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -2769,6 +2769,9 @@ public class VerifyCardDataTest { public void test_checkCardConstructors() { // create all cards, can catch additional verify and runtime checks from abilities and effects // example: wrong code usage errors + // + // warning, look at stack trace logs, not card names -- some error code can be hidden in static methods and can be called from un-related cards + // use test_showCardInfo for detailed errors Collection errorsList = new ArrayList<>(); Collection sets = Sets.getInstance().values(); for (ExpansionSet set : sets) { @@ -2777,7 +2780,7 @@ public class VerifyCardDataTest { Card card = CardImpl.createCard(setInfo.getCardClass(), new CardSetInfo(setInfo.getName(), set.getCode(), setInfo.getCardNumber(), setInfo.getRarity(), setInfo.getGraphicInfo())); if (card == null) { - errorsList.add("Error: broken constructor " + setInfo.getCardClass()); + errorsList.add("Error: can't create card - " + setInfo.getCardClass() + " - see logs for errors"); continue; } if (!card.getExpansionSetCode().equals(set.getCode())) { @@ -2785,7 +2788,7 @@ public class VerifyCardDataTest { } } catch (Throwable e) { // CardImpl.createCard don't throw exceptions (only error logs), so that logs are useless here - logger.error("Error: can't create card " + setInfo.getName() + ": " + e.getMessage(), e); + errorsList.add("Error: can't create card - " + setInfo.getCardClass() + " - see logs for errors"); } } } diff --git a/Mage/src/main/java/mage/filter/FilterCard.java b/Mage/src/main/java/mage/filter/FilterCard.java index c4d2f98183c..ccdb72f6e52 100644 --- a/Mage/src/main/java/mage/filter/FilterCard.java +++ b/Mage/src/main/java/mage/filter/FilterCard.java @@ -73,7 +73,9 @@ public class FilterCard extends FilterObject { throw new UnsupportedOperationException("You may not modify a locked filter"); } + // verify check checkPredicateIsSuitableForCardFilter(predicate); + Predicates.makeSurePredicateCompatibleWithFilter(predicate, Card.class); extraPredicates.add(predicate); } diff --git a/Mage/src/main/java/mage/filter/FilterPermanent.java b/Mage/src/main/java/mage/filter/FilterPermanent.java index 0177accc9f8..9eaee653392 100644 --- a/Mage/src/main/java/mage/filter/FilterPermanent.java +++ b/Mage/src/main/java/mage/filter/FilterPermanent.java @@ -5,6 +5,7 @@ import mage.constants.SubType; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; import mage.game.Game; import mage.game.permanent.Permanent; @@ -12,7 +13,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; /** * @author North @@ -64,6 +64,10 @@ public class FilterPermanent extends FilterObject implements FilterIn if (isLockedFilter()) { throw new UnsupportedOperationException("You may not modify a locked filter"); } + + // verify check + Predicates.makeSurePredicateCompatibleWithFilter(predicate, Permanent.class); + extraPredicates.add(predicate); } diff --git a/Mage/src/main/java/mage/filter/FilterPlayer.java b/Mage/src/main/java/mage/filter/FilterPlayer.java index 0fa56b3a2a7..766cba8253b 100644 --- a/Mage/src/main/java/mage/filter/FilterPlayer.java +++ b/Mage/src/main/java/mage/filter/FilterPlayer.java @@ -4,6 +4,7 @@ import mage.abilities.Ability; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; import mage.game.Game; import mage.players.Player; @@ -36,6 +37,10 @@ public class FilterPlayer extends FilterImpl { if (isLockedFilter()) { throw new UnsupportedOperationException("You may not modify a locked filter"); } + + // verify check + Predicates.makeSurePredicateCompatibleWithFilter(predicate, Player.class); + extraPredicates.add(predicate); return this; } diff --git a/Mage/src/main/java/mage/filter/FilterStackObject.java b/Mage/src/main/java/mage/filter/FilterStackObject.java index bfdd56d54fd..aa3b506c1ae 100644 --- a/Mage/src/main/java/mage/filter/FilterStackObject.java +++ b/Mage/src/main/java/mage/filter/FilterStackObject.java @@ -1,10 +1,13 @@ package mage.filter; import mage.abilities.Ability; +import mage.cards.Card; import mage.filter.predicate.ObjectSourcePlayer; import mage.filter.predicate.ObjectSourcePlayerPredicate; import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; import mage.game.Game; +import mage.game.stack.Spell; import mage.game.stack.StackObject; import java.util.ArrayList; @@ -43,6 +46,11 @@ public class FilterStackObject extends FilterObject { if (isLockedFilter()) { throw new UnsupportedOperationException("You may not modify a locked filter"); } + + // verify check + // Spell implements Card interface, so it can use some default predicates like owner + Predicates.makeSurePredicateCompatibleWithFilter(predicate, StackObject.class, Spell.class, Card.class); + extraPredicates.add(predicate); } diff --git a/Mage/src/main/java/mage/filter/predicate/Predicate.java b/Mage/src/main/java/mage/filter/predicate/Predicate.java index e4e64de3963..6b7b5382f31 100644 --- a/Mage/src/main/java/mage/filter/predicate/Predicate.java +++ b/Mage/src/main/java/mage/filter/predicate/Predicate.java @@ -1,4 +1,3 @@ - package mage.filter.predicate; import java.io.Serializable; diff --git a/Mage/src/main/java/mage/filter/predicate/Predicates.java b/Mage/src/main/java/mage/filter/predicate/Predicates.java index 0f2a0c9cbbd..bf3213c8290 100644 --- a/Mage/src/main/java/mage/filter/predicate/Predicates.java +++ b/Mage/src/main/java/mage/filter/predicate/Predicates.java @@ -2,6 +2,8 @@ package mage.filter.predicate; import mage.game.Game; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -11,7 +13,7 @@ import java.util.List; * *

All methods returns serializable predicates as long as they're given serializable parameters.

* - * @author North + * @author North, JayDi85 */ public final class Predicates { @@ -246,4 +248,48 @@ public final class Predicates { extraPredicates.forEach(p -> collectAllComponents(p, res)); } } + + /** + * Verify check: try to find filters usage + * Example use case: Player predicate was used for Permanent filter + * Example error: java.lang.ClassCastException: mage.game.permanent.PermanentToken cannot be cast to mage.players.Player + */ + public static void makeSurePredicateCompatibleWithFilter(Predicate predicate, Class... compatibleClasses) { + List list = new ArrayList<>(); + Predicates.collectAllComponents(predicate, list); + list.forEach(p -> { + Class predicateGenericParamClass = findGenericParam(predicate); + if (predicateGenericParamClass == null) { + throw new IllegalArgumentException("Somthing wrong. Can't find predicate's generic param for " + predicate.getClass()); + } + if (Arrays.stream(compatibleClasses).anyMatch(f -> predicateGenericParamClass.isAssignableFrom(f))) { + // predicate is fine + } else { + // How-to fix: use correct predicates (same type, e.g. getControllerPredicate() instead getPlayerPredicate()) + throw new IllegalArgumentException(String.format( + "Wrong code usage: predicate [%s] with generic param [%s] can't be added to filter, allow only %s", + predicate.getClass(), + predicateGenericParamClass, + Arrays.toString(compatibleClasses) + )); + } + }); + } + + private static Class findGenericParam(Predicate predicate) { + Type[] interfaces = predicate.getClass().getGenericInterfaces(); + for (Type type : interfaces) { + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + if (actualTypeArguments.length > 0) { + Type actualType = actualTypeArguments[0]; + if (actualType instanceof Class) { + return (Class) actualType; + } + } + } + } + return null; + } } diff --git a/Mage/src/main/java/mage/game/FakeGame.java b/Mage/src/main/java/mage/game/FakeGame.java new file mode 100644 index 00000000000..7bee3465e6a --- /dev/null +++ b/Mage/src/main/java/mage/game/FakeGame.java @@ -0,0 +1,63 @@ +package mage.game; + +import mage.constants.MultiplayerAttackOption; +import mage.constants.RangeOfInfluence; +import mage.game.match.MatchType; +import mage.game.mulligan.MulliganType; + +/** + * Fake game for tests and data check, do nothing. + * + * @author JayDi85 + */ +public class FakeGame extends GameImpl { + + private int numPlayers; + + public FakeGame() { + super(MultiplayerAttackOption.MULTIPLE, RangeOfInfluence.ALL, MulliganType.GAME_DEFAULT.getMulligan(0), 60, 20, 7); + } + + public FakeGame(final FakeGame game) { + super(game); + this.numPlayers = game.numPlayers; + } + + @Override + public MatchType getGameType() { + return new FakeGameType(); + } + + @Override + public int getNumPlayers() { + return numPlayers; + } + + @Override + public FakeGame copy() { + return new FakeGame(this); + } + +} + +class FakeGameType extends MatchType { + + public FakeGameType() { + this.name = "Test Game Type"; + this.maxPlayers = 10; + this.minPlayers = 3; + this.numTeams = 0; + this.useAttackOption = true; + this.useRange = true; + this.sideboardingAllowed = true; + } + + protected FakeGameType(final FakeGameType matchType) { + super(matchType); + } + + @Override + public FakeGameType copy() { + return new FakeGameType(this); + } +} \ No newline at end of file diff --git a/Mage/src/main/java/mage/game/FakeMatch.java b/Mage/src/main/java/mage/game/FakeMatch.java new file mode 100644 index 00000000000..44cb401b961 --- /dev/null +++ b/Mage/src/main/java/mage/game/FakeMatch.java @@ -0,0 +1,21 @@ +package mage.game; + +import mage.game.match.MatchImpl; +import mage.game.match.MatchOptions; + +/** + * Fake match for tests and data check, do nothing. + * + * @author JayDi85 + */ +public class FakeMatch extends MatchImpl { + + public FakeMatch() { + super(new MatchOptions("fake match", "fake game type", true, 2)); + } + + @Override + public void startGame() throws GameException { + throw new IllegalStateException("Can't start fake match"); + } +} From b40e7222b3fa39c5389963103620e849cba759f8 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Thu, 19 Sep 2024 13:42:23 +0400 Subject: [PATCH 58/62] Dungeon improves: * Dungeons: added dungeon name hint to room's game log and choices (part of #12274); * GUI, game: added card popup hints support in feedback panel (yes/no choices); * Images: fixed miss images for dungeons in command zone, game logs and choice dialogs; --- .../client/components/MageEditorPane.java | 12 ++++++- .../mage/client/dialog/PickChoiceDialog.java | 2 +- .../java/mage/client/game/FeedbackPanel.java | 5 +-- .../main/java/mage/client/game/GamePanel.java | 23 ++++++++---- .../java/mage/client/game/HelperPanel.java | 4 ++- .../src/main/java/mage/view/CardView.java | 18 ++++++---- Mage/src/main/java/mage/game/GameImpl.java | 11 +++++- .../main/java/mage/game/command/Dungeon.java | 36 ++++++++++++++++--- .../java/mage/game/command/DungeonRoom.java | 9 +++-- 9 files changed, 94 insertions(+), 26 deletions(-) diff --git a/Mage.Client/src/main/java/mage/client/components/MageEditorPane.java b/Mage.Client/src/main/java/mage/client/components/MageEditorPane.java index 6cd2e782184..48616418a5e 100644 --- a/Mage.Client/src/main/java/mage/client/components/MageEditorPane.java +++ b/Mage.Client/src/main/java/mage/client/components/MageEditorPane.java @@ -8,10 +8,12 @@ import mage.client.cards.BigCard; import mage.client.cards.VirtualCardInfo; import mage.client.dialog.PreferencesDialog; import mage.client.game.GamePanel; +import mage.game.command.Dungeon; import mage.game.command.Plane; import mage.util.CardUtil; import mage.util.GameLog; import mage.view.CardView; +import mage.view.DungeonView; import mage.view.PlaneView; import javax.swing.*; @@ -156,7 +158,7 @@ public class MageEditorPane extends JEditorPane { // show real object by priority (workable card hints and actual info) CardView cardView = needCard; - // if no game object found then show default card + // if no game object found then show default card/object if (cardView == null) { CardInfo card = CardRepository.instance.findCards(cardName).stream().findFirst().orElse(null); if (card != null) { @@ -172,6 +174,14 @@ public class MageEditorPane extends JEditorPane { } } + // dungeon + if (cardView == null) { + Dungeon dungeon = Dungeon.createDungeon(cardName, false); + if (dungeon != null) { + cardView = new CardView(new DungeonView(dungeon)); + } + } + // TODO: add other objects like dungeon, emblem, commander if (cardView != null) { diff --git a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java index 8090d65da04..8b2693d25ca 100644 --- a/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java +++ b/Mage.Client/src/main/java/mage/client/dialog/PickChoiceDialog.java @@ -314,7 +314,7 @@ public class PickChoiceDialog extends MageDialog { cardInfo.init(item.getHint(), this.bigCard, this.gameId); } else if (item.getHintType() == ChoiceHintType.CARD_DUNGEON) { // as card name - CardView cardView = new CardView(new DungeonView(Dungeon.createDungeon(item.getHint()))); + CardView cardView = new CardView(new DungeonView(Dungeon.createDungeon(item.getHint(), true))); cardInfo.init(cardView, this.bigCard, this.gameId); } else if (item.getHintType() == ChoiceHintType.GAME_OBJECT) { // as object diff --git a/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java b/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java index 90db8ae049e..bd32fd99840 100644 --- a/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/FeedbackPanel.java @@ -2,6 +2,7 @@ package mage.client.game; import mage.client.MageFrame; import mage.client.SessionHandler; +import mage.client.cards.BigCard; import mage.client.chat.ChatPanelBasic; import mage.client.dialog.MageDialog; import mage.client.util.audio.AudioManager; @@ -56,9 +57,9 @@ public class FeedbackPanel extends javax.swing.JPanel { customInitComponents(); } - public void init(UUID gameId) { + public void init(UUID gameId, BigCard bigCard) { this.gameId = gameId; - helper.init(gameId); + helper.init(gameId, bigCard); setGUISize(); } diff --git a/Mage.Client/src/main/java/mage/client/game/GamePanel.java b/Mage.Client/src/main/java/mage/client/game/GamePanel.java index c22d72ce1c5..70f9a813aa0 100644 --- a/Mage.Client/src/main/java/mage/client/game/GamePanel.java +++ b/Mage.Client/src/main/java/mage/client/game/GamePanel.java @@ -196,11 +196,20 @@ public final class GamePanel extends javax.swing.JPanel { player.getGraveyard().values().forEach(c -> this.allCardsIndex.put(c.getId(), c)); Optional.ofNullable(player.getTopCard()).ifPresent(c -> this.allCardsIndex.put(c.getId(), c)); // TODO: add support of dungeon, emblem all another non-card objects + // commanders and custom emblems player.getCommandObjectList() - .stream() - .filter(c -> c instanceof CardView) - .map(c -> (CardView) c) - .forEach(c -> this.allCardsIndex.put(c.getId(), c)); + .forEach(object -> { + if (object instanceof CardView) { + // commanders and custom emblems + this.allCardsIndex.put(object.getId(), (CardView) object); + } else if (object instanceof DungeonView) { + // dungeons + this.allCardsIndex.put(object.getId(), new CardView((DungeonView) object)); + } else { + // TODO: enable after all view types added here? + //throw new IllegalArgumentException("Unsupported object type: " + object.getName() + " - " + object.getClass().getSimpleName()); + } + }); }); } @@ -808,7 +817,7 @@ public final class GamePanel extends javax.swing.JPanel { this.gamePane = gamePane; this.playerId = playerId; MageFrame.addGame(gameId, this); - this.feedbackPanel.init(gameId); + this.feedbackPanel.init(gameId, bigCard); this.feedbackPanel.clear(); this.abilityPicker.init(gameId, bigCard); this.btnConcede.setVisible(true); @@ -851,7 +860,7 @@ public final class GamePanel extends javax.swing.JPanel { this.gamePane = gamePane; this.playerId = null; MageFrame.addGame(gameId, this); - this.feedbackPanel.init(gameId); + this.feedbackPanel.init(gameId, bigCard); this.feedbackPanel.clear(); this.btnConcede.setVisible(false); @@ -886,7 +895,7 @@ public final class GamePanel extends javax.swing.JPanel { this.gameId = gameId; this.playerId = null; MageFrame.addGame(gameId, this); - this.feedbackPanel.init(gameId); + this.feedbackPanel.init(gameId, bigCard); this.feedbackPanel.clear(); this.btnConcede.setVisible(false); this.btnSkipToNextTurn.setVisible(false); diff --git a/Mage.Client/src/main/java/mage/client/game/HelperPanel.java b/Mage.Client/src/main/java/mage/client/game/HelperPanel.java index 074ef407182..3ae0d814c18 100644 --- a/Mage.Client/src/main/java/mage/client/game/HelperPanel.java +++ b/Mage.Client/src/main/java/mage/client/game/HelperPanel.java @@ -1,6 +1,7 @@ package mage.client.game; import mage.client.SessionHandler; +import mage.client.cards.BigCard; import mage.client.components.MageTextArea; import mage.client.constants.Constants; import mage.client.dialog.PreferencesDialog; @@ -89,8 +90,9 @@ public class HelperPanel extends JPanel { initComponents(); } - public void init(UUID gameId) { + public void init(UUID gameId, BigCard bigCard) { this.gameId = gameId; + this.dialogTextArea.setGameData(gameId, bigCard); } public void changeGUISize() { diff --git a/Mage.Common/src/main/java/mage/view/CardView.java b/Mage.Common/src/main/java/mage/view/CardView.java index 1462b395b35..bc03a3d3633 100644 --- a/Mage.Common/src/main/java/mage/view/CardView.java +++ b/Mage.Common/src/main/java/mage/view/CardView.java @@ -878,7 +878,8 @@ public class CardView extends SimpleCardView { this.displayName = name; this.displayFullName = name; this.rules = new ArrayList<>(emblem.getRules()); - // emblem images are always with common (black) symbol + + // image - emblem are always with common (black) symbol this.frameStyle = FrameStyle.M15_NORMAL; this.expansionSetCode = emblem.getExpansionSetCode(); this.cardNumber = emblem.getCardNumber(); @@ -901,12 +902,13 @@ public class CardView extends SimpleCardView { this.displayName = name; this.displayFullName = name; this.rules = new ArrayList<>(dungeon.getRules()); - // emblem images are always with common (black) symbol - this.frameStyle = FrameStyle.M15_NORMAL; + + // image + this.frameStyle = FrameStyle.M15_NORMAL; // TODO: needs in full art? Test dungeon choose dialog this.expansionSetCode = dungeon.getExpansionSetCode(); this.cardNumber = ""; - this.imageFileName = ""; - this.imageNumber = 0; + this.imageFileName = dungeon.getImageFileName(); + this.imageNumber = dungeon.getImageNumber(); this.rarity = Rarity.SPECIAL; this.playableStats = dungeon.playableStats.copy(); @@ -923,7 +925,8 @@ public class CardView extends SimpleCardView { this.displayName = name; this.displayFullName = name; this.rules = new ArrayList<>(plane.getRules()); - // Display the plane in landscape (similar to Fused cards) + + // image - display the plane in landscape (similar to Fused cards) this.rotate = true; this.frameStyle = FrameStyle.M15_NORMAL; this.expansionSetCode = plane.getExpansionSetCode(); @@ -947,6 +950,8 @@ public class CardView extends SimpleCardView { this.displayFullName = name; this.rules = new ArrayList<>(); this.rules.add(stackAbility.getRule(designation.getName())); + + // image this.frameStyle = FrameStyle.M15_NORMAL; this.cardNumber = designation.getCardNumber(); this.expansionSetCode = designation.getExpansionSetCode(); @@ -954,6 +959,7 @@ public class CardView extends SimpleCardView { this.imageFileName = ""; this.imageNumber = 0; this.rarity = Rarity.SPECIAL; + // no playable/chooseable marks for designations } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index ba5d909dcf1..4631cb50f98 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -584,6 +584,9 @@ public abstract class GameImpl implements Game { return emblem; } TheRingEmblem newEmblem = new TheRingEmblem(playerId); + + // TODO: add image info + state.addCommandObject(newEmblem); return newEmblem; } @@ -1971,7 +1974,9 @@ public abstract class GameImpl implements Game { ability.setSourceId(newEmblem.getId()); } - state.addCommandObject(newEmblem); // TODO: generate image for emblem here? + // image info setup in setSourceObject + + state.addCommandObject(newEmblem); } /** @@ -1999,6 +2004,9 @@ public abstract class GameImpl implements Game { for (Ability ability : newPlane.getAbilities()) { ability.setSourceId(newPlane.getId()); } + + // image info setup in setSourceObject + state.addCommandObject(newPlane); informPlayers("You have planeswalked to " + newPlane.getLogName()); @@ -2020,6 +2028,7 @@ public abstract class GameImpl implements Game { @Override public Dungeon addDungeon(Dungeon dungeon, UUID playerId) { dungeon.setControllerId(playerId); + dungeon.setSourceObject(); state.addCommandObject(dungeon); return dungeon; } diff --git a/Mage/src/main/java/mage/game/command/Dungeon.java b/Mage/src/main/java/mage/game/command/Dungeon.java index adf15b5b8f4..8643378c17a 100644 --- a/Mage/src/main/java/mage/game/command/Dungeon.java +++ b/Mage/src/main/java/mage/game/command/Dungeon.java @@ -13,6 +13,8 @@ import mage.abilities.effects.ContinuousEffect; import mage.abilities.effects.Effect; import mage.abilities.hint.HintUtils; import mage.cards.FrameStyle; +import mage.cards.repository.TokenInfo; +import mage.cards.repository.TokenRepository; import mage.choices.Choice; import mage.choices.ChoiceHintType; import mage.choices.ChoiceImpl; @@ -87,6 +89,11 @@ public class Dungeon extends CommandObjectImpl { } public void moveToNextRoom(UUID playerId, Game game) { + Dungeon dungeon = game.getPlayerDungeon(playerId); + if (dungeon == null) { + return; + } + if (currentRoom == null) { currentRoom = dungeonRooms.get(0); } else { @@ -94,7 +101,7 @@ public class Dungeon extends CommandObjectImpl { } Player player = game.getPlayer(getControllerId()); if (player != null) { - game.informPlayers(player.getLogName() + " has entered " + currentRoom.getName()); + game.informPlayers(player.getLogName() + " has entered " + currentRoom.getName() + " (dungeon: " + dungeon.getLogName() + ")"); } game.fireEvent(GameEvent.getEvent( GameEvent.EventType.ROOM_ENTERED, currentRoom.getId(), null, playerId @@ -139,14 +146,14 @@ public class Dungeon extends CommandObjectImpl { choice.setChoices(dungeonNames); player.choose(Outcome.Neutral, choice, game); if (choice.getChoice() != null) { - return createDungeon(choice.getChoice()); + return createDungeon(choice.getChoice(), true); } else { // on disconnect - return createDungeon("Tomb of Annihilation"); + return createDungeon("Tomb of Annihilation", true); } } - public static Dungeon createDungeon(String name) { + public static Dungeon createDungeon(String name, boolean isNameMustExists) { switch (name) { case "Tomb of Annihilation": return new TombOfAnnihilationDungeon(); @@ -155,7 +162,26 @@ public class Dungeon extends CommandObjectImpl { case "Dungeon of the Mad Mage": return new DungeonOfTheMadMageDungeon(); default: - throw new UnsupportedOperationException("A dungeon should have been chosen"); + if (isNameMustExists) { + throw new UnsupportedOperationException("A dungeon should have been chosen"); + } else { + return null; + } + } + } + + public void setSourceObject() { + // choose set code due source + TokenInfo foundInfo = TokenRepository.instance.findPreferredTokenInfoForClass(this.getClass().getName(), null); + if (foundInfo != null) { + this.setExpansionSetCode(foundInfo.getSetCode()); + this.setUsesVariousArt(false); + this.setCardNumber(""); + this.setImageFileName(""); // use default + this.setImageNumber(foundInfo.getImageNumber()); + } else { + // how-to fix: add dungeon to the tokens-database + throw new IllegalArgumentException("Wrong code usage: can't find token info for the dungeon: " + this.getClass().getName()); } } diff --git a/Mage/src/main/java/mage/game/command/DungeonRoom.java b/Mage/src/main/java/mage/game/command/DungeonRoom.java index ba2174a7ecc..121264cef47 100644 --- a/Mage/src/main/java/mage/game/command/DungeonRoom.java +++ b/Mage/src/main/java/mage/game/command/DungeonRoom.java @@ -77,6 +77,11 @@ public class DungeonRoom { } public DungeonRoom chooseNextRoom(UUID playerId, Game game) { + Dungeon dungeon = game.getPlayerDungeon(playerId); + if (dungeon == null) { + return null; + } + switch (nextRooms.size()) { case 0: return null; @@ -90,8 +95,8 @@ public class DungeonRoom { return null; } return player.chooseUse( - Outcome.Neutral, "Choose which room to go to", - null, room1.name, room2.name, null, game + Outcome.Neutral, "Choose which room to go to in", + "dungeon: " + dungeon.getLogName(), room1.name, room2.name, null, game ) ? room1 : room2; default: throw new UnsupportedOperationException("there shouldn't be more than two rooms to go to"); From fb63fe0318ffeba21c2bb5dc9fe67bce77b6a92b Mon Sep 17 00:00:00 2001 From: Jeff Wadsworth Date: Thu, 19 Sep 2024 14:35:30 -0500 Subject: [PATCH 59/62] Fixed #12874. A code simplification is warranted if someone has the time. Fix is inline with the rest of it. --- .../mage/cards/b/BrunaLightOfAlabaster.java | 49 ++++++++++++++----- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/Mage.Sets/src/mage/cards/b/BrunaLightOfAlabaster.java b/Mage.Sets/src/mage/cards/b/BrunaLightOfAlabaster.java index 3d1ed50412e..de934d39ef3 100644 --- a/Mage.Sets/src/mage/cards/b/BrunaLightOfAlabaster.java +++ b/Mage.Sets/src/mage/cards/b/BrunaLightOfAlabaster.java @@ -1,4 +1,3 @@ - package mage.cards.b; import java.util.ArrayList; @@ -100,24 +99,34 @@ class BrunaLightOfAlabasterEffect extends OneShotEffect { List fromBattlefield = new ArrayList<>(); List fromHandGraveyard = new ArrayList<>(); - int countBattlefield = game.getBattlefield().getActivePermanents(filterAura, source.getControllerId(), source, game).size() - sourcePermanent.getAttachments().size(); + int countBattlefield = (int) (game.getBattlefield().getActivePermanents(filterAura, source.getControllerId(), source, game).size() + - sourcePermanent.getAttachments().stream().map(game::getPermanent).filter(permanent -> permanent != null + && permanent.hasSubtype(SubType.AURA, game)).count()); while (controller.canRespond() && countBattlefield > 0 && controller.chooseUse(Outcome.Benefit, "Attach an Aura from the battlefield?", source, game)) { Target targetAura = new TargetPermanent(filterAura); targetAura.withNotTarget(true); - if (!controller.choose(Outcome.Benefit, targetAura, source, game)) { continue; } + if (!controller.choose(Outcome.Benefit, targetAura, source, game)) { + continue; + } Permanent aura = game.getPermanent(targetAura.getFirstTarget()); - if (aura == null) { continue; } + if (aura == null) { + continue; + } Target target = aura.getSpellAbility().getTargets().get(0); - if (target == null) { continue; } + if (target == null) { + continue; + } fromBattlefield.add(aura); filterAura.add(Predicates.not(new CardIdPredicate(aura.getId()))); - countBattlefield = game.getBattlefield().getActivePermanents(filterAura, source.getControllerId(), source, game).size() - sourcePermanent.getAttachments().size(); + countBattlefield = (int) (game.getBattlefield().getActivePermanents(filterAura, source.getControllerId(), source, game).size() + - sourcePermanent.getAttachments().stream().map(game::getPermanent).filter(permanent -> permanent != null + && permanent.hasSubtype(SubType.AURA, game)).count()); } int countHand = controller.getHand().count(filterAuraCard, game); @@ -125,13 +134,19 @@ class BrunaLightOfAlabasterEffect extends OneShotEffect { && countHand > 0 && controller.chooseUse(Outcome.Benefit, "Attach an Aura from your hand?", source, game)) { TargetCard targetAura = new TargetCard(Zone.HAND, filterAuraCard); - if (!controller.choose(Outcome.Benefit, controller.getHand(), targetAura, source, game)) { continue; } + if (!controller.choose(Outcome.Benefit, controller.getHand(), targetAura, source, game)) { + continue; + } Card aura = game.getCard(targetAura.getFirstTarget()); - if (aura == null) { continue; } + if (aura == null) { + continue; + } Target target = aura.getSpellAbility().getTargets().get(0); - if (target == null) { continue; } + if (target == null) { + continue; + } fromHandGraveyard.add(aura); filterAuraCard.add(Predicates.not(new CardIdPredicate(aura.getId()))); @@ -143,13 +158,19 @@ class BrunaLightOfAlabasterEffect extends OneShotEffect { && countGraveyard > 0 && controller.chooseUse(Outcome.Benefit, "Attach an Aura from your graveyard?", source, game)) { TargetCard targetAura = new TargetCard(Zone.GRAVEYARD, filterAuraCard); - if (!controller.choose(Outcome.Benefit, controller.getGraveyard(), targetAura, source, game)) { continue; } + if (!controller.choose(Outcome.Benefit, controller.getGraveyard(), targetAura, source, game)) { + continue; + } Card aura = game.getCard(targetAura.getFirstTarget()); - if (aura == null) { continue; } + if (aura == null) { + continue; + } Target target = aura.getSpellAbility().getTargets().get(0); - if (target == null) { continue; } + if (target == null) { + continue; + } fromHandGraveyard.add(aura); filterAuraCard.add(Predicates.not(new CardIdPredicate(aura.getId()))); @@ -168,7 +189,9 @@ class BrunaLightOfAlabasterEffect extends OneShotEffect { // Move cards for (Card aura : fromHandGraveyard) { - if (aura == null) { continue; } + if (aura == null) { + continue; + } game.getState().setValue("attachTo:" + aura.getId(), sourcePermanent); controller.moveCards(aura, Zone.BATTLEFIELD, source, game); From f55bc2c4fcdd23ce65ed2d99b005ae3331fe0711 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 20 Sep 2024 15:29:06 +0400 Subject: [PATCH 60/62] [TMP] Implement Phyrexian Splicer (part of #5379) --- .../src/mage/cards/a/AkromaAngelOfWrath.java | 2 +- .../src/mage/cards/p/PhyrexianSplicer.java | 242 ++++++++++++++++++ Mage.Sets/src/mage/sets/Tempest.java | 1 + .../single/tmp/PhyrexianSplicerTest.java | 43 ++++ .../java/org/mage/test/player/TestPlayer.java | 5 +- 5 files changed, 291 insertions(+), 2 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/p/PhyrexianSplicer.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/PhyrexianSplicerTest.java diff --git a/Mage.Sets/src/mage/cards/a/AkromaAngelOfWrath.java b/Mage.Sets/src/mage/cards/a/AkromaAngelOfWrath.java index ba2333e5780..eedb8e44539 100644 --- a/Mage.Sets/src/mage/cards/a/AkromaAngelOfWrath.java +++ b/Mage.Sets/src/mage/cards/a/AkromaAngelOfWrath.java @@ -25,12 +25,12 @@ public final class AkromaAngelOfWrath extends CardImpl { this.power = new MageInt(6); this.toughness = new MageInt(6); + // Flying, first strike, vigilance, trample, haste, protection from black and from red this.addAbility(FlyingAbility.getInstance()); this.addAbility(FirstStrikeAbility.getInstance()); this.addAbility(VigilanceAbility.getInstance()); this.addAbility(TrampleAbility.getInstance()); this.addAbility(HasteAbility.getInstance()); - // protection from black and from red this.addAbility(ProtectionAbility.from(ObjectColor.BLACK, ObjectColor.RED)); } diff --git a/Mage.Sets/src/mage/cards/p/PhyrexianSplicer.java b/Mage.Sets/src/mage/cards/p/PhyrexianSplicer.java new file mode 100644 index 00000000000..b466c5f0467 --- /dev/null +++ b/Mage.Sets/src/mage/cards/p/PhyrexianSplicer.java @@ -0,0 +1,242 @@ +package mage.cards.p; + +import mage.abilities.Ability; +import mage.abilities.common.SimpleActivatedAbility; +import mage.abilities.costs.Cost; +import mage.abilities.costs.CostImpl; +import mage.abilities.costs.common.TapSourceCost; +import mage.abilities.costs.mana.ManaCostsImpl; +import mage.abilities.effects.ContinuousEffect; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; +import mage.abilities.effects.common.continuous.LoseAbilityTargetEffect; +import mage.abilities.hint.Hint; +import mage.abilities.keyword.FirstStrikeAbility; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.keyword.ShadowAbility; +import mage.abilities.keyword.TrampleAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.choices.Choice; +import mage.choices.ChoiceImpl; +import mage.constants.CardType; +import mage.constants.Duration; +import mage.constants.Outcome; +import mage.constants.Zone; +import mage.filter.common.FilterCreaturePermanent; +import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.AbilityPredicate; +import mage.filter.predicate.other.AnotherTargetPredicate; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.game.stack.StackAbility; +import mage.players.Player; +import mage.target.TargetPermanent; +import mage.target.targetpointer.FixedTarget; +import mage.util.CardUtil; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author JayDi85 + */ +public final class PhyrexianSplicer extends CardImpl { + + static final FilterCreaturePermanent filterLose = new FilterCreaturePermanent("creature with the chosen ability"); + private static final FilterCreaturePermanent filterGain = new FilterCreaturePermanent("another target creature"); + + static { + filterLose.add(Predicates.or( + new AbilityPredicate(FlyingAbility.class), + new AbilityPredicate(FirstStrikeAbility.class), + new AbilityPredicate(TrampleAbility.class), + new AbilityPredicate(ShadowAbility.class) + )); + filterLose.add(new AnotherTargetPredicate(1)); + + filterGain.add(new AnotherTargetPredicate(2)); + } + + public PhyrexianSplicer(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.ARTIFACT}, "{2}"); + + // {2}, {T}, Choose flying, first strike, trample, or shadow: Until end of turn, target creature with the chosen ability loses it and another target creature gains it. + Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new PhyrexianSplicerEffect(), new ManaCostsImpl<>("{2}")); + ability.addCost(new TapSourceCost()); + ability.addCost(new PhyrexianSplicerChooseCost()); + ability.addTarget(new TargetPermanent(filterLose).withChooseHint("to lose ability").setTargetTag(1)); + ability.addTarget(new TargetPermanent(filterGain).withChooseHint("to gain ability").setTargetTag(2)); + ability.addHint(PhyrexianSplicerCardHint.instance); + this.addAbility(ability); + } + + private PhyrexianSplicer(final PhyrexianSplicer card) { + super(card); + } + + @Override + public PhyrexianSplicer copy() { + return new PhyrexianSplicer(this); + } +} + +class PhyrexianSplicerEffect extends OneShotEffect { + + PhyrexianSplicerEffect() { + super(Outcome.LoseAbility); + this.staticText = "Until end of turn, target creature with the chosen ability loses it and another target creature gains it."; + } + + private PhyrexianSplicerEffect(final PhyrexianSplicerEffect effect) { + super(effect); + } + + @Override + public PhyrexianSplicerEffect copy() { + return new PhyrexianSplicerEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Ability loseAbility = findChosenAbility(source); + if (loseAbility == null) { + return false; + } + + // If the target which is having the ability removed does not have that ability during the resolution of this + // effect, then this effect still grants the chosen ability. The reason is that the second target is still + // legal even if the first one is not. + // (2004-10-04) + + Permanent targetLose = game.getPermanent(source.getTargets().get(0).getFirstTarget()); + Permanent targetGain = game.getPermanent(source.getTargets().get(1).getFirstTarget()); + if (targetGain == null) { + return false; + } + + // lose + if (targetLose != null) { + ContinuousEffect effect = new LoseAbilityTargetEffect(loseAbility, Duration.EndOfTurn); + effect.setTargetPointer(new FixedTarget(targetLose, game)); + game.addEffect(effect, source); + } + + // gain + ContinuousEffect effect = new GainAbilityTargetEffect(loseAbility, Duration.EndOfTurn); + effect.setTargetPointer(new FixedTarget(targetGain, game)); + game.addEffect(effect, source); + + return true; + } + + static Ability findChosenAbility(Ability source) { + return CardUtil + .castStream(source.getCosts().stream(), PhyrexianSplicerChooseCost.class) + .map(PhyrexianSplicerChooseCost::getTargetedAbility) + .filter(Objects::nonNull) + .findFirst() + .orElse(null); + } +} + +class PhyrexianSplicerChooseCost extends CostImpl { + + private static final Map allChoices = new LinkedHashMap<>(); + + static { + allChoices.put("Flying", FlyingAbility.getInstance()); + allChoices.put("First Strike", FirstStrikeAbility.getInstance()); + allChoices.put("Trample", TrampleAbility.getInstance()); + allChoices.put("Shadow", ShadowAbility.getInstance()); + } + + Ability targetedAbility = null; + + public PhyrexianSplicerChooseCost() { + this.text = "Choose flying, first strike, trample, or shadow"; + } + + private PhyrexianSplicerChooseCost(final PhyrexianSplicerChooseCost cost) { + super(cost); + this.targetedAbility = cost.targetedAbility == null ? null : cost.targetedAbility.copy(); + } + + @Override + public boolean pay(Ability ability, Game game, Ability source, UUID controllerId, boolean noMana, Cost costToPay) { + this.paid = false; + this.targetedAbility = null; + + Permanent losePermanent = game.getPermanent(source.getTargets().get(0).getFirstTarget()); + Permanent gainPermanent = game.getPermanent(source.getTargets().get(1).getFirstTarget()); + Player controller = game.getPlayer(source.getControllerId()); + if (losePermanent == null || gainPermanent == null || controller == null) { + return false; + } + + // choose ability to lose + Set choices = allChoices.entrySet().stream() + .filter(entry -> losePermanent.hasAbility(entry.getValue(), game)) + .map(Map.Entry::getKey) + .collect(Collectors.toCollection(LinkedHashSet::new)); + + Ability chosenAbility; + if (choices.size() == 1) { + chosenAbility = allChoices.getOrDefault(choices.stream().findFirst().orElse(null), null); + } else { + Choice choice = new ChoiceImpl(true); + choice.setMessage("Choose ability to remove from " + losePermanent.getLogName() + " to " + gainPermanent.getLogName()); + choice.setChoices(choices); + controller.choose(Outcome.LoseAbility, choice, game); + chosenAbility = allChoices.getOrDefault(choice.getChoice(), null); + } + if (chosenAbility == null) { + return false; + } + + // all fine + this.targetedAbility = chosenAbility; + paid = true; + + // additional logs + game.informPlayers(controller.getLogName() + " chosen ability to lose and gain: " + + CardUtil.getTextWithFirstCharUpperCase(chosenAbility.getRule())); + + return true; + } + + @Override + public boolean canPay(Ability ability, Ability source, UUID controllerId, Game game) { + return true; + } + + @Override + public PhyrexianSplicerChooseCost copy() { + return new PhyrexianSplicerChooseCost(this); + } + + Ability getTargetedAbility() { + return this.targetedAbility; + } +} + +enum PhyrexianSplicerCardHint implements Hint { + instance; + + @Override + public String getText(Game game, Ability ability) { + // works on stack only + if (ability instanceof StackAbility) { + Ability loseAbility = PhyrexianSplicerEffect.findChosenAbility(((StackAbility) ability).getStackAbility()); + if (loseAbility != null) { + return String.format("Chosen ability to lose and gain: " + CardUtil.getTextWithFirstCharUpperCase(loseAbility.getRule())); + } + } + return ""; + } + + @Override + public PhyrexianSplicerCardHint copy() { + return this; + } +} diff --git a/Mage.Sets/src/mage/sets/Tempest.java b/Mage.Sets/src/mage/sets/Tempest.java index 67ddcc80a58..6c694b4f6c4 100644 --- a/Mage.Sets/src/mage/sets/Tempest.java +++ b/Mage.Sets/src/mage/sets/Tempest.java @@ -232,6 +232,7 @@ public final class Tempest extends ExpansionSet { cards.add(new SetCardInfo("Perish", 147, Rarity.UNCOMMON, mage.cards.p.Perish.class)); cards.add(new SetCardInfo("Phyrexian Grimoire", 301, Rarity.RARE, mage.cards.p.PhyrexianGrimoire.class)); cards.add(new SetCardInfo("Phyrexian Hulk", 302, Rarity.UNCOMMON, mage.cards.p.PhyrexianHulk.class)); + cards.add(new SetCardInfo("Phyrexian Splicer", 303, Rarity.UNCOMMON, mage.cards.p.PhyrexianSplicer.class)); cards.add(new SetCardInfo("Pincher Beetles", 244, Rarity.COMMON, mage.cards.p.PincherBeetles.class)); cards.add(new SetCardInfo("Pine Barrens", 321, Rarity.RARE, mage.cards.p.PineBarrens.class)); cards.add(new SetCardInfo("Pit Imp", 148, Rarity.COMMON, mage.cards.p.PitImp.class)); diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/PhyrexianSplicerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/PhyrexianSplicerTest.java new file mode 100644 index 00000000000..4deb381dec9 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/tmp/PhyrexianSplicerTest.java @@ -0,0 +1,43 @@ +package org.mage.test.cards.single.tmp; + +import mage.abilities.keyword.TrampleAbility; +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author JayDi85 + */ +public class PhyrexianSplicerTest extends CardTestPlayerBase { + + @Test + public void test_Normal() { + // {2}, {T}, Choose flying, first strike, trample, or shadow: Until end of turn, target creature with the + // chosen ability loses it and another target creature gains it. + addCard(Zone.BATTLEFIELD, playerA, "Phyrexian Splicer", 1); // {2} + addCard(Zone.BATTLEFIELD, playerA, "Mountain", 2); + // + // Flying, first strike, vigilance, trample, haste, protection from black and from red + addCard(Zone.BATTLEFIELD, playerA, "Akroma, Angel of Wrath"); + // Shadow + addCard(Zone.BATTLEFIELD, playerA, "Augur il-Vec"); + + checkAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Wrath", TrampleAbility.class, true); + checkAbility("before", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Augur il-Vec", TrampleAbility.class, false); + + // move trample from one to another + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{2}, {T}, Choose"); + addTarget(playerA, "Akroma, Angel of Wrath"); // loose + addTarget(playerA, "Augur il-Vec"); // gain + setChoice(playerA, "Trample"); + waitStackResolved(1, PhaseStep.PRECOMBAT_MAIN); + + checkAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Akroma, Angel of Wrath", TrampleAbility.class, false); + checkAbility("after", 1, PhaseStep.PRECOMBAT_MAIN, playerA, "Augur il-Vec", TrampleAbility.class, true); + + setStrictChooseMode(true); + setStopAt(1, PhaseStep.END_TURN); + execute(); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index cf90889e0ea..49470e3d21b 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -1125,7 +1125,7 @@ public class TestPlayer implements Player { } private Permanent findPermanentWithAssert(PlayerAction action, Game game, Player player, String cardName) { - for (Permanent perm : game.getBattlefield().getAllPermanents()) { + for (Permanent perm : game.getBattlefield().getAllActivePermanents(player.getId())) { // need by controller if (!perm.getControllerId().equals(player.getId())) { continue; @@ -1139,6 +1139,9 @@ public class TestPlayer implements Player { // all fine return perm; } + printStart(game, "Permanents of " + player.getName()); + printPermanents(game, game.getBattlefield().getAllActivePermanents(player.getId()), this); + printEnd(); Assert.fail(action.getActionName() + " - can't find permanent to check: " + cardName); return null; } From e1ab0be505f95edb09ef578975425431371893a6 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 20 Sep 2024 17:23:14 +0400 Subject: [PATCH 61/62] images: fixed wrong miss image message in card hints for dungeons and other command objects (part of #12274); --- Mage.Common/src/main/java/mage/view/StackAbilityView.java | 2 +- .../src/test/java/org/mage/test/player/TestPlayer.java | 7 ------- Mage/src/main/java/mage/constants/MageObjectType.java | 3 ++- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/Mage.Common/src/main/java/mage/view/StackAbilityView.java b/Mage.Common/src/main/java/mage/view/StackAbilityView.java index 5c935cf674d..cdbd596d3da 100644 --- a/Mage.Common/src/main/java/mage/view/StackAbilityView.java +++ b/Mage.Common/src/main/java/mage/view/StackAbilityView.java @@ -32,7 +32,7 @@ public class StackAbilityView extends CardView { public StackAbilityView(Game game, StackAbility ability, String sourceName, MageObject sourceObject, CardView sourceView) { this.id = ability.getId(); - this.mageObjectType = MageObjectType.ABILITY_STACK; + this.mageObjectType = sourceView.getMageObjectType().isUseTokensRepository() ? MageObjectType.ABILITY_STACK_FROM_TOKEN : MageObjectType.ABILITY_STACK_FROM_CARD; this.abilityType = ability.getStackAbility().getAbilityType(); this.sourceCard = sourceView; this.sourceCard.setMageObjectType(mageObjectType); diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 49470e3d21b..b9c5d2c9e1a 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -1126,17 +1126,10 @@ public class TestPlayer implements Player { private Permanent findPermanentWithAssert(PlayerAction action, Game game, Player player, String cardName) { for (Permanent perm : game.getBattlefield().getAllActivePermanents(player.getId())) { - // need by controller - if (!perm.getControllerId().equals(player.getId())) { - continue; - } - // need by alias or by name if (!hasObjectTargetNameOrAlias(perm, cardName)) { continue; } - - // all fine return perm; } printStart(game, "Permanents of " + player.getName()); diff --git a/Mage/src/main/java/mage/constants/MageObjectType.java b/Mage/src/main/java/mage/constants/MageObjectType.java index f0ff79b9671..b870492232b 100644 --- a/Mage/src/main/java/mage/constants/MageObjectType.java +++ b/Mage/src/main/java/mage/constants/MageObjectType.java @@ -43,7 +43,8 @@ package mage.constants; * @author LevelX2 */ public enum MageObjectType { - ABILITY_STACK("Ability on the Stack", false, false, false), + ABILITY_STACK_FROM_CARD("Ability on the Stack", false, false, false), + ABILITY_STACK_FROM_TOKEN("Ability on the Stack", false, false, true), CARD("Card", false, true, false), COPY_CARD("Copy of a Card", false, true, false), TOKEN("Token", true, true, true), From bf2c4cac159cde4556b5eea578cbf605dc46f467 Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Fri, 20 Sep 2024 17:53:38 +0400 Subject: [PATCH 62/62] Venture into abilities - added card hints about current dungeon and room (part of #12274); --- .../src/mage/cards/a/AcererakTheArchlich.java | 5 ++- Mage.Sets/src/mage/cards/b/BarTheGate.java | 2 + .../mage/cards/b/BarrowinOfClanUndurr.java | 4 +- .../src/mage/cards/c/ClatteringSkeletons.java | 4 +- .../src/mage/cards/c/CloisterGargoyle.java | 4 +- Mage.Sets/src/mage/cards/d/DelversTorch.java | 4 +- .../src/mage/cards/d/DisplacerBeast.java | 4 +- .../src/mage/cards/d/DungeonDescent.java | 2 + Mage.Sets/src/mage/cards/d/DungeonMap.java | 2 + .../src/mage/cards/e/EccentricApprentice.java | 4 +- .../src/mage/cards/e/EllywickTumblestrum.java | 4 +- Mage.Sets/src/mage/cards/f/FatesReversal.java | 2 + .../src/mage/cards/f/FiftyFeetOfRope.java | 2 + Mage.Sets/src/mage/cards/f/FindThePath.java | 4 +- Mage.Sets/src/mage/cards/f/Fly.java | 2 + Mage.Sets/src/mage/cards/i/ImmovableRod.java | 4 +- .../src/mage/cards/i/IntrepidOutlander.java | 4 +- .../src/mage/cards/k/KeenEaredSentry.java | 3 +- Mage.Sets/src/mage/cards/k/KickInTheDoor.java | 2 + .../src/mage/cards/m/MidnightPathlighter.java | 4 +- .../mage/cards/n/NadaarSelflessPaladin.java | 4 +- Mage.Sets/src/mage/cards/p/PlanarAlly.java | 4 +- .../src/mage/cards/p/PrecipitousDrop.java | 4 +- Mage.Sets/src/mage/cards/r/RadiantSolar.java | 3 +- Mage.Sets/src/mage/cards/r/RangersHawk.java | 2 + Mage.Sets/src/mage/cards/s/SecretDoor.java | 3 +- .../mage/cards/s/SefrisOfTheHiddenWays.java | 3 +- .../src/mage/cards/s/ShortcutSeeker.java | 3 +- .../mage/cards/t/ThoroughInvestigation.java | 4 +- .../mage/cards/t/TriumphantAdventurer.java | 4 +- .../mage/cards/v/VarisSilverymoonRanger.java | 3 +- .../src/mage/cards/v/VeteranDungeoneer.java | 4 +- .../src/mage/cards/w/WanderingTroubadour.java | 3 +- .../src/mage/cards/y/YouFindACursedIdol.java | 2 + .../src/mage/cards/y/YuanTiFangBlade.java | 3 +- Mage.Sets/src/mage/cards/y/YuanTiMalison.java | 4 +- .../src/mage/cards/z/ZaltoFireGiantDuke.java | 4 +- Mage.Sets/src/mage/cards/z/ZombieOgre.java | 3 +- .../java/mage/verify/VerifyCardDataTest.java | 2 + .../hint/common/CurrentDungeonHint.java | 43 +++++++++++++++++++ .../java/mage/designations/Initiative.java | 2 + Mage/src/main/java/mage/game/Game.java | 9 +++- Mage/src/main/java/mage/game/GameImpl.java | 4 +- 43 files changed, 153 insertions(+), 33 deletions(-) create mode 100644 Mage/src/main/java/mage/abilities/hint/common/CurrentDungeonHint.java diff --git a/Mage.Sets/src/mage/cards/a/AcererakTheArchlich.java b/Mage.Sets/src/mage/cards/a/AcererakTheArchlich.java index 75ff120cfff..11eda4d0318 100644 --- a/Mage.Sets/src/mage/cards/a/AcererakTheArchlich.java +++ b/Mage.Sets/src/mage/cards/a/AcererakTheArchlich.java @@ -10,6 +10,7 @@ import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.ReturnToHandSourceEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -50,7 +51,9 @@ public final class AcererakTheArchlich extends CardImpl { "to its owner's hand and venture into the dungeon." ); ability.addEffect(new VentureIntoTheDungeonEffect()); - this.addAbility(ability.addHint(CompletedDungeonCondition.getHint()), new CompletedDungeonWatcher()); + ability.addHint(CurrentDungeonHint.instance); + ability.addHint(CompletedDungeonCondition.getHint()); + this.addAbility(ability, new CompletedDungeonWatcher()); // Whenever Acererak the Archlich attacks, for each opponent, you create a 2/2 black Zombie creature token unless that player sacrifices a creature. this.addAbility(new AttacksTriggeredAbility(new AcererakTheArchlichEffect())); diff --git a/Mage.Sets/src/mage/cards/b/BarTheGate.java b/Mage.Sets/src/mage/cards/b/BarTheGate.java index 5abaaa4d337..498a85471f6 100644 --- a/Mage.Sets/src/mage/cards/b/BarTheGate.java +++ b/Mage.Sets/src/mage/cards/b/BarTheGate.java @@ -4,6 +4,7 @@ import java.util.UUID; import mage.abilities.effects.common.CounterTargetEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -33,6 +34,7 @@ public final class BarTheGate extends CardImpl { this.getSpellAbility().addTarget(new TargetSpell(filter)); this.getSpellAbility().addEffect(new CounterTargetEffect()); this.getSpellAbility().addEffect(new VentureIntoTheDungeonEffect()); + this.getSpellAbility().addHint(CurrentDungeonHint.instance); } private BarTheGate(final BarTheGate card) { diff --git a/Mage.Sets/src/mage/cards/b/BarrowinOfClanUndurr.java b/Mage.Sets/src/mage/cards/b/BarrowinOfClanUndurr.java index d5a478178b9..4179fe20e0f 100644 --- a/Mage.Sets/src/mage/cards/b/BarrowinOfClanUndurr.java +++ b/Mage.Sets/src/mage/cards/b/BarrowinOfClanUndurr.java @@ -8,6 +8,7 @@ import mage.abilities.condition.common.CompletedDungeonCondition; import mage.abilities.decorator.ConditionalOneShotEffect; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -44,7 +45,8 @@ public final class BarrowinOfClanUndurr extends CardImpl { this.toughness = new MageInt(3); // When Barrowin of Clan Undurr enters the battlefield, venture into the dungeon. - this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect()) + .addHint(CurrentDungeonHint.instance)); // Whenever Barrowin of Clan Undurr attacks, return up to one creature card with mana value 3 or less from your graveyard to the battlefield if you've completed a dungeon. Ability ability = new AttacksTriggeredAbility(new ConditionalOneShotEffect( diff --git a/Mage.Sets/src/mage/cards/c/ClatteringSkeletons.java b/Mage.Sets/src/mage/cards/c/ClatteringSkeletons.java index ca58abf9247..54ea1ebc4a1 100644 --- a/Mage.Sets/src/mage/cards/c/ClatteringSkeletons.java +++ b/Mage.Sets/src/mage/cards/c/ClatteringSkeletons.java @@ -3,6 +3,7 @@ package mage.cards.c; import mage.MageInt; import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -23,7 +24,8 @@ public final class ClatteringSkeletons extends CardImpl { this.toughness = new MageInt(3); // When Clattering Skeletons dies, venture into the dungeon. - this.addAbility(new DiesSourceTriggeredAbility(new VentureIntoTheDungeonEffect())); + this.addAbility(new DiesSourceTriggeredAbility(new VentureIntoTheDungeonEffect()) + .addHint(CurrentDungeonHint.instance)); } private ClatteringSkeletons(final ClatteringSkeletons card) { diff --git a/Mage.Sets/src/mage/cards/c/CloisterGargoyle.java b/Mage.Sets/src/mage/cards/c/CloisterGargoyle.java index f524a0d514e..accffe32c7c 100644 --- a/Mage.Sets/src/mage/cards/c/CloisterGargoyle.java +++ b/Mage.Sets/src/mage/cards/c/CloisterGargoyle.java @@ -9,6 +9,7 @@ import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostSourceEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -32,7 +33,8 @@ public final class CloisterGargoyle extends CardImpl { this.toughness = new MageInt(4); // When Cloister Gargoyle enters the battlefield, venture into the dungeon. - this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect()) + .addHint(CurrentDungeonHint.instance)); // As long as you've completed a dungeon, Cloister Gargoyle gets +3/+0 and has flying. Ability ability = new SimpleStaticAbility(new ConditionalContinuousEffect( diff --git a/Mage.Sets/src/mage/cards/d/DelversTorch.java b/Mage.Sets/src/mage/cards/d/DelversTorch.java index 6f16ceb6485..9bddb32a41b 100644 --- a/Mage.Sets/src/mage/cards/d/DelversTorch.java +++ b/Mage.Sets/src/mage/cards/d/DelversTorch.java @@ -6,6 +6,7 @@ import mage.abilities.common.AttacksAttachedTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.BoostEquippedEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.EquipAbility; import mage.constants.SubType; import mage.cards.CardImpl; @@ -27,7 +28,8 @@ public final class DelversTorch extends CardImpl { this.addAbility(new SimpleStaticAbility(new BoostEquippedEffect(1, 1))); // Whenever equipped creature attacks, venture into the dungeon. - this.addAbility(new AttacksAttachedTriggeredAbility(new VentureIntoTheDungeonEffect())); + this.addAbility(new AttacksAttachedTriggeredAbility(new VentureIntoTheDungeonEffect()) + .addHint(CurrentDungeonHint.instance)); // Equip {3} this.addAbility(new EquipAbility(3)); diff --git a/Mage.Sets/src/mage/cards/d/DisplacerBeast.java b/Mage.Sets/src/mage/cards/d/DisplacerBeast.java index a93e64427a8..1f2adbf595a 100644 --- a/Mage.Sets/src/mage/cards/d/DisplacerBeast.java +++ b/Mage.Sets/src/mage/cards/d/DisplacerBeast.java @@ -6,6 +6,7 @@ import mage.abilities.common.SimpleActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.ReturnToHandSourceEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -27,7 +28,8 @@ public final class DisplacerBeast extends CardImpl { this.toughness = new MageInt(2); // When Displacer Beast enters the battlefield, venture into the dungeon. - this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect()) + .addHint(CurrentDungeonHint.instance)); // Displacement — {3}{U}: Return Displacer Beast to its owner's hand. this.addAbility(new SimpleActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/d/DungeonDescent.java b/Mage.Sets/src/mage/cards/d/DungeonDescent.java index 38d9030d41e..265a5dde952 100644 --- a/Mage.Sets/src/mage/cards/d/DungeonDescent.java +++ b/Mage.Sets/src/mage/cards/d/DungeonDescent.java @@ -9,6 +9,7 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapTargetCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.mana.ColorlessManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -45,6 +46,7 @@ public final class DungeonDescent extends CardImpl { Ability ability = new ActivateAsSorceryActivatedAbility(new VentureIntoTheDungeonEffect(), new GenericManaCost(4)); ability.addCost(new TapSourceCost()); ability.addCost(new TapTargetCost(new TargetControlledCreaturePermanent(filter))); + ability.addHint(CurrentDungeonHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/d/DungeonMap.java b/Mage.Sets/src/mage/cards/d/DungeonMap.java index 9a264aa828f..4aea25494da 100644 --- a/Mage.Sets/src/mage/cards/d/DungeonMap.java +++ b/Mage.Sets/src/mage/cards/d/DungeonMap.java @@ -5,6 +5,7 @@ import mage.abilities.common.ActivateAsSorceryActivatedAbility; import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.mana.ColorlessManaAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -28,6 +29,7 @@ public final class DungeonMap extends CardImpl { new VentureIntoTheDungeonEffect(), new GenericManaCost(3) ); ability.addCost(new TapSourceCost()); + ability.addHint(CurrentDungeonHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/e/EccentricApprentice.java b/Mage.Sets/src/mage/cards/e/EccentricApprentice.java index 0e8155a41a9..457d86a2d46 100644 --- a/Mage.Sets/src/mage/cards/e/EccentricApprentice.java +++ b/Mage.Sets/src/mage/cards/e/EccentricApprentice.java @@ -8,6 +8,7 @@ import mage.abilities.condition.common.CompletedDungeonCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -36,7 +37,8 @@ public final class EccentricApprentice extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // When Eccentric Apprentice enters the battlefield, venture into the dungeon. - this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect()) + .addHint(CurrentDungeonHint.instance)); // At the beginning of combat on your turn, if you've completed a dungeon, up to one target creature becomes a Bird with base power and toughness 1/1 and flying until end of turn. Ability ability = new ConditionalInterveningIfTriggeredAbility( diff --git a/Mage.Sets/src/mage/cards/e/EllywickTumblestrum.java b/Mage.Sets/src/mage/cards/e/EllywickTumblestrum.java index 57d83603cfa..89517ab492f 100644 --- a/Mage.Sets/src/mage/cards/e/EllywickTumblestrum.java +++ b/Mage.Sets/src/mage/cards/e/EllywickTumblestrum.java @@ -5,6 +5,7 @@ import mage.abilities.LoyaltyAbility; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.GetEmblemEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.*; import mage.constants.*; import mage.filter.StaticFilters; @@ -29,7 +30,8 @@ public final class EllywickTumblestrum extends CardImpl { this.setStartingLoyalty(4); // +1: Venture into the dungeon. - this.addAbility(new LoyaltyAbility(new VentureIntoTheDungeonEffect(), 1)); + this.addAbility(new LoyaltyAbility(new VentureIntoTheDungeonEffect(), 1) + .addHint(CurrentDungeonHint.instance)); // −2: Look at the top six cards of your library. You may reveal a creature card from among them and put it into your hand. If it's legendary, you gain 3 life. Put the rest on the bottom of your library in a random order. this.addAbility(new LoyaltyAbility(new EllywickTumblestrumEffect(), -2)); diff --git a/Mage.Sets/src/mage/cards/f/FatesReversal.java b/Mage.Sets/src/mage/cards/f/FatesReversal.java index 81beb254959..b501db13cd0 100644 --- a/Mage.Sets/src/mage/cards/f/FatesReversal.java +++ b/Mage.Sets/src/mage/cards/f/FatesReversal.java @@ -2,6 +2,7 @@ package mage.cards.f; import mage.abilities.effects.common.ReturnFromGraveyardToHandTargetEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -24,6 +25,7 @@ public final class FatesReversal extends CardImpl { 0, 1, StaticFilters.FILTER_CARD_CREATURE_YOUR_GRAVEYARD )); this.getSpellAbility().addEffect(new VentureIntoTheDungeonEffect().concatBy(".")); + this.getSpellAbility().addHint(CurrentDungeonHint.instance); } private FatesReversal(final FatesReversal card) { diff --git a/Mage.Sets/src/mage/cards/f/FiftyFeetOfRope.java b/Mage.Sets/src/mage/cards/f/FiftyFeetOfRope.java index 3145447945a..2d7ff655e9c 100644 --- a/Mage.Sets/src/mage/cards/f/FiftyFeetOfRope.java +++ b/Mage.Sets/src/mage/cards/f/FiftyFeetOfRope.java @@ -10,6 +10,7 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.DontUntapInControllersNextUntapStepTargetEffect; import mage.abilities.effects.common.combat.CantBlockTargetEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -44,6 +45,7 @@ public final class FiftyFeetOfRope extends CardImpl { // Rappel Down — {4}, {T}: Venture into the dungeon. Activate only as a sorcery. ability = new ActivateAsSorceryActivatedAbility(new VentureIntoTheDungeonEffect(), new ManaCostsImpl<>("{4}")); ability.addCost(new TapSourceCost()); + ability.addHint(CurrentDungeonHint.instance); this.addAbility(ability.withFlavorWord("Rappel Down")); } diff --git a/Mage.Sets/src/mage/cards/f/FindThePath.java b/Mage.Sets/src/mage/cards/f/FindThePath.java index 9f3464fb630..27478871c66 100644 --- a/Mage.Sets/src/mage/cards/f/FindThePath.java +++ b/Mage.Sets/src/mage/cards/f/FindThePath.java @@ -8,6 +8,7 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.EnchantAbility; import mage.abilities.mana.SimpleManaAbility; import mage.cards.CardImpl; @@ -36,7 +37,8 @@ public final class FindThePath extends CardImpl { this.addAbility(ability); // When Find the Path enters the battlefield, venture into the dungeon. - this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect()) + .addHint(CurrentDungeonHint.instance)); // Enchanted land has "{T}: Add {G}{G}." this.addAbility(new SimpleStaticAbility(new GainAbilityAttachedEffect( diff --git a/Mage.Sets/src/mage/cards/f/Fly.java b/Mage.Sets/src/mage/cards/f/Fly.java index b45ef4297eb..5eec8db2729 100644 --- a/Mage.Sets/src/mage/cards/f/Fly.java +++ b/Mage.Sets/src/mage/cards/f/Fly.java @@ -6,6 +6,7 @@ import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.continuous.GainAbilityAttachedEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.FlyingAbility; import mage.constants.AttachmentType; import mage.constants.SubType; @@ -43,6 +44,7 @@ public final class Fly extends CardImpl { new DealsCombatDamageToAPlayerTriggeredAbility(new VentureIntoTheDungeonEffect(), false), AttachmentType.AURA).setText("and \"Whenever this creature deals combat damage to a player, venture into the dungeon.\"") ); + ability.addHint(CurrentDungeonHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/i/ImmovableRod.java b/Mage.Sets/src/mage/cards/i/ImmovableRod.java index 9b07b152a39..49cc0385d2d 100644 --- a/Mage.Sets/src/mage/cards/i/ImmovableRod.java +++ b/Mage.Sets/src/mage/cards/i/ImmovableRod.java @@ -8,6 +8,7 @@ import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.ContinuousEffectImpl; import mage.abilities.effects.RestrictionEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.InspiredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -38,7 +39,8 @@ public final class ImmovableRod extends CardImpl { this.addAbility(new SkipUntapOptionalAbility()); // Whenever Immovable Rod becomes untapped, venture into the dungeon. - this.addAbility(new InspiredAbility(new VentureIntoTheDungeonEffect(), false, false)); + this.addAbility(new InspiredAbility(new VentureIntoTheDungeonEffect(), false, false) + .addHint(CurrentDungeonHint.instance)); // {3}{W}, {T}: For as long as Immovable Rod remains tapped, another target permanent loses all abilities and can't attack or block. Ability ability = new SimpleActivatedAbility(new ImmovableRodAbilityEffect(), new ManaCostsImpl<>("{3}{W}")); diff --git a/Mage.Sets/src/mage/cards/i/IntrepidOutlander.java b/Mage.Sets/src/mage/cards/i/IntrepidOutlander.java index 1d148762908..be3dfbcc45c 100644 --- a/Mage.Sets/src/mage/cards/i/IntrepidOutlander.java +++ b/Mage.Sets/src/mage/cards/i/IntrepidOutlander.java @@ -2,6 +2,7 @@ package mage.cards.i; import mage.MageInt; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.PackTacticsAbility; import mage.abilities.keyword.ReachAbility; import mage.cards.CardImpl; @@ -28,7 +29,8 @@ public final class IntrepidOutlander extends CardImpl { this.addAbility(ReachAbility.getInstance()); // Pack tactics — Whenever Intrepid Outlander attacks, if you attacked with creatures with total power 6 or greater this combat, venture into the dungeon. - this.addAbility(new PackTacticsAbility(new VentureIntoTheDungeonEffect())); + this.addAbility(new PackTacticsAbility(new VentureIntoTheDungeonEffect()) + .addHint(CurrentDungeonHint.instance)); } private IntrepidOutlander(final IntrepidOutlander card) { diff --git a/Mage.Sets/src/mage/cards/k/KeenEaredSentry.java b/Mage.Sets/src/mage/cards/k/KeenEaredSentry.java index a2025f4ff5b..21c170a4ecc 100644 --- a/Mage.Sets/src/mage/cards/k/KeenEaredSentry.java +++ b/Mage.Sets/src/mage/cards/k/KeenEaredSentry.java @@ -6,6 +6,7 @@ import mage.abilities.Ability; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.ContinuousRuleModifyingEffectImpl; import mage.abilities.effects.common.continuous.GainAbilityControllerEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.HexproofAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -35,7 +36,7 @@ public final class KeenEaredSentry extends CardImpl { this.addAbility(new SimpleStaticAbility(new GainAbilityControllerEffect(HexproofAbility.getInstance()))); // Your opponents can't venture into the dungeon more than once each turn. - this.addAbility(new SimpleStaticAbility(new KeenEaredSentryEffect()), new KeenEaredSentryWatcher()); + this.addAbility(new SimpleStaticAbility(new KeenEaredSentryEffect()).addHint(CurrentDungeonHint.instance), new KeenEaredSentryWatcher()); } private KeenEaredSentry(final KeenEaredSentry card) { diff --git a/Mage.Sets/src/mage/cards/k/KickInTheDoor.java b/Mage.Sets/src/mage/cards/k/KickInTheDoor.java index 75bb61a535e..2810a08ba31 100644 --- a/Mage.Sets/src/mage/cards/k/KickInTheDoor.java +++ b/Mage.Sets/src/mage/cards/k/KickInTheDoor.java @@ -6,6 +6,7 @@ import mage.abilities.effects.common.combat.CantBeBlockedTargetEffect; import mage.abilities.effects.common.continuous.GainAbilityTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.HasteAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -40,6 +41,7 @@ public final class KickInTheDoor extends CardImpl { .setText("and can't be blocked by Walls this turn") ); this.getSpellAbility().addEffect(new VentureIntoTheDungeonEffect()); + this.getSpellAbility().addHint(CurrentDungeonHint.instance); } private KickInTheDoor(final KickInTheDoor card) { diff --git a/Mage.Sets/src/mage/cards/m/MidnightPathlighter.java b/Mage.Sets/src/mage/cards/m/MidnightPathlighter.java index 8699dbf7eb2..827584eb51b 100644 --- a/Mage.Sets/src/mage/cards/m/MidnightPathlighter.java +++ b/Mage.Sets/src/mage/cards/m/MidnightPathlighter.java @@ -5,6 +5,7 @@ import mage.abilities.common.DealCombatDamageControlledTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.effects.common.combat.CantBeBlockedByCreaturesAllEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -44,7 +45,8 @@ public final class MidnightPathlighter extends CardImpl { ))); // Whenever one or more creatures you control deal combat damage to a player, venture into the dungeon. - this.addAbility(new DealCombatDamageControlledTriggeredAbility(new VentureIntoTheDungeonEffect())); + this.addAbility(new DealCombatDamageControlledTriggeredAbility(new VentureIntoTheDungeonEffect()) + .addHint(CurrentDungeonHint.instance)); } private MidnightPathlighter(final MidnightPathlighter card) { diff --git a/Mage.Sets/src/mage/cards/n/NadaarSelflessPaladin.java b/Mage.Sets/src/mage/cards/n/NadaarSelflessPaladin.java index 5d18921c76e..1f164dea3fc 100644 --- a/Mage.Sets/src/mage/cards/n/NadaarSelflessPaladin.java +++ b/Mage.Sets/src/mage/cards/n/NadaarSelflessPaladin.java @@ -7,6 +7,7 @@ import mage.abilities.condition.common.CompletedDungeonCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.BoostControlledEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.VigilanceAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -36,7 +37,8 @@ public final class NadaarSelflessPaladin extends CardImpl { this.addAbility(VigilanceAbility.getInstance()); // Whenever Nadaar, Selfless Paladin enters the battlefield or attacks, venture into the dungeon. - this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new VentureIntoTheDungeonEffect())); + this.addAbility(new EntersBattlefieldOrAttacksSourceTriggeredAbility(new VentureIntoTheDungeonEffect()) + .addHint(CurrentDungeonHint.instance)); // Other creatures you control get +1/+1 as long as you've completed a dungeon. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( diff --git a/Mage.Sets/src/mage/cards/p/PlanarAlly.java b/Mage.Sets/src/mage/cards/p/PlanarAlly.java index 77bacc0f9e5..d4bfad8d524 100644 --- a/Mage.Sets/src/mage/cards/p/PlanarAlly.java +++ b/Mage.Sets/src/mage/cards/p/PlanarAlly.java @@ -3,6 +3,7 @@ package mage.cards.p; import mage.MageInt; import mage.abilities.common.AttacksTriggeredAbility; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -27,7 +28,8 @@ public final class PlanarAlly extends CardImpl { this.addAbility(FlyingAbility.getInstance()); // Whenever Planar Ally attacks, venture into the dungeon. - this.addAbility(new AttacksTriggeredAbility(new VentureIntoTheDungeonEffect(), false)); + this.addAbility(new AttacksTriggeredAbility(new VentureIntoTheDungeonEffect(), false) + .addHint(CurrentDungeonHint.instance)); } private PlanarAlly(final PlanarAlly card) { diff --git a/Mage.Sets/src/mage/cards/p/PrecipitousDrop.java b/Mage.Sets/src/mage/cards/p/PrecipitousDrop.java index daf04fd43e1..f4f12f45a01 100644 --- a/Mage.Sets/src/mage/cards/p/PrecipitousDrop.java +++ b/Mage.Sets/src/mage/cards/p/PrecipitousDrop.java @@ -8,6 +8,7 @@ import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.AttachEffect; import mage.abilities.effects.common.continuous.BoostEnchantedEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.EnchantAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -38,7 +39,8 @@ public final class PrecipitousDrop extends CardImpl { this.addAbility(ability); // When Precipitous Drop enters the battlefield, venture into the dungeon. - this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect()) + .addHint(CurrentDungeonHint.instance)); // Enchanted creature gets -2/-2. It gets -5/-5 instead as long as you've completed a dungeon. this.addAbility(new SimpleStaticAbility(new ConditionalContinuousEffect( diff --git a/Mage.Sets/src/mage/cards/r/RadiantSolar.java b/Mage.Sets/src/mage/cards/r/RadiantSolar.java index fab09162a6e..609b33c0b4a 100644 --- a/Mage.Sets/src/mage/cards/r/RadiantSolar.java +++ b/Mage.Sets/src/mage/cards/r/RadiantSolar.java @@ -8,6 +8,7 @@ import mage.abilities.costs.common.DiscardSourceCost; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.GainLifeEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.LifelinkAbility; import mage.cards.CardImpl; @@ -48,7 +49,7 @@ public final class RadiantSolar extends CardImpl { // Whenever Radiant Solar or another nontoken creature you control enters, venture into the dungeon. this.addAbility(new EntersBattlefieldThisOrAnotherTriggeredAbility( new VentureIntoTheDungeonEffect(), filter, false, true - )); + ).addHint(CurrentDungeonHint.instance)); // {W}, Discard Radiant Solar: Venture into the dungeon and you gain 3 life. Ability ability = new SimpleActivatedAbility( diff --git a/Mage.Sets/src/mage/cards/r/RangersHawk.java b/Mage.Sets/src/mage/cards/r/RangersHawk.java index b0d4157bbe3..e2b3bd2c173 100644 --- a/Mage.Sets/src/mage/cards/r/RangersHawk.java +++ b/Mage.Sets/src/mage/cards/r/RangersHawk.java @@ -7,6 +7,7 @@ import mage.abilities.costs.common.TapSourceCost; import mage.abilities.costs.common.TapTargetCost; import mage.abilities.costs.mana.GenericManaCost; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -49,6 +50,7 @@ public final class RangersHawk extends CardImpl { ); ability.addCost(new TapSourceCost()); ability.addCost(new TapTargetCost(new TargetControlledPermanent(filter))); + ability.addHint(CurrentDungeonHint.instance); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/s/SecretDoor.java b/Mage.Sets/src/mage/cards/s/SecretDoor.java index 524bff4490f..acb9af22fc7 100644 --- a/Mage.Sets/src/mage/cards/s/SecretDoor.java +++ b/Mage.Sets/src/mage/cards/s/SecretDoor.java @@ -4,6 +4,7 @@ import mage.MageInt; import mage.abilities.common.ActivateAsSorceryActivatedAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.DefenderAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -30,7 +31,7 @@ public final class SecretDoor extends CardImpl { // {4}{U}: Venture into the dungeon. Activate only as a sorcery. this.addAbility(new ActivateAsSorceryActivatedAbility( new VentureIntoTheDungeonEffect(), new ManaCostsImpl<>("{4}{U}") - )); + ).addHint(CurrentDungeonHint.instance)); } private SecretDoor(final SecretDoor card) { diff --git a/Mage.Sets/src/mage/cards/s/SefrisOfTheHiddenWays.java b/Mage.Sets/src/mage/cards/s/SefrisOfTheHiddenWays.java index b68dcce7311..c84c56f4333 100644 --- a/Mage.Sets/src/mage/cards/s/SefrisOfTheHiddenWays.java +++ b/Mage.Sets/src/mage/cards/s/SefrisOfTheHiddenWays.java @@ -6,6 +6,7 @@ import mage.abilities.common.CompletedDungeonTriggeredAbility; import mage.abilities.common.PutCardIntoGraveFromAnywhereAllTriggeredAbility; import mage.abilities.effects.common.ReturnFromGraveyardToBattlefieldTargetEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -38,7 +39,7 @@ public final class SefrisOfTheHiddenWays extends CardImpl { // Whenever one or more creature cards are put into your graveyard from anywhere, venture into the dungeon. This ability triggers only once each turn. this.addAbility(new PutCardIntoGraveFromAnywhereAllTriggeredAbility( new VentureIntoTheDungeonEffect(), false, filter, TargetController.YOU - ).setTriggersLimitEachTurn(1)); + ).setTriggersLimitEachTurn(1).addHint(CurrentDungeonHint.instance)); // Create Undead — Whenever you complete a dungeon, return target creature card from your graveyard to the battlefield. Ability ability = new CompletedDungeonTriggeredAbility(new ReturnFromGraveyardToBattlefieldTargetEffect()); diff --git a/Mage.Sets/src/mage/cards/s/ShortcutSeeker.java b/Mage.Sets/src/mage/cards/s/ShortcutSeeker.java index 755321924ca..4b987104cfe 100644 --- a/Mage.Sets/src/mage/cards/s/ShortcutSeeker.java +++ b/Mage.Sets/src/mage/cards/s/ShortcutSeeker.java @@ -3,6 +3,7 @@ package mage.cards.s; import mage.MageInt; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -26,7 +27,7 @@ public final class ShortcutSeeker extends CardImpl { // Whenever Shortcut Seeker deals combat damage to a player, venture into the dungeon. this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( new VentureIntoTheDungeonEffect(), false - )); + ).addHint(CurrentDungeonHint.instance)); } private ShortcutSeeker(final ShortcutSeeker card) { diff --git a/Mage.Sets/src/mage/cards/t/ThoroughInvestigation.java b/Mage.Sets/src/mage/cards/t/ThoroughInvestigation.java index af6067f8b5d..37091da9657 100644 --- a/Mage.Sets/src/mage/cards/t/ThoroughInvestigation.java +++ b/Mage.Sets/src/mage/cards/t/ThoroughInvestigation.java @@ -4,6 +4,7 @@ import mage.abilities.common.AttacksWithCreaturesTriggeredAbility; import mage.abilities.common.SacrificePermanentTriggeredAbility; import mage.abilities.effects.keyword.InvestigateEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -23,7 +24,8 @@ public final class ThoroughInvestigation extends CardImpl { this.addAbility(new AttacksWithCreaturesTriggeredAbility(new InvestigateEffect(), 1)); // Whenever you sacrifice a Clue, venture into the dungeon. - this.addAbility(new SacrificePermanentTriggeredAbility(new VentureIntoTheDungeonEffect(), StaticFilters.FILTER_CONTROLLED_CLUE)); + this.addAbility(new SacrificePermanentTriggeredAbility(new VentureIntoTheDungeonEffect(), StaticFilters.FILTER_CONTROLLED_CLUE) + .addHint(CurrentDungeonHint.instance)); } private ThoroughInvestigation(final ThoroughInvestigation card) { diff --git a/Mage.Sets/src/mage/cards/t/TriumphantAdventurer.java b/Mage.Sets/src/mage/cards/t/TriumphantAdventurer.java index 03c7406dee0..6a562cf14b9 100644 --- a/Mage.Sets/src/mage/cards/t/TriumphantAdventurer.java +++ b/Mage.Sets/src/mage/cards/t/TriumphantAdventurer.java @@ -8,6 +8,7 @@ import mage.abilities.condition.common.MyTurnCondition; import mage.abilities.decorator.ConditionalContinuousEffect; import mage.abilities.effects.common.continuous.GainAbilitySourceEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.hint.common.MyTurnHint; import mage.abilities.keyword.FirstStrikeAbility; import mage.constants.Duration; @@ -42,7 +43,8 @@ public final class TriumphantAdventurer extends CardImpl { ).addHint(MyTurnHint.instance)); // Whenever Triumphant Adventurer attacks, venture into the dungeon. - this.addAbility(new AttacksTriggeredAbility(new VentureIntoTheDungeonEffect(), false)); + this.addAbility(new AttacksTriggeredAbility(new VentureIntoTheDungeonEffect(), false) + .addHint(CurrentDungeonHint.instance)); } private TriumphantAdventurer(final TriumphantAdventurer card) { diff --git a/Mage.Sets/src/mage/cards/v/VarisSilverymoonRanger.java b/Mage.Sets/src/mage/cards/v/VarisSilverymoonRanger.java index 636e004bd05..a4822f7517a 100644 --- a/Mage.Sets/src/mage/cards/v/VarisSilverymoonRanger.java +++ b/Mage.Sets/src/mage/cards/v/VarisSilverymoonRanger.java @@ -6,6 +6,7 @@ import mage.abilities.common.SpellCastControllerTriggeredAbility; import mage.abilities.costs.mana.ManaCostsImpl; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.ReachAbility; import mage.abilities.keyword.WardAbility; import mage.cards.CardImpl; @@ -52,7 +53,7 @@ public final class VarisSilverymoonRanger extends CardImpl { // Whenever you cast a creature or planeswalker spell, venture into the dungeon. This ability triggers only once each turn. this.addAbility(new SpellCastControllerTriggeredAbility( new VentureIntoTheDungeonEffect(), filter, false - ).setTriggersLimitEachTurn(1)); + ).setTriggersLimitEachTurn(1).addHint(CurrentDungeonHint.instance)); // Whenever you complete a dungeon, create a 2/2 green Wolf creature token. this.addAbility(new CompletedDungeonTriggeredAbility(new CreateTokenEffect(new WolfToken()))); diff --git a/Mage.Sets/src/mage/cards/v/VeteranDungeoneer.java b/Mage.Sets/src/mage/cards/v/VeteranDungeoneer.java index e3c42eefed8..b0d5f86142f 100644 --- a/Mage.Sets/src/mage/cards/v/VeteranDungeoneer.java +++ b/Mage.Sets/src/mage/cards/v/VeteranDungeoneer.java @@ -3,6 +3,7 @@ package mage.cards.v; import mage.MageInt; import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -24,7 +25,8 @@ public final class VeteranDungeoneer extends CardImpl { this.toughness = new MageInt(4); // When Veteran Dungeoneer enters the battlefield, venture into the dungeon. - this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect())); + this.addAbility(new EntersBattlefieldTriggeredAbility(new VentureIntoTheDungeonEffect()) + .addHint(CurrentDungeonHint.instance)); } private VeteranDungeoneer(final VeteranDungeoneer card) { diff --git a/Mage.Sets/src/mage/cards/w/WanderingTroubadour.java b/Mage.Sets/src/mage/cards/w/WanderingTroubadour.java index c7de5e80060..b04a532746a 100644 --- a/Mage.Sets/src/mage/cards/w/WanderingTroubadour.java +++ b/Mage.Sets/src/mage/cards/w/WanderingTroubadour.java @@ -6,6 +6,7 @@ import mage.abilities.common.BeginningOfYourEndStepTriggeredAbility; import mage.abilities.condition.common.LandfallCondition; import mage.abilities.decorator.ConditionalInterveningIfTriggeredAbility; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -31,7 +32,7 @@ public final class WanderingTroubadour extends CardImpl { new BeginningOfYourEndStepTriggeredAbility(new VentureIntoTheDungeonEffect(), false), LandfallCondition.instance, "At the beginning of your end step, if you had a land enter the battlefield under your control this turn, venture into the dungeon." - ), new LandfallWatcher()); + ).addHint(CurrentDungeonHint.instance), new LandfallWatcher()); } private WanderingTroubadour(final WanderingTroubadour card) { diff --git a/Mage.Sets/src/mage/cards/y/YouFindACursedIdol.java b/Mage.Sets/src/mage/cards/y/YouFindACursedIdol.java index a275b9292ea..95d30ba7bcb 100644 --- a/Mage.Sets/src/mage/cards/y/YouFindACursedIdol.java +++ b/Mage.Sets/src/mage/cards/y/YouFindACursedIdol.java @@ -4,6 +4,7 @@ import mage.abilities.Mode; import mage.abilities.effects.common.CreateTokenEffect; import mage.abilities.effects.common.DestroyTargetEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; @@ -36,6 +37,7 @@ public final class YouFindACursedIdol extends CardImpl { mode = new Mode(new CreateTokenEffect(new TreasureToken())); mode.addEffect(new VentureIntoTheDungeonEffect().concatBy("and")); this.getSpellAbility().addMode(mode.withFlavorWord("Steal Its Eyes")); + this.getSpellAbility().addHint(CurrentDungeonHint.instance); } private YouFindACursedIdol(final YouFindACursedIdol card) { diff --git a/Mage.Sets/src/mage/cards/y/YuanTiFangBlade.java b/Mage.Sets/src/mage/cards/y/YuanTiFangBlade.java index e33ca95618c..74752e85caf 100644 --- a/Mage.Sets/src/mage/cards/y/YuanTiFangBlade.java +++ b/Mage.Sets/src/mage/cards/y/YuanTiFangBlade.java @@ -3,6 +3,7 @@ package mage.cards.y; import mage.MageInt; import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.DeathtouchAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -30,7 +31,7 @@ public final class YuanTiFangBlade extends CardImpl { // Whenever Yuan-Ti Fang-Blade deals combat damage to a player, venture into the dungeon. this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility( new VentureIntoTheDungeonEffect(), false - )); + ).addHint(CurrentDungeonHint.instance)); } private YuanTiFangBlade(final YuanTiFangBlade card) { diff --git a/Mage.Sets/src/mage/cards/y/YuanTiMalison.java b/Mage.Sets/src/mage/cards/y/YuanTiMalison.java index b146543c87f..5e946677a36 100644 --- a/Mage.Sets/src/mage/cards/y/YuanTiMalison.java +++ b/Mage.Sets/src/mage/cards/y/YuanTiMalison.java @@ -8,6 +8,7 @@ import mage.abilities.condition.common.SourceAttackingAloneCondition; import mage.abilities.decorator.ConditionalRestrictionEffect; import mage.abilities.effects.common.combat.CantBeBlockedSourceEffect; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.constants.SubType; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -35,7 +36,8 @@ public final class YuanTiMalison extends CardImpl { ))); // Whenever Yuan-Ti Malison deals combat damage to a player, venture into the dungeon. - this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new VentureIntoTheDungeonEffect(), false)); + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new VentureIntoTheDungeonEffect(), false) + .addHint(CurrentDungeonHint.instance)); } private YuanTiMalison(final YuanTiMalison card) { diff --git a/Mage.Sets/src/mage/cards/z/ZaltoFireGiantDuke.java b/Mage.Sets/src/mage/cards/z/ZaltoFireGiantDuke.java index ae3cb993916..641b230a9d7 100644 --- a/Mage.Sets/src/mage/cards/z/ZaltoFireGiantDuke.java +++ b/Mage.Sets/src/mage/cards/z/ZaltoFireGiantDuke.java @@ -3,6 +3,7 @@ package mage.cards.z; import mage.MageInt; import mage.abilities.common.DealtDamageToSourceTriggeredAbility; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.keyword.TrampleAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -30,7 +31,8 @@ public final class ZaltoFireGiantDuke extends CardImpl { this.addAbility(TrampleAbility.getInstance()); // Whenever Zalto, Fire Giant Duke is dealt damage, venture into the dungeon. - this.addAbility(new DealtDamageToSourceTriggeredAbility(new VentureIntoTheDungeonEffect(), false)); + this.addAbility(new DealtDamageToSourceTriggeredAbility(new VentureIntoTheDungeonEffect(), false) + .addHint(CurrentDungeonHint.instance)); } private ZaltoFireGiantDuke(final ZaltoFireGiantDuke card) { diff --git a/Mage.Sets/src/mage/cards/z/ZombieOgre.java b/Mage.Sets/src/mage/cards/z/ZombieOgre.java index 43f04c185f0..89ff6becde8 100644 --- a/Mage.Sets/src/mage/cards/z/ZombieOgre.java +++ b/Mage.Sets/src/mage/cards/z/ZombieOgre.java @@ -4,6 +4,7 @@ import mage.MageInt; import mage.abilities.common.BeginningOfEndStepTriggeredAbility; import mage.abilities.condition.common.MorbidCondition; import mage.abilities.effects.keyword.VentureIntoTheDungeonEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.hint.common.MorbidHint; import mage.cards.CardImpl; import mage.cards.CardSetInfo; @@ -31,7 +32,7 @@ public final class ZombieOgre extends CardImpl { this.addAbility(new BeginningOfEndStepTriggeredAbility( Zone.BATTLEFIELD, new VentureIntoTheDungeonEffect(), TargetController.YOU, MorbidCondition.instance, false - ).addHint(MorbidHint.instance)); + ).addHint(MorbidHint.instance).addHint(CurrentDungeonHint.instance)); } private ZombieOgre(final ZombieOgre card) { diff --git a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java index d7d8717eb27..400605b0428 100644 --- a/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java +++ b/Mage.Verify/src/test/java/mage/verify/VerifyCardDataTest.java @@ -16,6 +16,7 @@ import mage.abilities.effects.common.FightTargetsEffect; import mage.abilities.effects.common.counter.ProliferateEffect; import mage.abilities.effects.keyword.ScryEffect; import mage.abilities.hint.common.CitysBlessingHint; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.abilities.hint.common.InitiativeHint; import mage.abilities.hint.common.MonarchHint; import mage.abilities.keyword.*; @@ -2070,6 +2071,7 @@ public class VerifyCardDataTest { cardHints.put(CitysBlessingHint.class, "city's blessing"); cardHints.put(MonarchHint.class, "the monarch"); cardHints.put(InitiativeHint.class, "the initiative"); + cardHints.put(CurrentDungeonHint.class, "venture into"); for (Class hintClass : cardHints.keySet()) { String lookupText = cardHints.get(hintClass); boolean needHint = ref.text.contains(lookupText); diff --git a/Mage/src/main/java/mage/abilities/hint/common/CurrentDungeonHint.java b/Mage/src/main/java/mage/abilities/hint/common/CurrentDungeonHint.java new file mode 100644 index 00000000000..5ce7d0763de --- /dev/null +++ b/Mage/src/main/java/mage/abilities/hint/common/CurrentDungeonHint.java @@ -0,0 +1,43 @@ +package mage.abilities.hint.common; + +import mage.abilities.Ability; +import mage.abilities.condition.common.CitysBlessingCondition; +import mage.abilities.hint.ConditionHint; +import mage.abilities.hint.Hint; +import mage.game.Game; +import mage.game.command.Dungeon; +import mage.players.Player; + +/** + * @author JayDi85 + */ +public enum CurrentDungeonHint implements Hint { + + instance; + private static final ConditionHint hint = new ConditionHint(CitysBlessingCondition.instance, "You have city's blessing"); + + @Override + public String getText(Game game, Ability ability) { + Player player = game.getPlayer(ability.getControllerId()); + if (player == null) { + return ""; + } + + Dungeon dungeon = game.getPlayerDungeon(ability.getControllerId()); + if (dungeon == null) { + return "Current dungeon: not yet entered"; + } + + String dungeonInfo = "Current dungeon: " + dungeon.getLogName(); + if (dungeon.getCurrentRoom() != null) { + dungeonInfo += ", room: " + dungeon.getCurrentRoom().getName(); + } + + return dungeonInfo; + } + + @Override + public Hint copy() { + return instance; + } +} diff --git a/Mage/src/main/java/mage/designations/Initiative.java b/Mage/src/main/java/mage/designations/Initiative.java index da72667065a..244ac075bfe 100644 --- a/Mage/src/main/java/mage/designations/Initiative.java +++ b/Mage/src/main/java/mage/designations/Initiative.java @@ -3,6 +3,7 @@ package mage.designations; import mage.abilities.Ability; import mage.abilities.TriggeredAbilityImpl; import mage.abilities.effects.OneShotEffect; +import mage.abilities.hint.common.CurrentDungeonHint; import mage.constants.Outcome; import mage.constants.Zone; import mage.game.Controllable; @@ -113,6 +114,7 @@ class InitiativeVentureTriggeredAbility extends TriggeredAbilityImpl { InitiativeVentureTriggeredAbility() { super(Zone.ALL, new InitiativeUndercityEffect()); + addHint(CurrentDungeonHint.instance); } private InitiativeVentureTriggeredAbility(final InitiativeVentureTriggeredAbility ability) { diff --git a/Mage/src/main/java/mage/game/Game.java b/Mage/src/main/java/mage/game/Game.java index 5e5f5b58ca5..c6d8c2b0aa0 100644 --- a/Mage/src/main/java/mage/game/Game.java +++ b/Mage/src/main/java/mage/game/Game.java @@ -88,7 +88,7 @@ public interface Game extends MageItem, Serializable, Copyable { Dungeon getDungeon(UUID objectId); - Dungeon getPlayerDungeon(UUID objectId); + Dungeon getPlayerDungeon(UUID playerId); UUID getControllerId(UUID objectId); @@ -456,7 +456,12 @@ public interface Game extends MageItem, Serializable, Copyable { Dungeon addDungeon(Dungeon dungeon, UUID playerId); - void ventureIntoDungeon(UUID playerId, boolean undercity); + /** + * Enter to dungeon or go to next room + * + * @param isEnterToUndercity - enter to Undercity instead choose a new dungeon + */ + void ventureIntoDungeon(UUID playerId, boolean isEnterToUndercity); void temptWithTheRing(UUID playerId); diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index 4631cb50f98..8c122146d71 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -560,14 +560,14 @@ public abstract class GameImpl implements Game { } @Override - public void ventureIntoDungeon(UUID playerId, boolean undercity) { + public void ventureIntoDungeon(UUID playerId, boolean isEnterToUndercity) { if (playerId == null) { return; } if (replaceEvent(GameEvent.getEvent(GameEvent.EventType.VENTURE, playerId, null, playerId))) { return; } - this.getOrCreateDungeon(playerId, undercity).moveToNextRoom(playerId, this); + this.getOrCreateDungeon(playerId, isEnterToUndercity).moveToNextRoom(playerId, this); fireEvent(GameEvent.getEvent(GameEvent.EventType.VENTURED, playerId, null, playerId)); }