From 1804b8df015949f734c6bc66c6878b214a15b5e5 Mon Sep 17 00:00:00 2001 From: AsterAether Date: Fri, 24 Apr 2020 15:39:53 +0200 Subject: [PATCH] Implemented Xyris and Kalamax (#6430) * Implemented Xyris, the Writhing Storm * Name change for Xyris's draw ability. * Implemented Kalamax, the Stormsire. * Added Kalamax and Xyris to Commander2020Edition Set. * Updated XyrisTheWrithingStorm drawCards implementation. * Fixed bug where "First card drawn" was not enforced. * Removed unnecessary Predicates.or, and replaced custom effect with CreateTokenEffect --- .../src/mage/cards/k/KalamaxTheStormsire.java | 137 ++++++++++++++++++ .../mage/cards/x/XyrisTheWrithingStorm.java | 129 +++++++++++++++++ .../src/mage/sets/Commander2020Edition.java | 2 + .../filter/common/FilterInstantSpell.java | 27 ++++ 4 files changed, 295 insertions(+) create mode 100644 Mage.Sets/src/mage/cards/k/KalamaxTheStormsire.java create mode 100644 Mage.Sets/src/mage/cards/x/XyrisTheWrithingStorm.java create mode 100644 Mage/src/main/java/mage/filter/common/FilterInstantSpell.java diff --git a/Mage.Sets/src/mage/cards/k/KalamaxTheStormsire.java b/Mage.Sets/src/mage/cards/k/KalamaxTheStormsire.java new file mode 100644 index 00000000000..99ca98fd8fc --- /dev/null +++ b/Mage.Sets/src/mage/cards/k/KalamaxTheStormsire.java @@ -0,0 +1,137 @@ +package mage.cards.k; + +import mage.MageInt; +import mage.MageObject; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.SpellCastControllerTriggeredAbility; +import mage.abilities.effects.Effect; +import mage.abilities.effects.common.CopyTargetSpellEffect; +import mage.abilities.effects.common.counter.AddCountersSourceEffect; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.Zone; +import mage.counters.CounterType; +import mage.filter.common.FilterInstantSpell; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.Permanent; +import mage.game.stack.Spell; +import mage.target.targetpointer.FixedTarget; +import mage.watchers.common.SpellsCastWatcher; + +import java.util.List; +import java.util.UUID; + +/** + * @author AsterAether + */ +public final class KalamaxTheStormsire extends CardImpl { + + public KalamaxTheStormsire(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{1}{G}{U}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.ELEMENTAL); + this.subtype.add(SubType.DINOSAUR); + this.power = new MageInt(4); + this.toughness = new MageInt(4); + + // Whenever you cast your first instant spell each turn, if Kalamax, the Stormsire is tapped, copy that spell. You may choose new targets for the copy. + this.addAbility(new KalamaxTheStormsireSpellCastAbility(), new SpellsCastWatcher()); + // Whenever you copy an instant spell, put a +1/+1 counter on Kalamax. + this.addAbility(new KalamaxTheStormsireCopyTriggeredAbility()); + } + + private KalamaxTheStormsire(final KalamaxTheStormsire card) { + super(card); + } + + @Override + public KalamaxTheStormsire copy() { + return new KalamaxTheStormsire(this); + } +} + +class KalamaxTheStormsireSpellCastAbility extends SpellCastControllerTriggeredAbility { + KalamaxTheStormsireSpellCastAbility() { + super(new CopyTargetSpellEffect(true), new FilterInstantSpell(), false); + } + + KalamaxTheStormsireSpellCastAbility(KalamaxTheStormsireSpellCastAbility ability) { + super(ability); + } + + @Override + public KalamaxTheStormsireSpellCastAbility copy() { + return new KalamaxTheStormsireSpellCastAbility(this); + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (super.checkTrigger(event, game)) { + SpellsCastWatcher watcher = game.getState().getWatcher(SpellsCastWatcher.class); + if (watcher != null) { + List spells = watcher.getSpellsCastThisTurn(event.getPlayerId()); + if (spells != null && spells.stream().filter(MageObject::isInstant).count() == 1) { + Spell spell = game.getStack().getSpell(event.getTargetId()); + if (spell != null && spell.isInstant()) { + for (Effect effect : this.getEffects()) { + effect.setTargetPointer(new FixedTarget(event.getTargetId())); + } + return true; + } + } + } + } + return false; + } + + @Override + public boolean checkInterveningIfClause(Game game) { + Permanent permanent = game.getPermanent(getSourceId()); + return permanent != null && permanent.isTapped(); + } + + @Override + public String getRule() { + return "Whenever you cast your first instant spell each turn, " + + "if Kalamax, the Stormsire is tapped, " + + "copy that spell. You may choose new targets for the copy."; + } +} + +class KalamaxTheStormsireCopyTriggeredAbility extends TriggeredAbilityImpl { + + KalamaxTheStormsireCopyTriggeredAbility() { + super(Zone.BATTLEFIELD, new AddCountersSourceEffect(CounterType.P1P1.createInstance()), false); + } + + private KalamaxTheStormsireCopyTriggeredAbility(final KalamaxTheStormsireCopyTriggeredAbility effect) { + super(effect); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.COPIED_STACKOBJECT; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + Spell spell = game.getSpell(event.getTargetId()); + return spell != null && spell.isControlledBy(getControllerId()) && spell.isInstant(); + } + + @Override + public KalamaxTheStormsireCopyTriggeredAbility copy() { + return new KalamaxTheStormsireCopyTriggeredAbility(this); + } + + @Override + public String getRule() { + return "Whenever you copy an instant spell, put a +1/+1 counter on {this}."; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/x/XyrisTheWrithingStorm.java b/Mage.Sets/src/mage/cards/x/XyrisTheWrithingStorm.java new file mode 100644 index 00000000000..85003d5036b --- /dev/null +++ b/Mage.Sets/src/mage/cards/x/XyrisTheWrithingStorm.java @@ -0,0 +1,129 @@ +package mage.cards.x; + +import mage.MageInt; +import mage.abilities.Ability; +import mage.abilities.TriggeredAbility; +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.common.DealsCombatDamageToAPlayerTriggeredAbility; +import mage.abilities.effects.OneShotEffect; +import mage.abilities.effects.common.CreateTokenEffect; +import mage.abilities.keyword.FlyingAbility; +import mage.cards.CardImpl; +import mage.cards.CardSetInfo; +import mage.constants.*; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.permanent.token.SnakeToken; +import mage.game.permanent.token.Token; +import mage.players.Player; +import mage.watchers.common.CardsDrawnDuringDrawStepWatcher; + +import java.util.UUID; + +/** + * @author AsterAether + */ +public final class XyrisTheWrithingStorm extends CardImpl { + + public XyrisTheWrithingStorm(UUID ownerId, CardSetInfo setInfo) { + super(ownerId, setInfo, new CardType[]{CardType.CREATURE}, "{2}{G}{U}{R}"); + + this.addSuperType(SuperType.LEGENDARY); + this.subtype.add(SubType.SNAKE); + this.subtype.add(SubType.LEVIATHAN); + this.power = new MageInt(3); + this.toughness = new MageInt(5); + + // Flying + this.addAbility(FlyingAbility.getInstance()); + + // Whenever an opponent draws a card except the first one they draw in each of their draw steps, create a 1/1 green Snake creature token. + this.addAbility(new XyrisTheWrithingStormDrawAbility(), new CardsDrawnDuringDrawStepWatcher()); + // Whenever Xyris, the Writhing Storm deals combat damage to a player, you and that player each draw that many cards. + this.addAbility(new DealsCombatDamageToAPlayerTriggeredAbility(new XyrisTheWrithingStormCombatDamageEffect(), false, true)); + } + + private XyrisTheWrithingStorm(final XyrisTheWrithingStorm card) { + super(card); + } + + @Override + public XyrisTheWrithingStorm copy() { + return new XyrisTheWrithingStorm(this); + } +} + +class XyrisTheWrithingStormDrawAbility extends TriggeredAbilityImpl { + + public XyrisTheWrithingStormDrawAbility() { + super(Zone.BATTLEFIELD, new CreateTokenEffect(new SnakeToken(), 1), false); + } + + public XyrisTheWrithingStormDrawAbility(final XyrisTheWrithingStormDrawAbility ability) { + super(ability); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.DREW_CARD; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + if (game.getPlayer(this.getControllerId()).hasOpponent(event.getPlayerId(), game)) { + if (game.isActivePlayer(event.getPlayerId()) + && game.getPhase().getStep().getType() == PhaseStep.DRAW) { + CardsDrawnDuringDrawStepWatcher watcher = game.getState().getWatcher(CardsDrawnDuringDrawStepWatcher.class); + if (watcher != null && watcher.getAmountCardsDrawn(event.getPlayerId()) > 1) { + return true; + } + } else { + return true; + } + } + return false; + } + + @Override + public TriggeredAbility copy() { + return new XyrisTheWrithingStormDrawAbility(this); + } + + @Override + public String getRule() { + return "Whenever an opponent draws a card except the first one they draw in each of their draw steps, create a 1/1 green Snake creature token."; + } +} + + +class XyrisTheWrithingStormCombatDamageEffect extends OneShotEffect { + + public XyrisTheWrithingStormCombatDamageEffect() { + super(Outcome.DrawCard); + this.staticText = "you and that player each draw that many cards."; + } + + public XyrisTheWrithingStormCombatDamageEffect(final XyrisTheWrithingStormCombatDamageEffect effect) { + super(effect); + } + + @Override + public XyrisTheWrithingStormCombatDamageEffect copy() { + return new XyrisTheWrithingStormCombatDamageEffect(this); + } + + @Override + public boolean apply(Game game, Ability source) { + Player sourceController = game.getPlayer(source.getControllerId()); + Player damagedPlayer = game.getPlayer(this.getTargetPointer().getFirst(game, source)); + if (sourceController != null && damagedPlayer != null) { + int amount = (Integer) getValue("damage"); + if (amount > 0) { + sourceController.drawCards(amount, source.getSourceId(), game); + damagedPlayer.drawCards(amount, source.getSourceId(), game); + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/sets/Commander2020Edition.java b/Mage.Sets/src/mage/sets/Commander2020Edition.java index b47a88c41b7..1d3283135fd 100644 --- a/Mage.Sets/src/mage/sets/Commander2020Edition.java +++ b/Mage.Sets/src/mage/sets/Commander2020Edition.java @@ -176,6 +176,7 @@ public final class Commander2020Edition extends ExpansionSet { cards.add(new SetCardInfo("Izzet Signet", 243, Rarity.UNCOMMON, mage.cards.i.IzzetSignet.class)); cards.add(new SetCardInfo("Jace, Architect of Thought", 114, Rarity.MYTHIC, mage.cards.j.JaceArchitectOfThought.class)); cards.add(new SetCardInfo("Jirina Kudro", 8, Rarity.MYTHIC, mage.cards.j.JirinaKudro.class)); + cards.add(new SetCardInfo("Kalamax, the Stormsire", 9, Rarity.MYTHIC, mage.cards.k.KalamaxTheStormsire.class)); cards.add(new SetCardInfo("Kalemne's Captain", 92, Rarity.RARE, mage.cards.k.KalemnesCaptain.class)); cards.add(new SetCardInfo("Karametra, God of Harvests", 218, Rarity.MYTHIC, mage.cards.k.KarametraGodOfHarvests.class)); cards.add(new SetCardInfo("Kathril, Aspect Warper", 10, Rarity.MYTHIC, mage.cards.k.KathrilAspectWarper.class)); @@ -343,6 +344,7 @@ public final class Commander2020Edition extends ExpansionSet { cards.add(new SetCardInfo("Wydwen, the Biting Gale", 235, Rarity.RARE, mage.cards.w.WydwenTheBitingGale.class)); cards.add(new SetCardInfo("Xathrid Necromancer", 141, Rarity.RARE, mage.cards.x.XathridNecromancer.class)); cards.add(new SetCardInfo("Yannik, Scavenging Sentinel", 19, Rarity.MYTHIC, mage.cards.y.YannikScavengingSentinel.class)); + cards.add(new SetCardInfo("Xyris, the Writhing Storm", 18, Rarity.MYTHIC, mage.cards.x.XyrisTheWrithingStorm.class)); cards.add(new SetCardInfo("Yavimaya Coast", 322, Rarity.RARE, mage.cards.y.YavimayaCoast.class)); cards.add(new SetCardInfo("Yavimaya Dryad", 197, Rarity.UNCOMMON, mage.cards.y.YavimayaDryad.class)); cards.add(new SetCardInfo("Zaxara, the Exemplary", 20, Rarity.MYTHIC, mage.cards.z.ZaxaraTheExemplary.class)); diff --git a/Mage/src/main/java/mage/filter/common/FilterInstantSpell.java b/Mage/src/main/java/mage/filter/common/FilterInstantSpell.java new file mode 100644 index 00000000000..c5c8ef6a889 --- /dev/null +++ b/Mage/src/main/java/mage/filter/common/FilterInstantSpell.java @@ -0,0 +1,27 @@ +package mage.filter.common; + +import mage.constants.CardType; +import mage.filter.FilterSpell; +import mage.filter.predicate.Predicates; + +public class FilterInstantSpell extends FilterSpell { + + public FilterInstantSpell() { + this("instant spell"); + } + + public FilterInstantSpell(String name) { + super(name); + this.add(CardType.INSTANT.getPredicate()); + } + + public FilterInstantSpell(final FilterInstantSpell filter) { + super(filter); + } + + @Override + public FilterInstantSpell copy() { + return new FilterInstantSpell(this); + } + +} \ No newline at end of file