From 63bbbcfc819daada67aa387ad0bd9af0365dce9b Mon Sep 17 00:00:00 2001 From: Daniel Bomar Date: Fri, 19 Aug 2022 14:33:11 -0500 Subject: [PATCH 1/2] [DMU] Implemented Braids, Arisen Nightmare --- .../mage/cards/b/BraidsArisenNightmare.java | 148 ++++++++++++++++++ Mage.Sets/src/mage/sets/DominariaUnited.java | 1 + 2 files changed, 149 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/b/BraidsArisenNightmare.java diff --git a/Mage.Sets/src/mage/cards/b/BraidsArisenNightmare.java b/Mage.Sets/src/mage/cards/b/BraidsArisenNightmare.java new file mode 100644 index 0000000000..56652700fb --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/BraidsArisenNightmare.java @@ -0,0 +1,148 @@ +package mage.cards.b; + +import java.util.HashSet; +import java.util.UUID; +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.common.BeginningOfYourEndStepTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.filter.common.FilterControlledPermanent; +import mage.filter.predicate.Predicate; +import mage.filter.predicate.Predicates; +import mage.game.Game; +import mage.game.permanent.Permanent; +import mage.players.Player; +import mage.target.common.TargetControlledPermanent; + +/** + * + * @author weirddan455 + */ +public final class BraidsArisenNightmare extends CardImpl { + + public BraidsArisenNightmare(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{B}{B}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.NIGHTMARE); + this.power = new MageInt(3); + this.toughness = new MageInt(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. + this.addAbility(new BeginningOfYourEndStepTriggeredAbility(new BraidsArisenNightmareEffect(), true)); + } + + private BraidsArisenNightmare(final BraidsArisenNightmare card) { + super(card); + } + + @Override + public BraidsArisenNightmare copy() { + return new BraidsArisenNightmare(this); + } +} + +class BraidsArisenNightmareEffect extends OneShotEffect { + + private static final FilterControlledPermanent filter = new FilterControlledPermanent("an artifact, creature, enchantment, land, or planeswalker"); + + static { + filter.add(Predicates.or( + CardType.ARTIFACT.getPredicate(), + CardType.CREATURE.getPredicate(), + CardType.ENCHANTMENT.getPredicate(), + CardType.LAND.getPredicate(), + CardType.PLANESWALKER.getPredicate() + )); + } + + public BraidsArisenNightmareEffect() { + super(Outcome.Sacrifice); + this.staticText = "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"; + } + + private BraidsArisenNightmareEffect(final BraidsArisenNightmareEffect effect) { + super(effect); + } + + @Override + public BraidsArisenNightmareEffect copy() { + return new BraidsArisenNightmareEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player controller = game.getPlayer(source.getControllerId()); + if (controller == null) { + return false; + } + TargetControlledPermanent target = new TargetControlledPermanent(1, 1, filter, true); + if (!target.canChoose(controller.getId(), source, game)) { + return false; + } + controller.chooseTarget(Outcome.Sacrifice, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + if (permanent == null) { + return false; + } + FilterControlledPermanent opponentFilter = new FilterControlledPermanent("a permanent that shares a card type with " + permanent.getName()); + opponentFilter.add(new BraidsArisenNightmarePredicate(new HashSet<>(permanent.getCardType(game)))); + if (!permanent.sacrifice(source, game)) { + return false; + } + for (UUID opponentId : game.getOpponents(controller.getId())) { + Player opponent = game.getPlayer(opponentId); + if (opponent == null) { + continue; + } + if (!braidsSacrifice(opponent, opponentFilter, game, source)) { + opponent.loseLife(2, game, source, false); + controller.drawCards(1, source, game); + } + } + return true; + } + + private boolean braidsSacrifice(Player opponent, FilterControlledPermanent opponentFilter, Game game, Ability source) { + TargetControlledPermanent target = new TargetControlledPermanent(1, 1, opponentFilter, true); + if (!target.canChoose(opponent.getId(), source, game)) { + return false; + } + if (!opponent.chooseUse(Outcome.Sacrifice, "Sacrifice " + opponentFilter.getMessage() + '?', source, game)) { + return false; + } + opponent.chooseTarget(Outcome.Sacrifice, target, source, game); + Permanent permanent = game.getPermanent(target.getFirstTarget()); + return permanent != null && permanent.sacrifice(source, game); + } +} + +class BraidsArisenNightmarePredicate implements Predicate { + + private final HashSet cardTypes; + + public BraidsArisenNightmarePredicate(HashSet cardTypes) { + this.cardTypes = cardTypes; + } + + @Override + public boolean apply(MageObject input, Game game) { + for (CardType type : input.getCardType(game)) { + if (cardTypes.contains(type)) { + return true; + } + } + return false; + } +} diff --git a/Mage.Sets/src/mage/sets/DominariaUnited.java b/Mage.Sets/src/mage/sets/DominariaUnited.java index e1fadd0915..861f621710 100644 --- a/Mage.Sets/src/mage/sets/DominariaUnited.java +++ b/Mage.Sets/src/mage/sets/DominariaUnited.java @@ -29,6 +29,7 @@ public final class DominariaUnited extends ExpansionSet { cards.add(new SetCardInfo("Adarkar Wastes", 243, Rarity.RARE, mage.cards.a.AdarkarWastes.class)); cards.add(new SetCardInfo("Benalish Sleeper", 8, Rarity.COMMON, mage.cards.b.BenalishSleeper.class)); + cards.add(new SetCardInfo("Braids, Arisen Nightmare", 84, Rarity.RARE, mage.cards.b.BraidsArisenNightmare.class)); cards.add(new SetCardInfo("Caves of Koilos", 244, Rarity.RARE, mage.cards.c.CavesOfKoilos.class)); cards.add(new SetCardInfo("Charismatic Vanguard", 10, Rarity.COMMON, mage.cards.c.CharismaticVanguard.class)); cards.add(new SetCardInfo("Evolved Sleeper", 93, Rarity.RARE, mage.cards.e.EvolvedSleeper.class)); From 0f9a293762fb5285da125d4a2e50a6f89df3e400 Mon Sep 17 00:00:00 2001 From: Daniel Bomar Date: Sat, 20 Aug 2022 12:37:50 -0500 Subject: [PATCH 2/2] Add/Refactor cards to use SharesCardTypePredicate --- .../mage/cards/b/BraidsArisenNightmare.java | 31 ++-------- .../src/mage/cards/c/ConfusionInTheRanks.java | 22 ++----- Mage.Sets/src/mage/cards/c/Counterlash.java | 18 +----- Mage.Sets/src/mage/cards/m/MartyrsBond.java | 23 ++----- .../src/mage/cards/s/SpiritSistersCall.java | 28 ++------- .../mageobject/SharesCardTypePredicate.java | 60 +++++++++++++++++++ 6 files changed, 82 insertions(+), 100 deletions(-) create mode 100644 Mage/src/main/java/mage/filter/predicate/mageobject/SharesCardTypePredicate.java diff --git a/Mage.Sets/src/mage/cards/b/BraidsArisenNightmare.java b/Mage.Sets/src/mage/cards/b/BraidsArisenNightmare.java index 56652700fb..6b70813cd5 100644 --- a/Mage.Sets/src/mage/cards/b/BraidsArisenNightmare.java +++ b/Mage.Sets/src/mage/cards/b/BraidsArisenNightmare.java @@ -1,9 +1,7 @@ package mage.cards.b; -import java.util.HashSet; import java.util.UUID; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.BeginningOfYourEndStepTriggeredAbility; import mage.abilities.effects.OneShotEffect; @@ -14,12 +12,13 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.Predicate; import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SharesCardTypePredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.players.Player; import mage.target.common.TargetControlledPermanent; +import mage.util.CardUtil; /** * @@ -96,8 +95,9 @@ class BraidsArisenNightmareEffect extends OneShotEffect { if (permanent == null) { return false; } - FilterControlledPermanent opponentFilter = new FilterControlledPermanent("a permanent that shares a card type with " + permanent.getName()); - opponentFilter.add(new BraidsArisenNightmarePredicate(new HashSet<>(permanent.getCardType(game)))); + SharesCardTypePredicate predicate = new SharesCardTypePredicate(permanent.getCardType(game)); + FilterControlledPermanent opponentFilter = new FilterControlledPermanent(predicate.toString()); + opponentFilter.add(predicate); if (!permanent.sacrifice(source, game)) { return false; } @@ -119,7 +119,7 @@ class BraidsArisenNightmareEffect extends OneShotEffect { if (!target.canChoose(opponent.getId(), source, game)) { return false; } - if (!opponent.chooseUse(Outcome.Sacrifice, "Sacrifice " + opponentFilter.getMessage() + '?', source, game)) { + if (!opponent.chooseUse(Outcome.Sacrifice, "Sacrifice " + CardUtil.addArticle(opponentFilter.getMessage()) + '?', source, game)) { return false; } opponent.chooseTarget(Outcome.Sacrifice, target, source, game); @@ -127,22 +127,3 @@ class BraidsArisenNightmareEffect extends OneShotEffect { return permanent != null && permanent.sacrifice(source, game); } } - -class BraidsArisenNightmarePredicate implements Predicate { - - private final HashSet cardTypes; - - public BraidsArisenNightmarePredicate(HashSet cardTypes) { - this.cardTypes = cardTypes; - } - - @Override - public boolean apply(MageObject input, Game game) { - for (CardType type : input.getCardType(game)) { - if (cardTypes.contains(type)) { - return true; - } - } - return false; - } -} diff --git a/Mage.Sets/src/mage/cards/c/ConfusionInTheRanks.java b/Mage.Sets/src/mage/cards/c/ConfusionInTheRanks.java index f6c9fbee9e..0ce70db568 100644 --- a/Mage.Sets/src/mage/cards/c/ConfusionInTheRanks.java +++ b/Mage.Sets/src/mage/cards/c/ConfusionInTheRanks.java @@ -13,15 +13,13 @@ import mage.constants.SetTargetPointer; import mage.constants.Zone; import mage.filter.FilterPermanent; import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SharesCardTypePredicate; import mage.filter.predicate.permanent.ControllerIdPredicate; import mage.game.Game; import mage.game.permanent.Permanent; import mage.target.TargetPermanent; import mage.target.targetadjustment.TargetAdjuster; -import java.util.HashSet; -import java.util.Locale; -import java.util.Set; import java.util.UUID; /** @@ -85,22 +83,12 @@ enum ConfusionInTheRanksAdjuster implements TargetAdjuster { return; } ability.getTargets().clear(); - FilterPermanent filterTarget = new FilterPermanent(); - String message = ""; + SharesCardTypePredicate predicate = new SharesCardTypePredicate(enteringPermanent.getCardType(game)); + FilterPermanent filterTarget = new FilterPermanent(predicate.toString() + " you don't control"); + filterTarget.add(predicate); filterTarget.add(Predicates.not(new ControllerIdPredicate(enteringPermanent.getControllerId()))); - Set cardTypesPredicates = new HashSet<>(1); - for (CardType cardTypeEntering : enteringPermanent.getCardType(game)) { - cardTypesPredicates.add(cardTypeEntering.getPredicate()); - if (!message.isEmpty()) { - message += "or "; - } - message += cardTypeEntering.toString().toLowerCase(Locale.ENGLISH) + ' '; - } - filterTarget.add(Predicates.or(cardTypesPredicates)); - message += "you don't control"; - filterTarget.setMessage(message); TargetPermanent target = new TargetPermanent(filterTarget); target.setTargetController(enteringPermanent.getControllerId()); ability.getTargets().add(target); } -} \ No newline at end of file +} diff --git a/Mage.Sets/src/mage/cards/c/Counterlash.java b/Mage.Sets/src/mage/cards/c/Counterlash.java index 458d03b8b1..4875b65c4a 100644 --- a/Mage.Sets/src/mage/cards/c/Counterlash.java +++ b/Mage.Sets/src/mage/cards/c/Counterlash.java @@ -1,6 +1,5 @@ package mage.cards.c; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.effects.OneShotEffect; import mage.cards.CardImpl; @@ -9,17 +8,14 @@ import mage.cards.CardsImpl; import mage.constants.CardType; import mage.constants.Outcome; import mage.filter.FilterCard; -import mage.filter.predicate.Predicate; -import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SharesCardTypePredicate; import mage.game.Game; import mage.game.stack.StackObject; import mage.players.Player; import mage.target.TargetSpell; import mage.util.CardUtil; -import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; /** * @author BetaSteward @@ -70,17 +66,9 @@ class CounterlashEffect extends OneShotEffect { if (stackObject == null || controller == null) { return false; } - Set> predicates = stackObject - .getCardType(game) - .stream() - .map(CardType::getPredicate) - .collect(Collectors.toSet()); - game.getStack().counter(source.getFirstTarget(), source, game); - if (predicates.isEmpty()) { - return true; - } FilterCard filter = new FilterCard(); - filter.add(Predicates.or(predicates)); + filter.add(new SharesCardTypePredicate(stackObject.getCardType(game))); + game.getStack().counter(source.getFirstTarget(), source, game); CardUtil.castSpellWithAttributesForFree(controller, source, game, new CardsImpl(controller.getHand()), filter); return true; } diff --git a/Mage.Sets/src/mage/cards/m/MartyrsBond.java b/Mage.Sets/src/mage/cards/m/MartyrsBond.java index a152e19f22..e5cdb42f5c 100644 --- a/Mage.Sets/src/mage/cards/m/MartyrsBond.java +++ b/Mage.Sets/src/mage/cards/m/MartyrsBond.java @@ -10,7 +10,7 @@ import mage.constants.CardType; import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; -import mage.filter.predicate.Predicates; +import mage.filter.predicate.mageobject.SharesCardTypePredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; @@ -112,24 +112,9 @@ class MartyrsBondEffect extends OneShotEffect { Permanent saccedPermanent = getTargetPointer().getFirstTargetPermanentOrLKI(game, source); Player controller = game.getPlayer(source.getControllerId()); if (controller != null && saccedPermanent != null) { - FilterControlledPermanent filter = new FilterControlledPermanent(); - String message = "permanent with type ("; - boolean firstType = true; - - List cardTypes = new ArrayList<>(); - - for (CardType type : saccedPermanent.getCardType(game)) { - cardTypes.add(type.getPredicate()); - if (firstType) { - message += type; - firstType = false; - } else { - message += " or " + type; - } - } - message += ") to sacrifice"; - filter.add(Predicates.or(cardTypes)); - filter.setMessage(message); + SharesCardTypePredicate predicate = new SharesCardTypePredicate(saccedPermanent.getCardType(game)); + FilterControlledPermanent filter = new FilterControlledPermanent(predicate.toString()); + filter.add(predicate); for (UUID playerId : game.getState().getPlayersInRange(controller.getId(), game)) { Player player = game.getPlayer(playerId); diff --git a/Mage.Sets/src/mage/cards/s/SpiritSistersCall.java b/Mage.Sets/src/mage/cards/s/SpiritSistersCall.java index 70ae459886..c45f80b57b 100644 --- a/Mage.Sets/src/mage/cards/s/SpiritSistersCall.java +++ b/Mage.Sets/src/mage/cards/s/SpiritSistersCall.java @@ -1,9 +1,7 @@ package mage.cards.s; -import java.util.HashSet; import java.util.UUID; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.common.BeginningOfYourEndStepTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; @@ -22,7 +20,7 @@ import mage.constants.Outcome; import mage.constants.Zone; import mage.filter.common.FilterControlledPermanent; import mage.filter.common.FilterPermanentCard; -import mage.filter.predicate.Predicate; +import mage.filter.predicate.mageobject.SharesCardTypePredicate; import mage.game.Game; import mage.game.events.GameEvent; import mage.game.events.ZoneChangeEvent; @@ -85,8 +83,9 @@ class SpiritSistersCallDoIfEffect extends OneShotEffect { if (card == null || game.getState().getZone(targetId) != Zone.GRAVEYARD) { return false; } - FilterControlledPermanent filter = new FilterControlledPermanent("a permanent that shares a card type with the chosen card"); - filter.add(new SpiritSistersCallPredicate(new HashSet(card.getCardType(game)))); + SharesCardTypePredicate predicate = new SharesCardTypePredicate(card.getCardType(game)); + FilterControlledPermanent filter = new FilterControlledPermanent(predicate.toString()); + filter.add(predicate); return new DoIfCostPaid(new SpiritSistersCallReturnToBattlefieldEffect(), new SacrificeTargetCost(filter)).apply(game, source); } } @@ -160,22 +159,3 @@ class SpiritSistersCallReplacementEffect extends ReplacementEffectImpl { && zEvent.getFromZone() == Zone.BATTLEFIELD && zEvent.getToZone() != Zone.EXILED; } } - -class SpiritSistersCallPredicate implements Predicate { - - private final HashSet cardTypes; - - public SpiritSistersCallPredicate(HashSet cardTypes) { - this.cardTypes = cardTypes; - } - - @Override - public boolean apply(MageObject input, Game game) { - for (CardType type : input.getCardType(game)) { - if (cardTypes.contains(type)) { - return true; - } - } - return false; - } -} diff --git a/Mage/src/main/java/mage/filter/predicate/mageobject/SharesCardTypePredicate.java b/Mage/src/main/java/mage/filter/predicate/mageobject/SharesCardTypePredicate.java new file mode 100644 index 0000000000..58f709bd4c --- /dev/null +++ b/Mage/src/main/java/mage/filter/predicate/mageobject/SharesCardTypePredicate.java @@ -0,0 +1,60 @@ +package mage.filter.predicate.mageobject; + +import mage.MageObject; +import mage.constants.CardType; +import mage.filter.predicate.Predicate; +import mage.game.Game; + +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Locale; + +/** + * + * @author weirddan455 + */ +public class SharesCardTypePredicate implements Predicate { + + private final LinkedHashSet cardTypes; + + public SharesCardTypePredicate(Collection cardTypes) { + this.cardTypes = new LinkedHashSet<>(cardTypes); + } + + @Override + public boolean apply(MageObject input, Game game) { + for (CardType type : input.getCardType(game)) { + if (cardTypes.contains(type)) { + return true; + } + } + return false; + } + + @Override + public String toString() { + Iterator it = cardTypes.iterator(); + switch(cardTypes.size()) { + case 0: + return ""; + case 1: + return it.next().toString().toLowerCase(Locale.ENGLISH); + case 2: + return it.next().toString().toLowerCase(Locale.ENGLISH) + " or " + it.next().toString().toLowerCase(Locale.ENGLISH); + default: { + StringBuilder sb = new StringBuilder(); + sb.append(it.next().toString().toLowerCase(Locale.ENGLISH)); + while (it.hasNext()) { + CardType type = it.next(); + sb.append(", "); + if (!it.hasNext()) { + sb.append("or "); + } + sb.append(type.toString().toLowerCase(Locale.ENGLISH)); + } + return sb.toString(); + } + } + } +}