From 1899fa0def4ae01957ac6a8db63e7dfb305daa4b Mon Sep 17 00:00:00 2001 From: Evan Kranzler Date: Mon, 6 Jun 2022 19:49:42 -0400 Subject: [PATCH] [CLB] Implemented Balor --- Mage.Sets/src/mage/cards/b/Balor.java | 117 ++++++++++++++++++ .../src/mage/cards/c/CephalidLooter.java | 10 +- .../src/mage/cards/g/GorexTheTombshell.java | 57 ++------- .../src/mage/cards/s/ShadrixSilverquill.java | 20 +-- .../src/mage/cards/v/VindictiveLich.java | 46 ++++--- .../CommanderLegendsBattleForBaldursGate.java | 1 + .../common/DrawDiscardTargetEffect.java | 37 +++--- Mage/src/main/java/mage/target/Target.java | 10 +- .../src/main/java/mage/target/TargetImpl.java | 3 +- .../mage/target/common/TargetOpponent.java | 12 +- 10 files changed, 191 insertions(+), 122 deletions(-) create mode 100644 Mage.Sets/src/mage/cards/b/Balor.java diff --git a/Mage.Sets/src/mage/cards/b/Balor.java b/Mage.Sets/src/mage/cards/b/Balor.java new file mode 100644 index 00000000000..8c72973b5e1 --- /dev/null +++ b/Mage.Sets/src/mage/cards/b/Balor.java @@ -0,0 +1,117 @@ +package mage.cards.b; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.Mode; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.DiesSourceTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.DrawDiscardTargetEffect; +import mage.abilities.effects.common.SacrificeEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.abilities.meta.OrTriggeredAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.Outcome; +import mage.constants.SubType; +import mage.constants.Zone; +import mage.filter.FilterOpponent; +import mage.filter.FilterPermanent; +import mage.filter.FilterPlayer; +import mage.filter.common.FilterArtifactPermanent; +import mage.filter.predicate.other.AnotherTargetPredicate; +import mage.filter.predicate.permanent.TokenPredicate; +import mage.game.Game; +import mage.players.Player; +import mage.target.TargetPlayer; + +import java.util.UUID; + +/** + * @author TheElk801 + */ +public final class Balor extends CardImpl { + + private static final FilterPlayer filter0 = new FilterPlayer("a different player"); + private static final FilterPlayer filter1 = new FilterOpponent(); + private static final FilterPlayer filter2 = new FilterOpponent(); + private static final FilterPlayer filter3 = new FilterOpponent(); + private static final FilterPermanent filter4 = new FilterArtifactPermanent("a nontoken artifact"); + + static { + filter1.add(new AnotherTargetPredicate(1, true)); + filter2.add(new AnotherTargetPredicate(2, true)); + filter3.add(new AnotherTargetPredicate(3, true)); + filter4.add(TokenPredicate.FALSE); + } + + public Balor(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{R}{R}"); + + this.subtype.add(SubType.DEMON); + this.power = new MageInt(5); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever Balor attacks or dies, choose one or more. Each mode must target a different player. + // • Target opponent draws three cards, then discards three cards at random. + Ability ability = new OrTriggeredAbility( + Zone.BATTLEFIELD, + new DrawDiscardTargetEffect( + 1, 1, true + ), false, "Whenever {this} attacks or dies, ", + new AttacksTriggeredAbility(null, false), + new DiesSourceTriggeredAbility(null, false) + ); + ability.getModes().setMinModes(1); + ability.getModes().setMaxModes(3); + ability.getModes().setMaxModesFilter(filter0); + ability.addTarget(new TargetPlayer(filter1).setTargetTag(1).withChooseHint("to draw and discard")); + + // • Target opponent sacrifices a nontoken artifact. + ability.addMode(new Mode(new SacrificeEffect( + filter4, 1, null + )).addTarget(new TargetPlayer(filter2).setTargetTag(2).withChooseHint("to sacrifice an artifact"))); + + // • Balor deals damage to target opponent equal to the number of cards in their hand. + ability.addMode(new Mode(new BalorEffect()).addTarget(new TargetPlayer(filter3) + .setTargetTag(3).withChooseHint("to deal damage"))); + } + + private Balor(final Balor card) { + super(card); + } + + @Override + public Balor copy() { + return new Balor(this); + } +} + +class BalorEffect extends OneShotEffect { + + BalorEffect() { + super(Outcome.Benefit); + staticText = "{this} deals damage to target opponent equal to the number of cards in their hand"; + } + + private BalorEffect(final BalorEffect effect) { + super(effect); + } + + @Override + public BalorEffect copy() { + return new BalorEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player player = game.getPlayer(game.getActivePlayerId()); + return player != null + && player.getHand().size() >= 1 + && player.damage(player.getHand().size(), source.getSourceId(), source, game) > 0; + } +} diff --git a/Mage.Sets/src/mage/cards/c/CephalidLooter.java b/Mage.Sets/src/mage/cards/c/CephalidLooter.java index cd0fe731a72..1b0166f39a6 100644 --- a/Mage.Sets/src/mage/cards/c/CephalidLooter.java +++ b/Mage.Sets/src/mage/cards/c/CephalidLooter.java @@ -1,7 +1,5 @@ - package mage.cards.c; -import java.util.UUID; import mage.MageInt; import mage.abilities.Ability; import mage.abilities.common.SimpleActivatedAbility; @@ -11,17 +9,17 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.target.TargetPlayer; +import java.util.UUID; + /** - * * @author cbt33, Loki (Merfolk Looter) */ public final class CephalidLooter extends CardImpl { public CephalidLooter(UUID ownerId, CardSetInfo setInfo) { - super(ownerId,setInfo,new CardType[]{CardType.CREATURE},"{2}{U}"); + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{U}"); this.subtype.add(SubType.CEPHALID); this.subtype.add(SubType.ROGUE); @@ -29,7 +27,7 @@ public final class CephalidLooter extends CardImpl { this.toughness = new MageInt(1); // {tap}: Target player draws a card, then discards a card. - Ability ability = new SimpleActivatedAbility(Zone.BATTLEFIELD, new DrawDiscardTargetEffect(), new TapSourceCost()); + Ability ability = new SimpleActivatedAbility(new DrawDiscardTargetEffect(1, 1), new TapSourceCost()); ability.addTarget(new TargetPlayer()); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/g/GorexTheTombshell.java b/Mage.Sets/src/mage/cards/g/GorexTheTombshell.java index 9e3d5b9fbc6..f07ebd687bf 100644 --- a/Mage.Sets/src/mage/cards/g/GorexTheTombshell.java +++ b/Mage.Sets/src/mage/cards/g/GorexTheTombshell.java @@ -1,24 +1,23 @@ package mage.cards.g; import mage.MageInt; -import mage.MageObject; import mage.abilities.Ability; import mage.abilities.SpellAbility; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.AttacksTriggeredAbility; +import mage.abilities.common.DiesSourceTriggeredAbility; import mage.abilities.common.SimpleStaticAbility; import mage.abilities.costs.Cost; import mage.abilities.costs.common.ExileXFromYourGraveCost; import mage.abilities.effects.OneShotEffect; import mage.abilities.effects.common.cost.CostModificationEffectImpl; import mage.abilities.keyword.DeathtouchAbility; +import mage.abilities.meta.OrTriggeredAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.*; import mage.filter.StaticFilters; import mage.game.ExileZone; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.ZoneChangeEvent; import mage.players.Player; import mage.util.CardUtil; @@ -51,7 +50,12 @@ public final class GorexTheTombshell extends CardImpl { this.addAbility(DeathtouchAbility.getInstance()); // Whenever Gorex, the Tombshell attacks or dies, choose a card at random exiled with Gorex and put that card into its owner's hand. - this.addAbility(new GorexTheTombshellTriggeredAbility()); + this.addAbility(new OrTriggeredAbility( + Zone.BATTLEFIELD, new GorexTheTombshellReturnEffect(), false, + "Whenever {this} attacks or dies, ", + new AttacksTriggeredAbility(null, false), + new DiesSourceTriggeredAbility(null, false) + )); } private GorexTheTombshell(final GorexTheTombshell card) { @@ -106,52 +110,11 @@ class GorexTheTombshellCostReductionEffect extends CostModificationEffectImpl { } } -class GorexTheTombshellTriggeredAbility extends TriggeredAbilityImpl { - - GorexTheTombshellTriggeredAbility() { - super(Zone.BATTLEFIELD, new GorexTheTombshellReturnEffect()); - } - - private GorexTheTombshellTriggeredAbility(final GorexTheTombshellTriggeredAbility ability) { - super(ability); - } - - @Override - public GorexTheTombshellTriggeredAbility copy() { - return new GorexTheTombshellTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.ATTACKER_DECLARED - || event.getType() == GameEvent.EventType.ENTERS_THE_BATTLEFIELD; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - if (event.getType() == GameEvent.EventType.ATTACKER_DECLARED) { - return event.getSourceId().equals(getSourceId()); - } - ZoneChangeEvent zEvent = (ZoneChangeEvent) event; - return zEvent.isDiesEvent() && event.getTargetId().equals(getSourceId()); - } - - @Override - public boolean isInUseableZone(Game game, MageObject source, GameEvent event) { - return TriggeredAbilityImpl.isInUseableZoneDiesTrigger(this, event, game); - } - - @Override - public String getRule() { - return "Whenever {this} attacks or dies, choose a card at random " + - "exiled with {this} and put that card into its owner's hand."; - } -} - class GorexTheTombshellReturnEffect extends OneShotEffect { GorexTheTombshellReturnEffect() { super(Outcome.Benefit); + staticText = "choose a card at random exiled with {this} and put that card into its owner's hand"; } private GorexTheTombshellReturnEffect(final GorexTheTombshellReturnEffect effect) { diff --git a/Mage.Sets/src/mage/cards/s/ShadrixSilverquill.java b/Mage.Sets/src/mage/cards/s/ShadrixSilverquill.java index 109e9826b61..aaa5b0130b7 100644 --- a/Mage.Sets/src/mage/cards/s/ShadrixSilverquill.java +++ b/Mage.Sets/src/mage/cards/s/ShadrixSilverquill.java @@ -64,24 +64,16 @@ public final class ShadrixSilverquill extends CardImpl { // • Target player creates a 2/1 white and black Inkling creature token with flying. ability.addEffect(new CreateTokenTargetEffect(new InklingToken())); - TargetPlayer target = new TargetPlayer(filter1); - target.setTargetTag(1); - ability.addTarget(target.withChooseHint("to create a token")); + ability.addTarget(new TargetPlayer(filter1).setTargetTag(1).withChooseHint("to create a token")); // • Target player draws a card and loses 1 life. - Mode mode = new Mode(new DrawCardTargetEffect(1)); - mode.addEffect(new LoseLifeTargetEffect(1).setText("and loses 1 life")); - target = new TargetPlayer(filter2); - target.setTargetTag(2); - mode.addTarget(target.withChooseHint("to draw a card and lose 1 life")); - ability.addMode(mode); + ability.addMode(new Mode(new DrawCardTargetEffect(1)) + .addEffect(new LoseLifeTargetEffect(1).setText("and loses 1 life")) + .addTarget(new TargetPlayer(filter2).setTargetTag(2).withChooseHint("to draw a card and lose 1 life"))); // • Target player puts a +1/+1 counter on each creature they control. - mode = new Mode(new ShadrixSilverquillEffect()); - target = new TargetPlayer(filter3); - target.setTargetTag(3); - mode.addTarget(target.withChooseHint("to put a counter on each creature")); - ability.addMode(mode); + ability.addMode(new Mode(new ShadrixSilverquillEffect()) + .addTarget(new TargetPlayer(filter3).setTargetTag(3).withChooseHint("to put a counter on each creature"))); this.addAbility(ability); } diff --git a/Mage.Sets/src/mage/cards/v/VindictiveLich.java b/Mage.Sets/src/mage/cards/v/VindictiveLich.java index 9ec09cfb4ca..288ab66e1f8 100644 --- a/Mage.Sets/src/mage/cards/v/VindictiveLich.java +++ b/Mage.Sets/src/mage/cards/v/VindictiveLich.java @@ -12,10 +12,10 @@ import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; import mage.filter.FilterOpponent; +import mage.filter.FilterPlayer; import mage.filter.StaticFilters; import mage.filter.predicate.other.AnotherTargetPredicate; -import mage.target.Target; -import mage.target.common.TargetOpponent; +import mage.target.TargetPlayer; import java.util.UUID; @@ -24,6 +24,17 @@ import java.util.UUID; */ public final class VindictiveLich extends CardImpl { + private static final FilterPlayer filter0 = new FilterPlayer("a different player"); + private static final FilterPlayer filter1 = new FilterOpponent(); + private static final FilterPlayer filter2 = new FilterOpponent(); + private static final FilterPlayer filter3 = new FilterOpponent(); + + static { + filter1.add(new AnotherTargetPredicate(1, true)); + filter2.add(new AnotherTargetPredicate(2, true)); + filter3.add(new AnotherTargetPredicate(3, true)); + } + public VindictiveLich(UUID ownerId, CardSetInfo setInfo) { super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{3}{B}"); @@ -34,34 +45,22 @@ public final class VindictiveLich extends CardImpl { // When Vindictive Lich dies, choose one or more. Each mode must target a different player. // * Target opponent sacrifices a creature. - Ability ability = new DiesSourceTriggeredAbility(new SacrificeEffect(StaticFilters.FILTER_PERMANENT_CREATURE, 1, "target opponent")); + Ability ability = new DiesSourceTriggeredAbility(new SacrificeEffect( + StaticFilters.FILTER_PERMANENT_CREATURE, 1, "target opponent" + )); ability.getModes().setMinModes(1); ability.getModes().setMaxModes(3); ability.getModes().setEachModeOnlyOnce(true); - ability.getModes().setMaxModesFilter(new FilterOpponent("a different player")); - FilterOpponent filter = new FilterOpponent(); - filter.add(new AnotherTargetPredicate(1, true)); - Target target = new TargetOpponent(filter, false).withChooseHint("who sacrifice a creature"); - target.setTargetTag(1); - ability.addTarget(target); + ability.getModes().setMaxModesFilter(filter0); + ability.addTarget(new TargetPlayer(filter1).setTargetTag(1).withChooseHint("to sacrifice a creature")); // * Target opponent discards two cards. - Mode mode = new Mode(new DiscardTargetEffect(2, false)); - filter = new FilterOpponent(); - filter.add(new AnotherTargetPredicate(2, true)); - target = new TargetOpponent(filter, false); - target.setTargetTag(2); - mode.addTarget(target.withChooseHint("who discard a card")); - ability.addMode(mode); + ability.addMode(new Mode(new DiscardTargetEffect(2, false)) + .addTarget(new TargetPlayer(filter2).setTargetTag(2).withChooseHint("to discard a card"))); // * Target opponent loses 5 life. - mode = new Mode(new LoseLifeTargetEffect(5)); - filter = new FilterOpponent(); - filter.add(new AnotherTargetPredicate(3, true)); - target = new TargetOpponent(filter, false); - target.setTargetTag(3); - mode.addTarget(target.withChooseHint("who lose 5 life")); - ability.addMode(mode); + ability.addMode(new Mode(new LoseLifeTargetEffect(5)) + .addTarget(new TargetPlayer(filter3).setTargetTag(3).withChooseHint("to lose 5 life"))); this.addAbility(ability); } @@ -73,5 +72,4 @@ public final class VindictiveLich extends CardImpl { public VindictiveLich copy() { return new VindictiveLich(this); } - } diff --git a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java index a41aba190ee..d6b67067f6a 100644 --- a/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java +++ b/Mage.Sets/src/mage/sets/CommanderLegendsBattleForBaldursGate.java @@ -63,6 +63,7 @@ public final class CommanderLegendsBattleForBaldursGate extends ExpansionSet { cards.add(new SetCardInfo("Baba Lysaga, Night Witch", 266, Rarity.RARE, mage.cards.b.BabaLysagaNightWitch.class)); cards.add(new SetCardInfo("Bag of Holding", 299, Rarity.UNCOMMON, mage.cards.b.BagOfHolding.class)); cards.add(new SetCardInfo("Baldur's Gate", 345, Rarity.RARE, mage.cards.b.BaldursGate.class)); + cards.add(new SetCardInfo("Balor", 162, Rarity.MYTHIC, mage.cards.b.Balor.class)); cards.add(new SetCardInfo("Band Together", 216, Rarity.COMMON, mage.cards.b.BandTogether.class)); cards.add(new SetCardInfo("Bane's Contingency", 57, Rarity.UNCOMMON, mage.cards.b.BanesContingency.class)); cards.add(new SetCardInfo("Bane's Invoker", 7, Rarity.COMMON, mage.cards.b.BanesInvoker.class)); diff --git a/Mage/src/main/java/mage/abilities/effects/common/DrawDiscardTargetEffect.java b/Mage/src/main/java/mage/abilities/effects/common/DrawDiscardTargetEffect.java index 9b9c234d578..bed30783b07 100644 --- a/Mage/src/main/java/mage/abilities/effects/common/DrawDiscardTargetEffect.java +++ b/Mage/src/main/java/mage/abilities/effects/common/DrawDiscardTargetEffect.java @@ -1,5 +1,3 @@ - - package mage.abilities.effects.common; import mage.abilities.Ability; @@ -10,34 +8,40 @@ import mage.players.Player; import mage.util.CardUtil; /** - * * @author LevelX2 */ public class DrawDiscardTargetEffect extends OneShotEffect { - private int cardsToDraw; - private int cardsToDiscard; - - public DrawDiscardTargetEffect() { - this(1,1); - } + private final int cardsToDraw; + private final int cardsToDiscard; + private final boolean random; public DrawDiscardTargetEffect(int cardsToDraw, int cardsToDiscard) { + this(cardsToDraw, cardsToDiscard, false); + } + + public DrawDiscardTargetEffect(int cardsToDraw, int cardsToDiscard, boolean random) { super(Outcome.DrawCard); this.cardsToDraw = cardsToDraw; this.cardsToDiscard = cardsToDiscard; - staticText = new StringBuilder("Target player draws ") - .append(cardsToDraw == 1?"a": CardUtil.numberToText(cardsToDraw)) - .append(" card").append(cardsToDraw == 1?"": "s") + this.random = random; + staticText = new StringBuilder("target player draws ") + .append(CardUtil.numberToText(cardsToDraw, "a")) + .append(" card") + .append(cardsToDraw > 1 ? "s" : "") .append(", then discards ") - .append(cardsToDiscard == 1?"a": CardUtil.numberToText(cardsToDiscard)) - .append(" card").append(cardsToDiscard == 1?"": "s").toString(); + .append(CardUtil.numberToText(cardsToDiscard, "a")) + .append(" card") + .append(cardsToDiscard > 1 ? "s" : "") + .append(random ? "at random" : "") + .toString(); } - public DrawDiscardTargetEffect(final DrawDiscardTargetEffect effect) { + private DrawDiscardTargetEffect(final DrawDiscardTargetEffect effect) { super(effect); this.cardsToDraw = effect.cardsToDraw; this.cardsToDiscard = effect.cardsToDiscard; + this.random = effect.random; } @Override @@ -50,10 +54,9 @@ public class DrawDiscardTargetEffect extends OneShotEffect { Player player = game.getPlayer(getTargetPointer().getFirst(game, source)); if (player != null) { player.drawCards(cardsToDraw, source, game); - player.discard(cardsToDiscard, false, false, source, game); + player.discard(cardsToDiscard, random, false, source, game); return true; } return false; } - } diff --git a/Mage/src/main/java/mage/target/Target.java b/Mage/src/main/java/mage/target/Target.java index f88136cb84f..4bd7e1fb778 100644 --- a/Mage/src/main/java/mage/target/Target.java +++ b/Mage/src/main/java/mage/target/Target.java @@ -39,10 +39,10 @@ public interface Target extends Serializable { /** * Returns a set of all possible targets that match the criteria of the implemented Target class. * - * @param sourceControllerId UUID of the ability's controller - * @param source Ability which requires the targets - * @param game Current game - * @return Set of the UUIDs of possible targets + * @param sourceControllerId UUID of the ability's controller + * @param source Ability which requires the targets + * @param game Current game + * @return Set of the UUIDs of possible targets */ Set possibleTargets(UUID sourceControllerId, Ability source, Game game); @@ -143,7 +143,7 @@ public interface Target extends Serializable { int getTargetTag(); - void setTargetTag(int tag); + Target setTargetTag(int tag); Target getOriginalTarget(); diff --git a/Mage/src/main/java/mage/target/TargetImpl.java b/Mage/src/main/java/mage/target/TargetImpl.java index e539764f3a1..f7ae3d46997 100644 --- a/Mage/src/main/java/mage/target/TargetImpl.java +++ b/Mage/src/main/java/mage/target/TargetImpl.java @@ -554,8 +554,9 @@ public abstract class TargetImpl implements Target { * @param targetTag */ @Override - public void setTargetTag(int targetTag) { + public TargetImpl setTargetTag(int targetTag) { this.targetTag = targetTag; + return this; } @Override diff --git a/Mage/src/main/java/mage/target/common/TargetOpponent.java b/Mage/src/main/java/mage/target/common/TargetOpponent.java index 287653ba9f1..b974b870a2b 100644 --- a/Mage/src/main/java/mage/target/common/TargetOpponent.java +++ b/Mage/src/main/java/mage/target/common/TargetOpponent.java @@ -1,29 +1,25 @@ - package mage.target.common; import mage.filter.FilterOpponent; import mage.target.TargetPlayer; /** - * * @author BetaSteward_at_googlemail.com * @author North */ public class TargetOpponent extends TargetPlayer { + private static final FilterOpponent filter = new FilterOpponent(); + public TargetOpponent() { this(false); } - + public TargetOpponent(boolean notTarget) { - this(new FilterOpponent(), notTarget); - } - - public TargetOpponent(FilterOpponent filter, boolean notTarget) { super(1, 1, notTarget, filter); } - public TargetOpponent(final TargetOpponent target) { + private TargetOpponent(final TargetOpponent target) { super(target); }