From d7e856da680fd609399d7123fdd3d213c8c6cce0 Mon Sep 17 00:00:00 2001 From: Martin Petricek Date: Mon, 22 Oct 2018 15:09:12 +0200 Subject: [PATCH] Add several new cards (#1619) * Add several new cards: Balustrade Spy Corpse Augur Galvanoth Gift of Growth Living Artifact Marchesa, the Black Rose Phytotitan Pyromancer's Assault Settle the Score * Fix indentation in Galvanoth * Refactor Pyromancer's Assault, remove trigger condition from action text. * Generalize milling actions to fixed amount milling and "mill until certain number of cartain cards are milled". * Refactor cards to use new MillLibraryUntilAction * Add parser for MillLibraryUntilAction, so it can be used without need for groovy. * Add new parseable cards: Mind Funeral Consuming Aberration Destroy the Evidence Undercity Informer (no groovy needed) * Balustrade Spy now does not need groovy anymore. --- .../Balustrade_Spy.txt | 1 - .../Consuming_Aberration.txt | 1 - release/Magarena/scripts/Corpse_Augur.groovy | 23 ++++++++ .../Corpse_Augur.txt | 3 +- .../Destroy_the_Evidence.txt | 3 +- release/Magarena/scripts/Galvanoth.groovy | 40 +++++++++++++ .../Galvanoth.txt | 3 +- .../Magarena/scripts/Gift_of_Growth.groovy | 22 ++++++++ .../Gift_of_Growth.txt | 4 +- .../Magarena/scripts/Living_Artifact.groovy | 19 +++++++ .../Living_Artifact.txt | 3 +- .../scripts/Marchesa__the_Black_Rose.groovy | 51 +++++++++++++++++ .../Marchesa__the_Black_Rose.txt | 5 +- .../Mind_Funeral.txt | 3 +- release/Magarena/scripts/Mind_Grind.groovy | 9 +-- .../scripts/Mirko_Vosk__Mind_Drinker.groovy | 10 +--- release/Magarena/scripts/Phytotitan.groovy | 48 ++++++++++++++++ .../Phytotitan.txt | 3 +- .../scripts/Pyromancer_s_Assault.groovy | 25 +++++++++ .../Pyromancer_s_Assault.txt | 3 +- .../Magarena/scripts/Settle_the_Score.groovy | 24 ++++++++ .../Settle_the_Score.txt | 2 +- .../Undercity_Informer.txt | 1 - src/magic/model/ARG.java | 5 ++ .../model/action/AbstractMillAction.java | 56 +++++++++++++++++++ src/magic/model/action/MillLibraryAction.java | 42 ++++---------- .../model/action/MillLibraryUntilAction.java | 55 ++++++++++++++++++ .../model/event/MagicRuleEventAction.java | 19 +++++++ .../target/MagicTargetFilterFactory.java | 1 + 29 files changed, 414 insertions(+), 70 deletions(-) rename release/Magarena/{scripts_missing => scripts}/Balustrade_Spy.txt (96%) rename release/Magarena/{scripts_missing => scripts}/Consuming_Aberration.txt (97%) create mode 100644 release/Magarena/scripts/Corpse_Augur.groovy rename release/Magarena/{scripts_missing => scripts}/Corpse_Augur.txt (68%) rename release/Magarena/{scripts_missing => scripts}/Destroy_the_Evidence.txt (83%) create mode 100644 release/Magarena/scripts/Galvanoth.groovy rename release/Magarena/{scripts_missing => scripts}/Galvanoth.txt (62%) create mode 100644 release/Magarena/scripts/Gift_of_Growth.groovy rename release/Magarena/{scripts_missing => scripts}/Gift_of_Growth.txt (64%) create mode 100644 release/Magarena/scripts/Living_Artifact.groovy rename release/Magarena/{scripts_missing => scripts}/Living_Artifact.txt (84%) create mode 100644 release/Magarena/scripts/Marchesa__the_Black_Rose.groovy rename release/Magarena/{scripts_missing => scripts}/Marchesa__the_Black_Rose.txt (63%) rename release/Magarena/{scripts_missing => scripts}/Mind_Funeral.txt (73%) create mode 100644 release/Magarena/scripts/Phytotitan.groovy rename release/Magarena/{scripts_missing => scripts}/Phytotitan.txt (66%) create mode 100644 release/Magarena/scripts/Pyromancer_s_Assault.groovy rename release/Magarena/{scripts_missing => scripts}/Pyromancer_s_Assault.txt (70%) create mode 100644 release/Magarena/scripts/Settle_the_Score.groovy rename release/Magarena/{scripts_missing => scripts}/Settle_the_Score.txt (71%) rename release/Magarena/{scripts_missing => scripts}/Undercity_Informer.txt (96%) create mode 100644 src/magic/model/action/AbstractMillAction.java create mode 100644 src/magic/model/action/MillLibraryUntilAction.java diff --git a/release/Magarena/scripts_missing/Balustrade_Spy.txt b/release/Magarena/scripts/Balustrade_Spy.txt similarity index 96% rename from release/Magarena/scripts_missing/Balustrade_Spy.txt rename to release/Magarena/scripts/Balustrade_Spy.txt index c36d70cf55..46581eaafa 100644 --- a/release/Magarena/scripts_missing/Balustrade_Spy.txt +++ b/release/Magarena/scripts/Balustrade_Spy.txt @@ -11,4 +11,3 @@ ability=Flying;\ When SN enters the battlefield, target player reveals cards from the top of his or her library until he or she reveals a land card, then puts those cards into his or her graveyard. timing=main oracle=Flying\nWhen Balustrade Spy enters the battlefield, target player reveals cards from the top of his or her library until he or she reveals a land card, then puts those cards into his or her graveyard. -status=needs groovy diff --git a/release/Magarena/scripts_missing/Consuming_Aberration.txt b/release/Magarena/scripts/Consuming_Aberration.txt similarity index 97% rename from release/Magarena/scripts_missing/Consuming_Aberration.txt rename to release/Magarena/scripts/Consuming_Aberration.txt index 14b7f4e911..0d2d8b8a69 100644 --- a/release/Magarena/scripts_missing/Consuming_Aberration.txt +++ b/release/Magarena/scripts/Consuming_Aberration.txt @@ -11,4 +11,3 @@ ability=SN's power and toughness are each equal to the number of cards in your o Whenever you cast a spell, each opponent reveals cards from the top of his or her library until he or she reveals a land card, then puts those cards into his or her graveyard. timing=main oracle=Consuming Aberration's power and toughness are each equal to the number of cards in your opponents' graveyards.\nWhenever you cast a spell, each opponent reveals cards from the top of his or her library until he or she reveals a land card, then puts those cards into his or her graveyard. -status=needs groovy diff --git a/release/Magarena/scripts/Corpse_Augur.groovy b/release/Magarena/scripts/Corpse_Augur.groovy new file mode 100644 index 0000000000..78181a427f --- /dev/null +++ b/release/Magarena/scripts/Corpse_Augur.groovy @@ -0,0 +1,23 @@ +// When SN dies, you draw X cards and you lose X life, where X is the number of creature cards in target player's graveyard. + +[ + new ThisDiesTrigger() { + @Override + public MagicEvent executeTrigger(final MagicGame game,final MagicPermanent source, final MagicPermanent died) { + return new MagicEvent( + source, + TARGET_PLAYER, + this, + "PN draws X cards and loses X life, where X is the number of creature cards in target player's graveyard" + ); + } + @Override + public void executeEvent(final MagicGame game, final MagicEvent event) { + event.processTargetPlayer(game, { + int creatureCards = it.getGraveyard().getNrOf(MagicType.Creature); + game.doAction(new DrawAction(event.getPlayer(), creatureCards)); + game.doAction(new ChangeLifeAction(event.getPlayer(), -creatureCards)); + }); + } + } +] diff --git a/release/Magarena/scripts_missing/Corpse_Augur.txt b/release/Magarena/scripts/Corpse_Augur.txt similarity index 68% rename from release/Magarena/scripts_missing/Corpse_Augur.txt rename to release/Magarena/scripts/Corpse_Augur.txt index e3dea7274b..8cafaaa6b0 100644 --- a/release/Magarena/scripts_missing/Corpse_Augur.txt +++ b/release/Magarena/scripts/Corpse_Augur.txt @@ -7,7 +7,6 @@ type=Creature subtype=Zombie,Wizard cost={3}{B} pt=4/2 -ability=When SN dies, you draw X cards and you lose X life, where X is the number of creature cards in target player's graveyard. timing=main +requires_groovy_code oracle=When Corpse Augur dies, you draw X cards and you lose X life, where X is the number of creature cards in target player's graveyard. -status=needs groovy diff --git a/release/Magarena/scripts_missing/Destroy_the_Evidence.txt b/release/Magarena/scripts/Destroy_the_Evidence.txt similarity index 83% rename from release/Magarena/scripts_missing/Destroy_the_Evidence.txt rename to release/Magarena/scripts/Destroy_the_Evidence.txt index 20155f7c38..f306ad2032 100644 --- a/release/Magarena/scripts_missing/Destroy_the_Evidence.txt +++ b/release/Magarena/scripts/Destroy_the_Evidence.txt @@ -4,7 +4,6 @@ value=2.180 rarity=C type=Sorcery cost={4}{B} -effect=Destroy target land. Its controller reveals cards from the top of his or her library until he or she reveals a land card, then puts those cards into his or her graveyard. +effect=Destroy target land.~Its controller reveals cards from the top of his or her library until he or she reveals a land card, then puts those cards into his or her graveyard. timing=main oracle=Destroy target land. Its controller reveals cards from the top of his or her library until he or she reveals a land card, then puts those cards into his or her graveyard. -status=needs groovy diff --git a/release/Magarena/scripts/Galvanoth.groovy b/release/Magarena/scripts/Galvanoth.groovy new file mode 100644 index 0000000000..5b7b6be86d --- /dev/null +++ b/release/Magarena/scripts/Galvanoth.groovy @@ -0,0 +1,40 @@ +def action = { + final MagicGame game, final MagicEvent event -> + if (event.isYes()) { + game.doAction(CastCardAction.WithoutManaCost(event.getPlayer(), event.getRefCard(), MagicLocationType.OwnersLibrary, MagicLocationType.Graveyard)); + } +} + +[ + new AtYourUpkeepTrigger() { + @Override + public MagicEvent executeTrigger(final MagicGame game,final MagicPermanent permanent,final MagicPlayer upkeepPlayer) { + return new MagicEvent( + permanent, + new MagicMayChoice("look at the top card of your library?"), + this, + "PN may\$ looks at the top card of PN's library." + ); + } + + @Override + public void executeEvent(final MagicGame game, final MagicEvent event) { + if (event.isYes()) { + final MagicPlayer player = event.getPlayer(); + final MagicCardList cards = player.getLibrary().getCardsFromTop(1); + for (final MagicCard card : cards) { + game.doAction(new LookAction(card, player, "top card of your library")); + if (card.hasType(MagicType.Instant) || card.hasType(MagicType.Sorcery)) { + game.addEvent(new MagicEvent( + event.getSource(), + new MagicMayChoice("Cast the card without paying its mana cost?"), + card, + action, + "If it's an instant or sorcery card, PN may\$ cast it without paying its mana cost." + )); + } + } + } + } + } +] diff --git a/release/Magarena/scripts_missing/Galvanoth.txt b/release/Magarena/scripts/Galvanoth.txt similarity index 62% rename from release/Magarena/scripts_missing/Galvanoth.txt rename to release/Magarena/scripts/Galvanoth.txt index cde7aa2462..27e594f24b 100644 --- a/release/Magarena/scripts_missing/Galvanoth.txt +++ b/release/Magarena/scripts/Galvanoth.txt @@ -6,7 +6,6 @@ type=Creature subtype=Beast cost={3}{R}{R} pt=3/3 -ability=At the beginning of your upkeep, you may look at the top card of your library. If it's an instant or sorcery card, you may cast it without paying its mana cost. timing=main oracle=At the beginning of your upkeep, you may look at the top card of your library. If it's an instant or sorcery card, you may cast it without paying its mana cost. -status=needs groovy +requires_groovy_code diff --git a/release/Magarena/scripts/Gift_of_Growth.groovy b/release/Magarena/scripts/Gift_of_Growth.groovy new file mode 100644 index 0000000000..e843b889b5 --- /dev/null +++ b/release/Magarena/scripts/Gift_of_Growth.groovy @@ -0,0 +1,22 @@ +[ + new MagicSpellCardEvent() { + @Override + public MagicEvent getEvent(final MagicCardOnStack cardOnStack, final MagicPayedCost payedCost) { + return new MagicEvent( + cardOnStack, + POS_TARGET_CREATURE, + this, + "Untap target creature\$. It gets +2/+2 until end of turn. " + + "If SN was kicked, that creature gets +4/+4 instead." + ); + } + @Override + public void executeEvent(final MagicGame game, final MagicEvent event) { + event.processTargetPermanent(game, { + game.doAction(new UntapAction(it)); + int amount = event.isKicked() ? 4 : 2; + game.doAction(new ChangeTurnPTAction(it, amount, amount)); + }); + } + } +] diff --git a/release/Magarena/scripts_missing/Gift_of_Growth.txt b/release/Magarena/scripts/Gift_of_Growth.txt similarity index 64% rename from release/Magarena/scripts_missing/Gift_of_Growth.txt rename to release/Magarena/scripts/Gift_of_Growth.txt index 1f0983e5b9..316da41918 100644 --- a/release/Magarena/scripts_missing/Gift_of_Growth.txt +++ b/release/Magarena/scripts/Gift_of_Growth.txt @@ -5,6 +5,6 @@ rarity=C type=Instant cost={1}{G} ability=Kicker {2} -effect=Untap target creature. It gets +2/+2 until end of turn. If this spell was kicked, that creature gets +4/+4 until end of turn instead. -timing=removal +timing=pump +requires_groovy_code oracle=Kicker {2}\nUntap target creature. It gets +2/+2 until end of turn. If this spell was kicked, that creature gets +4/+4 until end of turn instead. diff --git a/release/Magarena/scripts/Living_Artifact.groovy b/release/Magarena/scripts/Living_Artifact.groovy new file mode 100644 index 0000000000..055afa8a08 --- /dev/null +++ b/release/Magarena/scripts/Living_Artifact.groovy @@ -0,0 +1,19 @@ +[ + new DamageIsDealtTrigger() { + @Override + public MagicEvent executeTrigger(final MagicGame game,final MagicPermanent permanent,final MagicDamage damage) { + return permanent.isController(damage.getTarget()) ? + new MagicEvent( + permanent, + damage.getDealtAmount(), + this, + "Put RN vitality counters on SN." + ): + MagicEvent.NONE; + } + @Override + public void executeEvent(final MagicGame game, final MagicEvent event) { + game.doAction(new ChangeCountersAction(event.getPlayer(),event.getPermanent(),MagicCounterType.Vitality,event.getRefInt())); + } + } +] diff --git a/release/Magarena/scripts_missing/Living_Artifact.txt b/release/Magarena/scripts/Living_Artifact.txt similarity index 84% rename from release/Magarena/scripts_missing/Living_Artifact.txt rename to release/Magarena/scripts/Living_Artifact.txt index 14e0bacd0e..397ef94b93 100644 --- a/release/Magarena/scripts_missing/Living_Artifact.txt +++ b/release/Magarena/scripts/Living_Artifact.txt @@ -6,9 +6,8 @@ type=Enchantment subtype=Aura cost={G} ability=Enchant artifact;\ - Whenever you're dealt damage, put that many vitality counters on SN.;\ At the beginning of your upkeep, you may remove a vitality counter from SN. If you do, you gain 1 life. timing=aura enchant=default,artifact oracle=Enchant artifact\nWhenever you're dealt damage, put that many vitality counters on Living Artifact.\nAt the beginning of your upkeep, you may remove a vitality counter from Living Artifact. If you do, you gain 1 life. -status=needs groovy +requires_groovy_code diff --git a/release/Magarena/scripts/Marchesa__the_Black_Rose.groovy b/release/Magarena/scripts/Marchesa__the_Black_Rose.groovy new file mode 100644 index 0000000000..af839f7a33 --- /dev/null +++ b/release/Magarena/scripts/Marchesa__the_Black_Rose.groovy @@ -0,0 +1,51 @@ +def DelayedTrigger = { + final MagicPermanent staleSource, final MagicPlayer stalePlayer, final MagicCard staleCard -> + return new AtEndOfTurnTrigger() { + @Override + public MagicEvent executeTrigger(final MagicGame game, final MagicPermanent permanent, final MagicPlayer eotPlayer) { + game.addDelayedAction(new RemoveTriggerAction(this)); + + final MagicCard mappedCard = staleCard.getOwner().map(game).getGraveyard().getCard(staleCard.getId()); + + return mappedCard.isInGraveyard() ? + new MagicEvent( + game.createDelayedSource(staleSource, stalePlayer), + mappedCard, + this, + "Return RN to the battlefield under PN's control." + ): + MagicEvent.NONE; + } + @Override + public void executeEvent(final MagicGame game, final MagicEvent event) { + game.doAction(new ReanimateAction( + event.getRefCard(), + event.getPlayer() + )); + } + }; +} + +[ + new OtherDiesTrigger() { + @Override + public MagicEvent executeTrigger(final MagicGame game,final MagicPermanent permanent,final MagicPermanent otherPermanent) { + return otherPermanent.isCreature() && otherPermanent.isNonToken() && otherPermanent.isFriend(permanent) && otherPermanent.getCounters(MagicCounterType.PlusOne) ? + new MagicEvent( + permanent, + otherPermanent.getCard(), + this, + "Return RN to the battlefield under PN's control at the beginning of the next end step." + ) : + MagicEvent.NONE; + } + @Override + public void executeEvent(final MagicGame game, final MagicEvent event) { + game.doAction(new AddTriggerAction(DelayedTrigger( + event.getPermanent(), + event.getPlayer(), + event.getRefCard() + ))); + } + } +] diff --git a/release/Magarena/scripts_missing/Marchesa__the_Black_Rose.txt b/release/Magarena/scripts/Marchesa__the_Black_Rose.txt similarity index 63% rename from release/Magarena/scripts_missing/Marchesa__the_Black_Rose.txt rename to release/Magarena/scripts/Marchesa__the_Black_Rose.txt index 65b95531d1..4f0f364a41 100644 --- a/release/Magarena/scripts_missing/Marchesa__the_Black_Rose.txt +++ b/release/Magarena/scripts/Marchesa__the_Black_Rose.txt @@ -7,8 +7,7 @@ subtype=Human,Wizard cost={1}{U}{B}{R} pt=3/3 ability=Dethrone;\ - Other creatures you control have dethrone.;\ - Whenever a creature you control with a +1/+1 counter on it dies, return that card to the battlefield under your control at the beginning of the next end step. + Other creatures you control have dethrone. timing=main oracle=Dethrone\nOther creatures you control have dethrone.\nWhenever a creature you control with a +1/+1 counter on it dies, return that card to the battlefield under your control at the beginning of the next end step. -status=needs groovy +requires_groovy_code diff --git a/release/Magarena/scripts_missing/Mind_Funeral.txt b/release/Magarena/scripts/Mind_Funeral.txt similarity index 73% rename from release/Magarena/scripts_missing/Mind_Funeral.txt rename to release/Magarena/scripts/Mind_Funeral.txt index 277970c31e..81008d0ae5 100644 --- a/release/Magarena/scripts_missing/Mind_Funeral.txt +++ b/release/Magarena/scripts/Mind_Funeral.txt @@ -4,7 +4,6 @@ value=3.833 rarity=U type=Sorcery cost={1}{U}{B} -effect=Target opponent reveals cards from the top of his or her library until four land cards are revealed. That player puts all cards revealed this way into his or her graveyard. +effect=Target opponent reveals cards from the top of his or her library until he or she reveals four land cards, then puts those cards into his or her graveyard. timing=main oracle=Target opponent reveals cards from the top of his or her library until four land cards are revealed. That player puts all cards revealed this way into his or her graveyard. -status=needs groovy diff --git a/release/Magarena/scripts/Mind_Grind.groovy b/release/Magarena/scripts/Mind_Grind.groovy index 88b4c2ba20..04f3d5c0d8 100644 --- a/release/Magarena/scripts/Mind_Grind.groovy +++ b/release/Magarena/scripts/Mind_Grind.groovy @@ -15,14 +15,7 @@ public void executeEvent(final MagicGame game, final MagicEvent event) { final int amount = event.getRefInt(); final MagicPlayer player = event.getPlayer(); - final MagicCardList library = player.getLibrary(); - int landCards = 0; - while (landCards < amount && library.size() > 0) { - if (library.getCardAtTop().hasType(MagicType.Land)) { - landCards++; - } - game.doAction(new MillLibraryAction(player,1)); - } + game.doAction(new MillLibraryUntilAction(player, MagicType.Land, amount)); } } ] diff --git a/release/Magarena/scripts/Mirko_Vosk__Mind_Drinker.groovy b/release/Magarena/scripts/Mirko_Vosk__Mind_Drinker.groovy index f6b08aa70d..ca61340506 100644 --- a/release/Magarena/scripts/Mirko_Vosk__Mind_Drinker.groovy +++ b/release/Magarena/scripts/Mirko_Vosk__Mind_Drinker.groovy @@ -17,15 +17,7 @@ public void executeEvent(final MagicGame game, final MagicEvent event) { final int amount = 4; final MagicPlayer player = event.getRefPlayer(); - final MagicCardList library = player.getLibrary(); - int landCards = 0; - while (landCards < amount && library.size() > 0) { - game.doAction(new RevealAction(library.getCardAtTop())); - if (library.getCardAtTop().hasType(MagicType.Land)) { - landCards++; - } - game.doAction(new MillLibraryAction(player,1)); - } + game.doAction(new MillLibraryUntilAction(player, MagicType.Land, amount)); } } ] diff --git a/release/Magarena/scripts/Phytotitan.groovy b/release/Magarena/scripts/Phytotitan.groovy new file mode 100644 index 0000000000..544646d624 --- /dev/null +++ b/release/Magarena/scripts/Phytotitan.groovy @@ -0,0 +1,48 @@ +def DelayedTrigger = { + final MagicSource staleSource, final MagicPlayer stalePlayer, final MagicCard staleCard -> + return new AtUpkeepTrigger() { + @Override + public boolean accept(final MagicPermanent permanent, final MagicPlayer upkeepPlayer) { + return stalePlayer.getId() == upkeepPlayer.getId(); + } + @Override + public MagicEvent executeTrigger(final MagicGame game,final MagicPermanent permanent,final MagicPlayer upkeepPlayer) { + game.addDelayedAction(new RemoveTriggerAction(this)); + final MagicCard mappedCard = staleCard.getOwner().map(game).getGraveyard().getCard(staleCard.getId()); + + return mappedCard.isInGraveyard() ? + new MagicEvent( + game.createDelayedSource(staleSource, stalePlayer), + mappedCard, + this, + "PN returns RN to the battlefield tapped." + ): + MagicEvent.NONE; + } + @Override + public void executeEvent(final MagicGame game, final MagicEvent event) { + game.doAction(new ReanimateAction(event.getRefCard(),event.getPlayer(),MagicPlayMod.TAPPED)); + } + } +} + +[ + new ThisDiesTrigger() { + @Override + public MagicEvent executeTrigger(final MagicGame game,final MagicPermanent source, final MagicPermanent died) { + return new MagicEvent( + source, + source.getOwner(), + source.getCard(), + this, + "Return SN to the battlefield tapped under its owner's control at the beginning of PN's next upkeep." + ); + } + @Override + public void executeEvent(final MagicGame game, final MagicEvent event) { + game.doAction(new AddTriggerAction( + DelayedTrigger(event.getSource(), event.getPlayer(), event.getRefCard()) + )); + } + } +] diff --git a/release/Magarena/scripts_missing/Phytotitan.txt b/release/Magarena/scripts/Phytotitan.txt similarity index 66% rename from release/Magarena/scripts_missing/Phytotitan.txt rename to release/Magarena/scripts/Phytotitan.txt index 07bf0e5b24..96b429e0fb 100644 --- a/release/Magarena/scripts_missing/Phytotitan.txt +++ b/release/Magarena/scripts/Phytotitan.txt @@ -6,7 +6,6 @@ type=Creature subtype=Plant,Elemental cost={4}{G}{G} pt=7/2 -ability=When SN dies, return it to the battlefield tapped under its owner's control at the beginning of his or her next upkeep. timing=main +requires_groovy_code oracle=When Phytotitan dies, return it to the battlefield tapped under its owner's control at the beginning of his or her next upkeep. -status=needs groovy diff --git a/release/Magarena/scripts/Pyromancer_s_Assault.groovy b/release/Magarena/scripts/Pyromancer_s_Assault.groovy new file mode 100644 index 0000000000..f82e0ce1cf --- /dev/null +++ b/release/Magarena/scripts/Pyromancer_s_Assault.groovy @@ -0,0 +1,25 @@ +[ + new OtherSpellIsCastTrigger() { + @Override + public boolean accept(final MagicPermanent permanent, final MagicCardOnStack spell) { + return permanent.isController(spell.getController()) && permanent.getController().getSpellsCast() == 1; + } + + @Override + public MagicEvent executeTrigger(final MagicGame game, final MagicPermanent permanent, final MagicCardOnStack cardOnStack) { + return new MagicEvent( + permanent, + NEG_TARGET_CREATURE_OR_PLAYER, + this, + "SN deals 2 damage to target creature or player\$." + ); + } + + @Override + public void executeEvent(final MagicGame game, final MagicEvent event) { + event.processTarget(game, { + game.doAction(new DealDamageAction(event.getSource(),it,2)); + }); + } + } +] diff --git a/release/Magarena/scripts_missing/Pyromancer_s_Assault.txt b/release/Magarena/scripts/Pyromancer_s_Assault.txt similarity index 70% rename from release/Magarena/scripts_missing/Pyromancer_s_Assault.txt rename to release/Magarena/scripts/Pyromancer_s_Assault.txt index ae4eb24324..6f13cd71f2 100644 --- a/release/Magarena/scripts_missing/Pyromancer_s_Assault.txt +++ b/release/Magarena/scripts/Pyromancer_s_Assault.txt @@ -5,7 +5,6 @@ value=2.500 rarity=U type=Enchantment cost={3}{R} -ability=Whenever you cast your second spell each turn, SN deals 2 damage to target creature or player. timing=enchantment oracle=Whenever you cast your second spell each turn, Pyromancer's Assault deals 2 damage to target creature or player. -status=needs groovy +requires_groovy_code \ No newline at end of file diff --git a/release/Magarena/scripts/Settle_the_Score.groovy b/release/Magarena/scripts/Settle_the_Score.groovy new file mode 100644 index 0000000000..c6b13737c1 --- /dev/null +++ b/release/Magarena/scripts/Settle_the_Score.groovy @@ -0,0 +1,24 @@ +def effect = MagicRuleEventAction.create("Put two loyalty counters on a planeswalker you control.") + +[ + new MagicSpellCardEvent() { + @Override + public MagicEvent getEvent(final MagicCardOnStack cardOnStack,final MagicPayedCost payedCost) { + return new MagicEvent( + cardOnStack, + NEG_TARGET_CREATURE, + MagicExileTargetPicker.create(), + this, + "Exile target creature\$." + ); + } + + @Override + public void executeEvent(final MagicGame game, final MagicEvent event) { + event.processTargetPermanent(game, { + game.doAction(new RemoveFromPlayAction(it, MagicLocationType.Exile)); + game.addEvent(effect.getEvent(event)); + }); + } + } +] diff --git a/release/Magarena/scripts_missing/Settle_the_Score.txt b/release/Magarena/scripts/Settle_the_Score.txt similarity index 71% rename from release/Magarena/scripts_missing/Settle_the_Score.txt rename to release/Magarena/scripts/Settle_the_Score.txt index 50defa2651..e8941cf9c1 100644 --- a/release/Magarena/scripts_missing/Settle_the_Score.txt +++ b/release/Magarena/scripts/Settle_the_Score.txt @@ -4,6 +4,6 @@ value=2.500 rarity=U type=Sorcery cost={2}{B}{B} -effect=Exile target creature. Put two loyalty counters on a planeswalker you control. timing=main +requires_groovy_code oracle=Exile target creature. Put two loyalty counters on a planeswalker you control. diff --git a/release/Magarena/scripts_missing/Undercity_Informer.txt b/release/Magarena/scripts/Undercity_Informer.txt similarity index 96% rename from release/Magarena/scripts_missing/Undercity_Informer.txt rename to release/Magarena/scripts/Undercity_Informer.txt index c59d4e6493..02cddad50d 100644 --- a/release/Magarena/scripts_missing/Undercity_Informer.txt +++ b/release/Magarena/scripts/Undercity_Informer.txt @@ -9,4 +9,3 @@ pt=2/3 ability={1}, Sacrifice a creature: Target player reveals cards from the top of his or her library until he or she reveals a land card, then puts those cards into his or her graveyard. timing=main oracle={1}, Sacrifice a creature: Target player reveals cards from the top of his or her library until he or she reveals a land card, then puts those cards into his or her graveyard. -status=needs groovy diff --git a/src/magic/model/ARG.java b/src/magic/model/ARG.java index da3f3a30bc..ecccc4b74a 100644 --- a/src/magic/model/ARG.java +++ b/src/magic/model/ARG.java @@ -23,6 +23,7 @@ public class ARG { public static final String THING = "(permanent|creature|artifact|land|spell or ability|spell|ability)"; public static final String PLAYER = "(player|opponent)"; public static final String EVENQUOTES = "(?=([^\"]*'[^\"]*')*[^\"]*$)"; + public static final String CARDTYPE = "(?(creature|artifact|land|enchantment|conspiracy|instant|phenomenon|plane|planeswalker|scheme|sorcery|tribal|vanguard))"; public static final String ENERGY = "(?(\\{E\\})+)"; public static int energy(final Matcher m) { @@ -55,6 +56,10 @@ public class ARG { return MagicAmountParser.build(m.group("amount")); } + public static MagicType cardType(final Matcher m) { + return MagicType.getType(m.group("cardtype")); + } + public static final String AMOUNT2 = "(?[^ ]+?)"; public static int amount2(final Matcher m) { return EnglishToInt.convert(m.group("amount2")); diff --git a/src/magic/model/action/AbstractMillAction.java b/src/magic/model/action/AbstractMillAction.java new file mode 100644 index 0000000000..dfb59ed0c6 --- /dev/null +++ b/src/magic/model/action/AbstractMillAction.java @@ -0,0 +1,56 @@ +package magic.model.action; + +import magic.ai.ArtificialScoringSystem; +import magic.model.MagicCard; +import magic.model.MagicCardList; +import magic.model.MagicGame; +import magic.model.MagicMessage; +import magic.model.MagicPlayer; + +import java.util.List; + +/** + * Ancestor of various library-milling actions + */ +public abstract class AbstractMillAction extends MagicAction { + protected final MagicPlayer player; + protected final MagicCardList milledCards = new MagicCardList(); + + protected AbstractMillAction(final MagicPlayer player) { + this.player = player; + } + + public List getMilledCards() { + return milledCards; + } + + @Override + public void undoAction(final MagicGame game) { + } + + protected String getMillDescription(int finalCount) { + return String.format("top %d cards", finalCount); + } + + protected abstract void setCardsToMill(MagicGame game); + + + @Override + public void doAction(final MagicGame game) { + getMilledCards().clear(); + setCardsToMill(game); + final int count = getMilledCards().size(); + if (count > 0) { + setScore(player,ArtificialScoringSystem.getMillScore(count)); + game.logAppendMessage( + player, + String.format( + "%s puts the %s of his or her library into his or her graveyard. (%s)", + player, + getMillDescription(count), + count > 5 ? "..." : MagicMessage.getTokenizedCardNames(getMilledCards()) + ) + ); + } + } +} diff --git a/src/magic/model/action/MillLibraryAction.java b/src/magic/model/action/MillLibraryAction.java index c40aed667b..bbd827455a 100644 --- a/src/magic/model/action/MillLibraryAction.java +++ b/src/magic/model/action/MillLibraryAction.java @@ -1,28 +1,30 @@ package magic.model.action; -import magic.ai.ArtificialScoringSystem; import magic.model.MagicCard; import magic.model.MagicCardList; import magic.model.MagicGame; import magic.model.MagicLocationType; import magic.model.MagicPlayer; -import java.util.List; -import magic.model.MagicMessage; +/** + * Action that removes fixed amount of cards from players library and moves them to that player's graveyard. + */ +public class MillLibraryAction extends AbstractMillAction { -public class MillLibraryAction extends MagicAction { - - private final MagicPlayer player; private final int amount; - private final MagicCardList milledCards = new MagicCardList(); + /** + * Mill target player's library (move cards to that player's graveyard). + * + * @param aPlayer target player + * @param aAmount number of cards to move + */ public MillLibraryAction(final MagicPlayer aPlayer,final int aAmount) { - player = aPlayer; + super(aPlayer); amount = aAmount; } - @Override - public void doAction(final MagicGame game) { + protected void setCardsToMill(MagicGame game) { final MagicCardList topN = player.getLibrary().getCardsFromTop(amount); for (final MagicCard card : topN) { milledCards.add(card); @@ -32,25 +34,5 @@ public class MillLibraryAction extends MagicAction { MagicLocationType.Graveyard )); } - final int count = topN.size(); - if (count > 0) { - setScore(player,ArtificialScoringSystem.getMillScore(count)); - game.logAppendMessage( - player, - String.format( - "%s puts the top %d cards of his or her library into his or her graveyard. (%s)", - player, - count, - count > 5 ? "..." : MagicMessage.getTokenizedCardNames(topN) - ) - ); - } } - - public List getMilledCards() { - return milledCards; - } - - @Override - public void undoAction(final MagicGame game) {} } diff --git a/src/magic/model/action/MillLibraryUntilAction.java b/src/magic/model/action/MillLibraryUntilAction.java new file mode 100644 index 0000000000..eecde6e9c4 --- /dev/null +++ b/src/magic/model/action/MillLibraryUntilAction.java @@ -0,0 +1,55 @@ +package magic.model.action; + +import magic.model.MagicCard; +import magic.model.MagicCardList; +import magic.model.MagicGame; +import magic.model.MagicLocationType; +import magic.model.MagicPlayer; +import magic.model.MagicType; + +/** + * Action that removes cards from players library and moves them to that player's graveyard, + * until certain card type is seen given number of times (or whole library is milled). + */ +public class MillLibraryUntilAction extends AbstractMillAction { + private final MagicType cardType; + private final int cards; + + /** + * Mill target player's library (move cards to that player's graveyard) until certain amount of certain cards are + * moved. + * + * @param aPlayer target player + * @param aCardType type of card to check + * @param aCards number of those cards to encounter in order for milling to stop + */ + public MillLibraryUntilAction(final MagicPlayer aPlayer, final MagicType aCardType, int aCards) { + super(aPlayer); + cardType = aCardType; + cards = aCards; + } + + @Override + protected String getMillDescription(int finalCount) { + return String.format("top %d cards - until %d %s(s) are seen -", finalCount, cards, cardType.getDisplayName()); + } + + protected void setCardsToMill(MagicGame game) { + final MagicCardList all = player.getLibrary().getCardsFromTop(Integer.MAX_VALUE); + int seenTargets = 0; + for (final MagicCard card : all) { + milledCards.add(card); + game.doAction(new ShiftCardAction( + card, + MagicLocationType.OwnersLibrary, + MagicLocationType.Graveyard + )); + if (card.hasType(cardType)) { + seenTargets++; + } + if (seenTargets >= cards) { + break; + } + } + } +} diff --git a/src/magic/model/event/MagicRuleEventAction.java b/src/magic/model/event/MagicRuleEventAction.java index 5a918c9dc3..4247a4974a 100644 --- a/src/magic/model/event/MagicRuleEventAction.java +++ b/src/magic/model/event/MagicRuleEventAction.java @@ -2097,6 +2097,25 @@ public enum MagicRuleEventAction { }; } }, + MillUntil( + ARG.PLAYERS + "( )?reveal(s)? cards from the top of (your|his or her) library until (you|he or she) reveal(s) " + + ARG.AMOUNT + "?( )?" + ARG.CARDTYPE + " card(s)?, then puts those cards into (your|his or her) graveyard", + MagicTiming.Draw, + "MillUntil" + ) { + @Override + public MagicEventAction getAction(final Matcher matcher) { + final MagicAmount count = ARG.amountObj(matcher); + final MagicTargetFilter filter = ARG.playersParse(matcher); + final MagicType cardType = ARG.cardType(matcher); + return (game, event) -> { + final int amount = count.getAmount(event); + for (final MagicPlayer it : ARG.players(event, matcher, filter)) { + game.doAction(new MillLibraryUntilAction(it, cardType, amount)); + } + }; + } + }, CantCastSpells( ARG.PLAYERS + " can't cast spells this turn", MagicTargetHint.Negative, diff --git a/src/magic/model/target/MagicTargetFilterFactory.java b/src/magic/model/target/MagicTargetFilterFactory.java index 471fa129d3..99e3f9ebb1 100644 --- a/src/magic/model/target/MagicTargetFilterFactory.java +++ b/src/magic/model/target/MagicTargetFilterFactory.java @@ -2384,6 +2384,7 @@ public class MagicTargetFilterFactory { // card from an opponent's graveyard add("card from an opponent's graveyard", CARD_FROM_OPPONENTS_GRAVEYARD); + add("card in an opponent's graveyard", CARD_FROM_OPPONENTS_GRAVEYARD); add("creature card in an opponent's graveyard", CREATURE_CARD_FROM_OPPONENTS_GRAVEYARD); // card from your hand