From 56ccfd2630133373a2d9a09ba5cd4bc0a9e34f89 Mon Sep 17 00:00:00 2001 From: Lashed Date: Sun, 4 May 2025 18:58:01 +0200 Subject: [PATCH 1/2] Added WinterCynicalOpportunist.java card to the DuskmournHouseOfHorrorCommander set and card. --- .../cards/w/WinterCynicalOpportunist.java | 220 ++++++++++++++++++ .../sets/DuskmournHouseOfHorrorCommander.java | 7 +- 2 files changed, 221 insertions(+), 6 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/w/WinterCynicalOpportunist.java diff --git a/Mage.Sets/src/mage/cards/w/WinterCynicalOpportunist.java b/Mage.Sets/src/mage/cards/w/WinterCynicalOpportunist.java new file mode 100644 index 00000000000..97f6d9b8fcb --- /dev/null +++ b/Mage.Sets/src/mage/cards/w/WinterCynicalOpportunist.java @@ -0,0 +1,220 @@ +package mage.cards.w; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.condition.common.DeliriumCondition; +import mage.abilities.dynamicvalue.common.CardTypesInGraveyardCount; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.MillCardsControllerEffect; +import mage.abilities.hint.HintUtils; +import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.triggers.BeginningOfEndStepTriggeredAbility; +import mage.cards.*; +import mage.constants.*; +import mage.counters.CounterType; +import mage.filter.FilterCard; +import mage.filter.StaticFilters; +import mage.filter.common.FilterPermanentCard; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetCard; +import mage.target.common.TargetCardInYourGraveyard; +import mage.util.CardUtil; + +import java.awt.*; +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +/** + * + * @author Lashed + */ +public final class WinterCynicalOpportunist extends CardImpl { + + public WinterCynicalOpportunist(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{B}{G}"); + + this.supertype.add(SuperType.LEGENDARY); + this.subtype.add(SubType.HUMAN); + this.subtype.add(SubType.WARLOCK); + this.power = new MageInt(2); + this.toughness = new MageInt(5); + + // Deathtouch + this.addAbility(DeathtouchAbility.getInstance()); + + // Whenever Winter attacks, mill three cards. + this.addAbility(new AttacksTriggeredAbility(new MillCardsControllerEffect(3), false)); + + // Delirium -- At the beginning of your end step, you may exile any number of cards from your graveyard with four or more card types among them. If you do, put a permanent card from among them onto the battlefield with a finality counter on it. + this.addAbility(new BeginningOfEndStepTriggeredAbility( + TargetController.YOU, + new WinterCynicalOpportunistDeliriumEffect(), + true) + .setAbilityWord(AbilityWord.DELIRIUM) + .withInterveningIf(DeliriumCondition.instance) + .addHint(CardTypesInGraveyardCount.YOU.getHint())); + } + + + private WinterCynicalOpportunist(final WinterCynicalOpportunist card) { + super(card); + } + + @Override + public WinterCynicalOpportunist copy() { + return new WinterCynicalOpportunist(this); + } + +} + +class WinterCynicalOpportunistDeliriumEffect extends OneShotEffect { + + public WinterCynicalOpportunistDeliriumEffect() { + super(Outcome.PutCardInPlay); + this.staticText = "exile any number of cards from your graveyard. " + + "If you do, put a permanent card from among them onto the battlefield with a finality counter on it"; + } + + private WinterCynicalOpportunistDeliriumEffect(final WinterCynicalOpportunistDeliriumEffect effect) { + super(effect); + } + + @Override + public WinterCynicalOpportunistDeliriumEffect copy() { + return new WinterCynicalOpportunistDeliriumEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + MageObject sourceObject = source.getSourceObject(game); + if (controller == null || sourceObject == null) { + return false; + } + + // Let the player select cards to exile + DeliriumTarget target = new DeliriumTarget(); + if (!target.canChoose(controller.getId(), source, game) || + !controller.choose(Outcome.Exile, target, source, game)) { + return false; + } + + // Exile the selected cards + Set cardsToExile = target.getTargets().stream() + .map(game::getCard) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + if (cardsToExile.isEmpty()) { + return false; + } + + controller.moveCards(cardsToExile, Zone.EXILED, source, game); + + // Filter permanent cards from the exiled cards + FilterCard filter = new FilterPermanentCard("permanent card from among the exiled cards to put onto the battlefield"); + filter.add(new WinterCynicalOpportunistExiledPredicate(cardsToExile)); + + TargetCard targetPermanent = new TargetCard(1, 1, Zone.EXILED, filter); + Cards permanentCards = new CardsImpl(); + cardsToExile.stream() + .filter(card -> card.isPermanent(game)) + .forEach(permanentCards::add); + + if (controller.choose(Outcome.PutCardInPlay, permanentCards, targetPermanent, source, game)) { + + Card cardToPut = game.getCard(targetPermanent.getFirstTarget()); + if (cardToPut != null) { + controller.moveCards(cardToPut, Zone.BATTLEFIELD, source, game); + + // Add a finality counter + Card permanentCard = game.getPermanent(cardToPut.getId()); + if (permanentCard != null) { + permanentCard.addCounters(CounterType.FINALITY.createInstance(), source.getControllerId(), source, game); + } + + return true; + } + } + + return false; + } +} + +class DeliriumTarget extends TargetCardInYourGraveyard { + + DeliriumTarget() { + super(0, Integer.MAX_VALUE, StaticFilters.FILTER_CARD, true); + this.targetName = "any number of cards from your graveyard"; + } + + private DeliriumTarget(final DeliriumTarget target) { + super(target); + } + + @Override + public DeliriumTarget copy() { + return new DeliriumTarget(this); + } + + @Override + public String getMessage(Game game) { + String text = "Select " + CardUtil.addArticle(targetName); + Set types = getCardTypes(this.getTargets(), game); + text += " (selected " + this.getTargets().size() + " cards; card types: "; + text += HintUtils.prepareText( + types.size() + " of 4", + types.size() >= 4 ? Color.GREEN : Color.RED + ); + text += " [" + types.stream().map(CardType::toString).collect(Collectors.joining(", ")) + "])"; + return text; + } + + @Override + public boolean isChosen(Game game) { + return super.isChosen(game) && getCardTypesCount(this.getTargets(), game) >= 4; + } + + @Override + public boolean canChoose(UUID sourceControllerId, Ability source, Game game) { + if (!super.canChoose(sourceControllerId, source, game)) { + return false; + } + // Check that selecting all the possible cards would have >= 4 different card types + return getCardTypesCount(this.possibleTargets(sourceControllerId, source, game), game) >= 4; + } + + private static Set getCardTypes(Collection cardIds, Game game) { + return cardIds + .stream() + .map(game::getCard) + .filter(Objects::nonNull) + .flatMap(c -> c.getCardType(game).stream()) + .collect(Collectors.toSet()); + } + + private static int getCardTypesCount(Collection cardIds, Game game) { + return getCardTypes(cardIds, game).size(); + } +} + +class WinterCynicalOpportunistExiledPredicate implements mage.filter.predicate.Predicate { + + private final Set exiledCards; + + public WinterCynicalOpportunistExiledPredicate(Set exiledCards) { + this.exiledCards = exiledCards; + } + + + @Override + public boolean apply(Card input, Game game) { + return exiledCards.contains(input); + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java index 7a2f0b977f2..c965046f8f9 100644 --- a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java +++ b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java @@ -23,7 +23,6 @@ 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("Ancient Cellarspawn", 16, Rarity.RARE, mage.cards.a.AncientCellarspawn.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)); @@ -39,7 +38,6 @@ 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("Barbflare Gremlin", 26, Rarity.RARE, mage.cards.b.BarbflareGremlin.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)); @@ -90,7 +88,6 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { 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)); - cards.add(new SetCardInfo("Demonic Covenant", 19, Rarity.RARE, mage.cards.d.DemonicCovenant.class)); cards.add(new SetCardInfo("Diabolic Vision", 87, Rarity.UNCOMMON, mage.cards.d.DiabolicVision.class)); 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)); @@ -148,7 +145,6 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { 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("Into the Pit", 20, Rarity.RARE, mage.cards.i.IntoThePit.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)); @@ -222,7 +218,6 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Rendmaw, Creaking Nest", 5, Rarity.MYTHIC, mage.cards.r.RendmawCreakingNest.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("Sadistic Shell Game", 24, Rarity.RARE, mage.cards.s.SadisticShellGame.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)); @@ -241,7 +236,6 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { 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("Soaring Lightbringer", 11, Rarity.RARE, mage.cards.s.SoaringLightbringer.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)); @@ -310,6 +304,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { 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("Winter, Cynical Opportunist", 7, Rarity.MYTHIC, mage.cards.w.WinterCynicalOpportunist.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)); From 116a31d38390251f085d351fbaa175b8a63a612b Mon Sep 17 00:00:00 2001 From: Lashed Date: Sun, 4 May 2025 19:37:38 +0200 Subject: [PATCH 2/2] Update DuskmournHouseOfHorrorCommander.java fixed the set --- .../src/mage/sets/DuskmournHouseOfHorrorCommander.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java b/Mage.Sets/src/mage/sets/DuskmournHouseOfHorrorCommander.java index c965046f8f9..2872e6dfeb0 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("Ancient Cellarspawn", 16, Rarity.RARE, mage.cards.a.AncientCellarspawn.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)); @@ -38,6 +39,7 @@ 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("Barbflare Gremlin", 26, Rarity.RARE, mage.cards.b.BarbflareGremlin.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)); @@ -88,6 +90,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { 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)); + cards.add(new SetCardInfo("Demonic Covenant", 19, Rarity.RARE, mage.cards.d.DemonicCovenant.class)); cards.add(new SetCardInfo("Diabolic Vision", 87, Rarity.UNCOMMON, mage.cards.d.DiabolicVision.class)); 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)); @@ -145,6 +148,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { 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("Into the Pit", 20, Rarity.RARE, mage.cards.i.IntoThePit.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)); @@ -218,6 +222,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { cards.add(new SetCardInfo("Rendmaw, Creaking Nest", 5, Rarity.MYTHIC, mage.cards.r.RendmawCreakingNest.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("Sadistic Shell Game", 24, Rarity.RARE, mage.cards.s.SadisticShellGame.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)); @@ -236,6 +241,7 @@ public final class DuskmournHouseOfHorrorCommander extends ExpansionSet { 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("Soaring Lightbringer", 11, Rarity.RARE, mage.cards.s.SoaringLightbringer.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));