package magic.model.event; import java.util.Collection; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import magic.data.CardDefinitions; import magic.model.*; import magic.model.action.*; import magic.model.choice.MagicChoice; import magic.model.choice.MagicChoiceFactory; import magic.model.choice.MagicFromCardFilterChoice; import magic.model.choice.MagicMayChoice; import magic.model.choice.MagicOrChoice; import magic.model.choice.MagicTargetChoice; import magic.model.condition.MagicArtificialCondition; import magic.model.condition.MagicCondition; import magic.model.condition.MagicConditionFactory; import magic.model.condition.MagicConditionParser; import magic.model.mstatic.MagicLayer; import magic.model.mstatic.MagicStatic; import magic.model.stack.MagicCardOnStack; import magic.model.stack.MagicItemOnStack; import magic.model.target.*; import magic.model.trigger.*; public enum MagicRuleEventAction { ChooseOneOfFour( "choose one — \\(1\\) (?<effect1>.*) \\(2\\) (?<effect2>.*) \\(3\\) (?<effect3>.*) \\(4\\) (?<effect4>.*)", MagicTiming.Pump, "Modal" ) { @Override public MagicChoice getChoice(final Matcher matcher) { final MagicSourceEvent e1 = MagicRuleEventAction.create(matcher.group("effect1")); final MagicSourceEvent e2 = MagicRuleEventAction.create(matcher.group("effect2")); final MagicSourceEvent e3 = MagicRuleEventAction.create(matcher.group("effect3")); final MagicSourceEvent e4 = MagicRuleEventAction.create(matcher.group("effect4")); return new MagicOrChoice(e1.getChoice(), e2.getChoice(), e3.getChoice(), e4.getChoice()); } @Override public MagicEventAction getAction(final Matcher matcher) { final MagicSourceEvent e1 = MagicRuleEventAction.create(matcher.group("effect1")); final MagicSourceEvent e2 = MagicRuleEventAction.create(matcher.group("effect2")); final MagicSourceEvent e3 = MagicRuleEventAction.create(matcher.group("effect3")); final MagicSourceEvent e4 = MagicRuleEventAction.create(matcher.group("effect4")); return (game, event) -> event.executeModalEvent(game, e1, e2, e3, e4); } }, ChooseOneOfThree( "choose one — \\(1\\) (?<effect1>.*) \\(2\\) (?<effect2>.*) \\(3\\) (?<effect3>.*)", MagicTiming.Pump, "Modal" ) { @Override public MagicChoice getChoice(final Matcher matcher) { final MagicSourceEvent e1 = MagicRuleEventAction.create(matcher.group("effect1")); final MagicSourceEvent e2 = MagicRuleEventAction.create(matcher.group("effect2")); final MagicSourceEvent e3 = MagicRuleEventAction.create(matcher.group("effect3")); return new MagicOrChoice(e1.getChoice(), e2.getChoice(), e3.getChoice()); } @Override public MagicEventAction getAction(final Matcher matcher) { final MagicSourceEvent e1 = MagicRuleEventAction.create(matcher.group("effect1")); final MagicSourceEvent e2 = MagicRuleEventAction.create(matcher.group("effect2")); final MagicSourceEvent e3 = MagicRuleEventAction.create(matcher.group("effect3")); return (game, event) -> event.executeModalEvent(game, e1, e2, e3); } }, ChooseOneOfTwo( "choose one — \\(1\\) (?<effect1>.*) \\(2\\) (?<effect2>.*)", MagicTiming.Pump, "Modal" ) { @Override public MagicChoice getChoice(final Matcher matcher) { final MagicSourceEvent e1 = MagicRuleEventAction.create(matcher.group("effect1")); final MagicSourceEvent e2 = MagicRuleEventAction.create(matcher.group("effect2")); return new MagicOrChoice(e1.getChoice(), e2.getChoice()); } @Override public MagicEventAction getAction(final Matcher matcher) { final MagicSourceEvent e1 = MagicRuleEventAction.create(matcher.group("effect1")); final MagicSourceEvent e2 = MagicRuleEventAction.create(matcher.group("effect2")); return (game, event) -> event.executeModalEvent(game, e1, e2); } }, DestroyAtEndOfCombat( "destroy " + ARG.PERMANENTS + " at end of combat", MagicTargetHint.Negative, MagicTiming.Removal, "Destroy" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new AddTurnTriggerAction( it, AtEndOfCombatTrigger.Destroy )); } }; } }, DestroyAtEndOfTurn( "destroy " + ARG.PERMANENTS + " at the beginning of the next end step", MagicTargetHint.Negative, MagicTiming.Removal, "Destroy" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new AddTurnTriggerAction( it, AtEndOfTurnTrigger.Destroy )); } }; } }, Destroy( "destroy " + ARG.PERMANENTS + "(|\\.| and)(?<noregen> (They|It|That creature) can't be regenerated\\.)?", MagicTargetHint.Negative, MagicTiming.Removal, "Destroy" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { final Collection<MagicPermanent> targets = ARG.permanents(event, matcher, filter); if (matcher.group("noregen") != null) { for (final MagicPermanent it : targets) { game.doAction(ChangeStateAction.Set(it, MagicPermanentState.CannotBeRegenerated)); } } game.doAction(new DestroyAction(targets)); }; } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { return matcher.group("noregen") == null ? MagicDestroyTargetPicker.Destroy : MagicDestroyTargetPicker.DestroyNoRegen; } }, CounterUnless( "counter " + ARG.ITEMS + " unless its controller pays (?<cost>[^\\.]*)", MagicTargetHint.Negative, MagicTiming.Counter, "Counter" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicManaCost cost = MagicManaCost.create(matcher.group("cost")); final MagicTargetFilter<MagicItemOnStack> filter = ARG.itemsParse(matcher); return (game, event) -> { for (final MagicItemOnStack item : ARG.items(event, matcher, filter)) { game.addEvent(new MagicCounterUnlessEvent( event.getSource(), item, cost )); } }; } }, CounterSpellToExile( "counter " + ARG.ITEMS + "\\. if that spell is countered this way, exile it instead of putting it into its owner's graveyard", MagicTargetHint.Negative, MagicTiming.Counter, "Counter" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicItemOnStack> filter = ARG.itemsParse(matcher); return (game, event) -> { for (final MagicItemOnStack item : ARG.items(event, matcher, filter)) { game.doAction(new CounterItemOnStackAction(item, MagicLocationType.Exile)); } }; } }, CounterSpell( "counter " + ARG.ITEMS, MagicTargetHint.Negative, MagicTiming.Counter, "Counter" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicItemOnStack> filter = ARG.itemsParse(matcher); return (game, event) -> { for (final MagicItemOnStack item : ARG.items(event, matcher, filter)) { game.doAction(new CounterItemOnStackAction(item)); } }; } }, FlickerYour( "exile " + ARG.PERMANENTS + ", then return (it|that card) to the battlefield under your control", MagicTargetHint.Positive, MagicBounceTargetPicker.create(), MagicTiming.Removal, "Flicker" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new RemoveFromPlayAction( it, MagicLocationType.Exile )); game.doAction(new ReturnCardAction( MagicLocationType.Exile, it.getCard(), event.getPlayer() )); } }; } }, FlickerOwner( "exile " + ARG.PERMANENTS + ", then return (it|that card) to the battlefield" + ARG.MODS + " under its owner's control", MagicTargetHint.Positive, MagicBounceTargetPicker.create(), MagicTiming.Removal, "Flicker" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final List<MagicPlayMod> mods = ARG.mods(matcher); final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new RemoveFromPlayAction( it, MagicLocationType.Exile )); game.doAction(new ReturnCardAction( MagicLocationType.Exile, it.getCard(), it.getOwner(), mods )); } }; } }, FlickerEndStep( "exile " + ARG.PERMANENTS + "\\. (if you do, )?return (those cards|the exiled card|that card|it|sn) to the battlefield under (their|its) owner('s|s') control at the beginning of the next end step", MagicTargetHint.None, MagicBounceTargetPicker.create(), MagicTiming.Removal, "Flicker" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new ExileUntilEndOfTurnAction(it)); } }; } }, ExilePermOrSpell( "exile sn", MagicTiming.Removal, "Exile", (game, event) -> { if (event.getSource().isPermanent()) { game.doAction(new RemoveFromPlayAction(event.getPermanent(), MagicLocationType.Exile)); } else { game.doAction(new ChangeCardDestinationAction(event.getCardOnStack(), MagicLocationType.Exile)); } } ), ExileCards( "exile " + ARG.CARDS, MagicTargetHint.Negative, MagicTiming.Removal, "Exile" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicCard> filter = ARG.cardsParse(matcher); return (game, event) -> { for (final MagicCard it : ARG.cards(event, matcher, filter)) { game.doAction(new ExileLinkAction( event.getSource().isPermanent() ? event.getPermanent() : MagicPermanent.NONE, it, it.getLocation() )); } }; } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { if (matcher.group("choice") != null) { if (matcher.group("choice").contains("your")) { return MagicGraveyardTargetPicker.ExileOwn; } else { return MagicGraveyardTargetPicker.ExileOpp; } } else { return MagicDefaultTargetPicker.create(); } } }, ExilePermanentsUntilLeaves( "exile " + ARG.PERMANENTS + " until SN leaves the battlefield", MagicTargetHint.Negative, MagicExileTargetPicker.create(), MagicTiming.Removal, "Exile" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent perm : ARG.permanents(event, matcher, filter)) { if (event.getPermanent().isValid()) { game.doAction(new ExileLinkAction(event.getPermanent(), perm)); game.doAction(new AddTriggerAction(event.getPermanent(), ThisLeavesBattlefieldTrigger.ExileUntilLeaves)); } } }; } }, ExilePermanents( "exile " + ARG.PERMANENTS, MagicTargetHint.Negative, MagicExileTargetPicker.create(), MagicTiming.Removal, "Exile" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent perm : ARG.permanents(event, matcher, filter)) { game.doAction(new ExileLinkAction( event.getSource().isPermanent() ? event.getPermanent() : MagicPermanent.NONE, perm )); } }; } }, RemoveFromCombat( "remove " + ARG.PERMANENTS + " from combat", MagicTargetHint.None, MagicBounceTargetPicker.create(), MagicTiming.Removal, "Remove" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent perm : ARG.permanents(event, matcher, filter)) { game.doAction(new RemoveFromCombatAction(perm)); } }; } }, DamageEqual( ARG.IT + " deal(s)? damage equal to " + ARG.WORDRUN + " to " + ARG.TARGETS, MagicTargetHint.Negative, MagicTiming.Removal, "Damage" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAmount count = MagicAmountParser.build(ARG.wordrun(matcher)); final MagicTargetFilter<MagicTarget> filter = ARG.targetsParse(matcher); return (game, event) -> { final int amount = count.getAmount(event); game.logAppendValue(event.getPlayer(), amount); for (final MagicTarget target : ARG.targets(event, matcher, filter)) { game.doAction(new DealDamageAction( ARG.itSource(event, matcher), target, amount )); } }; } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { final MagicAmount count = MagicAmountParser.build(ARG.wordrun(matcher)); return new MagicDamageTargetPicker(count); } }, DamageEqualAlt( ARG.IT + " deal(s)? damage to " + ARG.TARGETS + " equal to " + ARG.WORDRUN, MagicTargetHint.Negative, MagicTiming.Removal, "Damage" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return DamageEqual.getAction(matcher); } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { return DamageEqual.getPicker(matcher); } }, DamageEqualX( ARG.IT + " deal(s)? X damage to " + ARG.TARGETS + ", where X is " + ARG.WORDRUN, MagicTargetHint.Negative, MagicTiming.Removal, "Damage" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return DamageEqual.getAction(matcher); } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { return DamageEqual.getPicker(matcher); } }, DamageTwoGroupAlt( ARG.IT + " deal(s)? " + ARG.AMOUNT + " damage to " + ARG.TARGETS + " and " + ARG.AMOUNT2 + " (additional )?damage to " + ARG.TARGETS2, MagicTargetHint.Negative, MagicTiming.Removal, "Damage" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAmount count = ARG.amountObj(matcher); final int amount2 = ARG.amount2(matcher); final MagicTargetFilter<MagicTarget> filter1 = ARG.targetsParse(matcher); final MagicTargetFilter<MagicTarget> filter2 = ARG.targets2Parse(matcher); return (game, event) -> { final MagicSource source = ARG.itSource(event, matcher); final int amount1 = count.getAmount(event); for (final MagicTarget target : ARG.targets(event, matcher, filter1)) { game.doAction(new DealDamageAction(source, target, amount1)); } for (final MagicTarget target : ARG.targets2(event, matcher, filter2)) { game.doAction(new DealDamageAction(source, target, amount2)); } }; } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { return DamageGroup.getPicker(matcher); } }, DamageTwoGroup( ARG.IT + " deal(s)? " + ARG.AMOUNT + " damage to " + ARG.TARGETS + " and " + ARG.TARGETS2, MagicTargetHint.Negative, MagicTiming.Removal, "Damage" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAmount count = ARG.amountObj(matcher); final MagicTargetFilter<MagicTarget> filter1 = ARG.targetsParse(matcher); final MagicTargetFilter<MagicTarget> filter2 = ARG.targets2Parse(matcher); return (game, event) -> { final int amount = count.getAmount(event); final MagicSource source = ARG.itSource(event, matcher); for (final MagicTarget target : ARG.targets(event, matcher, filter1)) { game.doAction(new DealDamageAction(source, target, amount)); } for (final MagicTarget target : ARG.targets2(event, matcher, filter2)) { game.doAction(new DealDamageAction(source, target, amount)); } }; } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { return DamageGroup.getPicker(matcher); } }, DamageGroupExile( ARG.IT + " deal(s)? " + ARG.AMOUNT + " damage to " + ARG.TARGETS + "\\. " + "(?<noregen>That creature can't be regenerated this turn. )?" + "If (a|that|the) creature (?<dealt>dealt damage this way )?would die this turn, exile it instead.", MagicTiming.Removal, "Damage" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAmount count = ARG.amountObj(matcher); final MagicTargetFilter<MagicTarget> filter = ARG.targetsParse(matcher); return (game, event) -> { final int amount = count.getAmount(event); for (final MagicTarget it : ARG.targets(event, matcher, filter)) { final MagicDamage damage = new MagicDamage(ARG.itSource(event, matcher), it, amount); game.doAction(new DealDamageAction(damage)); if (it.isPermanent()) { final MagicPermanent perm = (MagicPermanent)it; if (matcher.group("noregen") != null) { game.doAction(ChangeStateAction.Set( perm, MagicPermanentState.CannotBeRegenerated )); } if (matcher.group("dealt") == null || damage.getDealtAmount() > 0) { game.doAction(new AddTurnTriggerAction( perm, ThisLeavesBattlefieldTrigger.IfDieExileInstead )); } } } }; } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { return DamageGroup.getPicker(matcher); } }, DamageGroup( ARG.IT + " deal(s)? " + ARG.AMOUNT + " damage to " + ARG.TARGETS, MagicTiming.Removal, "Damage" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAmount count = ARG.amountObj(matcher); final MagicTargetFilter<MagicTarget> filter = ARG.targetsParse(matcher); return (game, event) -> { final int amount = count.getAmount(event); for (final MagicTarget target : ARG.targets(event, matcher, filter)) { game.doAction(new DealDamageAction(ARG.itSource(event, matcher), target, amount)); } }; } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { final MagicAmount count = ARG.amountObj(matcher); return new MagicDamageTargetPicker(count); } }, Fight( ARG.IT + " fight(s)? " + ARG.TARGETS, MagicTiming.Removal, "Fight" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return (game, event) -> event.processTargetPermanent(game, (final MagicPermanent other) -> { final MagicPermanent it = ARG.itPermanent(event, matcher); game.doAction(new DealDamageAction(it, other, it.getPower())); game.doAction(new DealDamageAction(other, it, other.getPower())); }); } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { if (matcher.group("sn") != null) { return new MagicDamageTargetPicker(MagicAmountFactory.SN_Power); } else { return MagicDefaultTargetPicker.create(); } } }, PreventNextDamage( "prevent the next " + ARG.AMOUNT + " damage that would be dealt to " + ARG.TARGETS + " this turn", MagicTargetHint.Positive, MagicPreventTargetPicker.create(), MagicTiming.Pump, "Prevent" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAmount count = ARG.amountObj(matcher); final MagicTargetFilter<MagicTarget> filter = ARG.targetsParse(matcher); return (game, event) -> { final int amount = count.getAmount(event); for (final MagicTarget it : ARG.targets(event, matcher, filter)) { game.doAction(new PreventDamageAction(it, amount)); } }; } }, PreventAllCombat( "prevent all combat damage that would be dealt this turn", MagicTiming.Block, "Prevent", (game, event) -> game.doAction(new AddTurnTriggerAction( PreventDamageTrigger.PreventCombatDamage )) ), PreventAllCombatToBy( "prevent all combat damage that would be dealt to and dealt by " + ARG.PERMANENTS + " this turn", MagicTiming.Block, "Prevent" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new AddTurnTriggerAction( it, PreventDamageTrigger.PreventCombatDamageDealtToDealtBy )); } }; } }, PreventAllCombatTo( "prevent all combat damage that would be dealt to " + ARG.TARGETS + " this turn", MagicTargetHint.Positive, MagicPreventTargetPicker.create(), MagicTiming.Block, "Prevent" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicTarget> filter = ARG.targetsParse(matcher); return (game, event) -> { if (matcher.group("group1") != null) { game.doAction(new AddTurnTriggerAction( PreventDamageTrigger.PreventCombatDamageDealtTo(filter) )); } else { for (final MagicTarget it : ARG.targets(event, matcher, filter)) { if (it.isPermanent()) { game.doAction(new AddTurnTriggerAction( (MagicPermanent)it, PreventDamageTrigger.PreventCombatDamageDealtTo )); } else { game.doAction(new AddTurnTriggerAction( PreventDamageTrigger.PreventCombatDamageDealtToYou((MagicPlayer)it) )); } } } }; } }, PreventAllCombatBy( "prevent all combat damage (that would be dealt (this turn )?by )?" + ARG.PERMANENTS + "( would deal)?( this turn)?", MagicTargetHint.Negative, new MagicNoCombatTargetPicker(true, true, false), MagicTiming.Block, "Prevent" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { if (matcher.group("group") != null) { game.doAction(new AddTurnTriggerAction( PreventDamageTrigger.PreventCombatDamageDealtBy(filter) )); } else { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new AddTurnTriggerAction(it, PreventDamageTrigger.PreventCombatDamageDealtBy)); } } }; } }, PreventAllDamageTo( "prevent all damage that would be dealt to " + ARG.TARGETS + " this turn", MagicTargetHint.Positive, MagicPreventTargetPicker.create(), MagicTiming.Pump, "Prevent" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicTarget> filter = ARG.targetsParse(matcher); return (game, event) -> { if (matcher.group("group1") != null) { game.doAction(new AddTurnTriggerAction( PreventDamageTrigger.PreventDamageDealtTo(filter) )); } else { for (final MagicTarget it : ARG.targets(event, matcher, filter)) { if (it.isPermanent()) { game.doAction(new AddTurnTriggerAction( (MagicPermanent)it, PreventDamageTrigger.PreventDamageDealtTo )); } else { game.doAction(new AddTurnTriggerAction( PreventDamageTrigger.PreventDamageDealtToYou((MagicPlayer)it) )); } } } }; } }, PreventAllDamageBy( "prevent all damage (that would be dealt (this turn )?by )?" + ARG.PERMANENTS + "( would deal)?( this turn)?", MagicTargetHint.Negative, new MagicNoCombatTargetPicker(true, true, false), MagicTiming.Pump, "Prevent" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { if (matcher.group("group") != null) { game.doAction(new AddTurnTriggerAction( PreventDamageTrigger.PreventDamageDealtBy(filter) )); } else { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new AddTurnTriggerAction(it, PreventDamageTrigger.PreventDamageDealtBy)); } } }; } }, DrawLosePlayers( ARG.PLAYERS + " draw(s)? " + ARG.AMOUNT + " card(s)? and (you )?lose(s)? " + ARG.AMOUNT2 + " life", MagicTiming.Draw, "Draw" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount1 = ARG.amount(matcher); final int amount2 = ARG.amount2(matcher); final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.doAction(new DrawAction(it, amount1)); game.doAction(new ChangeLifeAction(it, -amount2)); } }; } }, DrawSelfNextUpkeep( "(pn |you )?draw(s)? a card at the beginning of the next turn's upkeep", MagicTiming.Draw, "Draw" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return (game, event) -> game.doAction(new AddTriggerAction( AtUpkeepTrigger.YouDraw( event.getSource(), event.getPlayer() ) )); } }, Draw( ARG.PLAYERS + "( )?draw(s)?( " + ARG.AMOUNT + ")? (additional )?card(s)?( (for each|equal to) " + ARG.WORDRUN + ")?", MagicTargetHint.Positive, MagicTiming.Draw, "Draw" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAmount eachCount = MagicAmountParser.build(ARG.wordrun(matcher)); final MagicAmount cardCount = ARG.amountObj(matcher); final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { final int multiplier = eachCount.getAmount(event); final int total = cardCount.getAmount(event) * multiplier; if (eachCount != MagicAmountFactory.One) { game.logAppendValue(event.getPlayer(), total); } for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.addEvent(new MagicDrawEvent(event.getSource(), it, total, "")); } }; } }, DrawDiscard( ARG.PLAYERS + "( )?draw(s)? " + ARG.AMOUNT + " card(s)?(, then|\\. if you do,) discard(s)? " + ARG.AMOUNT2 + " card(s)?(?<random> at random)?", MagicTiming.Draw, "Draw" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount1 = ARG.amount(matcher); final int amount2 = ARG.amount2(matcher); final boolean isRandom = matcher.group("random") != null; final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.doAction(new DrawAction(it, amount1)); if (isRandom) { game.addEvent(MagicDiscardEvent.Random(event.getSource(), it, amount2)); } else { game.addEvent(new MagicDiscardEvent(event.getSource(), it, amount2)); } } }; } }, Discard( ARG.PLAYERS + "( )?discard(s)? " + ARG.AMOUNT + " card(s)?(?<random> at random)?( for each " + ARG.WORDRUN + ")?", MagicTargetHint.Negative, MagicTiming.Draw, "Discard" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAmount eachCount = MagicAmountParser.build(ARG.wordrun(matcher)); final MagicAmount cardCount = ARG.amountObj(matcher); final boolean isRandom = matcher.group("random") != null; final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { final int multiplier = eachCount.getAmount(event); final int total = cardCount.getAmount(event) * multiplier; if (eachCount != MagicAmountFactory.One) { game.logAppendValue(event.getPlayer(), total); } for (final MagicPlayer it : ARG.players(event, matcher, filter)) { if (isRandom) { game.addEvent(MagicDiscardEvent.Random(event.getSource(), it, total)); } else { game.addEvent(new MagicDiscardEvent(event.getSource(), it, total)); } } }; } }, DiscardHand( ARG.PLAYERS + "( )?discard(s)? (your|his or her) hand", MagicTargetHint.Negative, MagicTiming.Draw, "Discard" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.addEvent(new MagicDiscardHandEvent(event.getSource(), it)); } }; } }, DrainLife( ARG.PLAYERS + " lose(s)? " + ARG.AMOUNT + " life and you gain " + ARG.AMOUNT2 + " life", MagicTargetHint.Negative, MagicTiming.Removal, "-Life" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount1 = ARG.amount(matcher); final int amount2 = ARG.amount2(matcher); final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { final List<MagicPlayer> players = ARG.players(event, matcher, filter); for (final MagicPlayer it : players) { game.doAction(new ChangeLifeAction(it, -amount1)); } //continue to the second part if // there is no target OR // there is a target and it is legal if (matcher.group("choice") == null || players.isEmpty() == false) { game.doAction(new ChangeLifeAction(event.getPlayer(), amount2)); } }; } }, DrainLifeAlt( ARG.PLAYERS + "( )?lose(s)?( " + ARG.AMOUNT + ")? life( (for each|equal to) " + ARG.WORDRUN + ")?\\. You gain life equal to the life lost this way", MagicTargetHint.Negative, MagicTiming.Removal, "-Life" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.amount(matcher); final MagicAmount count = MagicAmountParser.build(ARG.wordrun(matcher)); final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { final int multiplier = count.getAmount(event); final int total = amount * multiplier; if (count != MagicAmountFactory.One) { game.logAppendValue(event.getPlayer(), total); } int totalLost = 0; for (final MagicPlayer it : ARG.players(event, matcher, filter)) { final ChangeLifeAction act = new ChangeLifeAction(it, -total); game.doAction(act); if (act.getLifeChange() < 0) { totalLost += -act.getLifeChange(); } } game.doAction(new ChangeLifeAction(event.getPlayer(), totalLost)); }; } }, GainLife( ARG.PLAYERS + "( )?gain(s)?( " + ARG.AMOUNT + ")? life( (for each|equal to) " + ARG.WORDRUN + ")?", MagicTargetHint.Positive, MagicTiming.Removal, "+Life" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAmount lifeCount = ARG.amountObj(matcher); final MagicAmount eachCount = MagicAmountParser.build(ARG.wordrun(matcher)); final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { final int multiplier = eachCount.getAmount(event); final int total = lifeCount.getAmount(event) * multiplier; if (eachCount != MagicAmountFactory.One) { game.logAppendValue(event.getPlayer(), total); } for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.doAction(new ChangeLifeAction(it, total)); } }; } }, LoseLife( ARG.PLAYERS + "( )?lose(s)?( " + ARG.AMOUNT + ")? life( (for each|equal to) " + ARG.WORDRUN + ")?", MagicTargetHint.Negative, MagicTiming.Removal, "-Life" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.amount(matcher); final MagicAmount count = MagicAmountParser.build(ARG.wordrun(matcher)); final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { final int multiplier = count.getAmount(event); final int total = amount * multiplier; if (count != MagicAmountFactory.One) { game.logAppendValue(event.getPlayer(), total); } for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.doAction(new ChangeLifeAction(it, -total)); } }; } }, SetLife( ARG.PLAYERS + "('s|r) life total becomes " + ARG.AMOUNT, MagicTiming.Removal, "=Life" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.amount(matcher); final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.doAction(new ChangeLifeAction(it, amount - it.getLife())); } }; } }, Pump( ARG.PERMANENTS + " get(s)? (an additional )?(?<pt>[X0-9+]+/[X0-9+]+) until end of turn( for each " + ARG.WORDRUN + ")?", MagicTargetHint.Positive, MagicPumpTargetPicker.create(), MagicTiming.Pump, "Pump" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final String[] pt = ARG.ptStr(matcher); final MagicAmount powerCounter = MagicAmountParser.build(pt[0]); final MagicAmount toughnessCounter = MagicAmountParser.build(pt[1]); final MagicAmount count = MagicAmountParser.build(ARG.wordrun(matcher)); final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { final int amount = count.getAmount(event); final int power = powerCounter.getAmount(event); final int toughness = toughnessCounter.getAmount(event); if (count != MagicAmountFactory.One) { game.logAppendValue(event.getPlayer(), amount); } for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new ChangeTurnPTAction(it, power * amount, toughness * amount)); } }; } }, PumpX( ARG.PERMANENTS + "( gain(s)? (?<ability>.+?) and)? get(s)? (an additional )?(?<pt>[X0-9+]+/[X0-9+]+) until end of turn, where X is " + ARG.WORDRUN, MagicTargetHint.Positive, MagicPumpTargetPicker.create(), MagicTiming.Pump, "Pump" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final String[] pt = ARG.ptStr(matcher); final MagicAmount countX = MagicAmountParser.build(ARG.wordrun(matcher)); final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); final MagicAbilityList abilityList = matcher.group("ability") != null ? MagicAbility.getAbilityList(matcher.group("ability")) : null; return (game, event) -> { final int X = countX.getAmount(event); final int power = MagicAmountParser.getX(pt[0], X); final int toughness = MagicAmountParser.getX(pt[1], X); game.logAppendX(event.getPlayer(), X); for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new ChangeTurnPTAction(it, power, toughness)); if (abilityList != null) { game.doAction(new GainAbilityAction(it, abilityList)); } } }; } }, Weaken( ARG.PERMANENTS + " get(s)? (?<pt>[X0-9-]+/[X0-9-]+) until end of turn( for each " + ARG.WORDRUN + ")?", MagicTargetHint.Negative, MagicTiming.Removal, "Weaken" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return Pump.getAction(matcher); } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { final String[] pt = ARG.ptStr(matcher); final MagicAmount toughnessCounter = MagicAmountParser.build(pt[1]); return new MagicWeakenTargetPicker(toughnessCounter); } }, WeakenX( ARG.PERMANENTS + "( gain(s)? (?<ability>.+?) and)? get(s)? (an additional )?(?<pt>[X0-9-]+/[X0-9-]+) until end of turn, where X is " + ARG.WORDRUN, MagicTargetHint.Negative, MagicTiming.Removal, "Weaken" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return PumpX.getAction(matcher); } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { final String[] pt = ARG.ptStr(matcher); final MagicAmount countX = MagicAmountParser.build(ARG.wordrun(matcher)); final MagicAmount toughnessCounter = pt[1].equalsIgnoreCase("-x") ? countX : MagicAmountParser.build(pt[1]); return new MagicWeakenTargetPicker(toughnessCounter); } }, ModPT( ARG.PERMANENTS + " get(s)? (?<pt>[X0-9+-]+/[X0-9+-]+) until end of turn( for each " + ARG.WORDRUN + ")?", MagicTiming.Removal, "Pump" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return Pump.getAction(matcher); } }, PumpGain( ARG.PERMANENTS + " get(s)? (?<pt>[X0-9+]+/[X0-9+]+) and (gain(s)?|is) (?<ability>.+) until end of turn", MagicTargetHint.Positive ) { @Override public MagicEventAction getAction(final Matcher matcher) { final String[] pt = ARG.ptStr(matcher); final MagicAmount powerCounter = MagicAmountParser.build(pt[0]); final MagicAmount toughnessCounter = MagicAmountParser.build(pt[1]); final MagicAbilityList abilityList = MagicAbility.getAbilityList(matcher.group("ability")); final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { final int power = powerCounter.getAmount(event); final int toughness = toughnessCounter.getAmount(event); for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new ChangeTurnPTAction(it, power, toughness)); game.doAction(new GainAbilityAction(it, abilityList)); } }; } @Override public MagicTiming getTiming(final Matcher matcher) { return GainAbility.getTiming(matcher); } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { return GainAbility.getPicker(matcher); } @Override public String getName(final Matcher matcher) { return GainAbility.getName(matcher); } }, PumpGainAlt( "until end of turn, " + ARG.PERMANENTS + " get(s)? (?<pt>[X0-9+]+/[X0-9+]+) and (gain(s)?|is) (?<ability>.+)", MagicTargetHint.Positive ) { @Override public MagicEventAction getAction(final Matcher matcher) { return PumpGain.getAction(matcher); } @Override public MagicTiming getTiming(final Matcher matcher) { return GainAbility.getTiming(matcher); } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { return GainAbility.getPicker(matcher); } @Override public String getName(final Matcher matcher) { return GainAbility.getName(matcher); } }, PumpGainCan( ARG.PERMANENTS + " get(s)? (?<pt>[X0-9+]+/[X0-9+]+) (until end of turn and|and) (?<ability>can('t)? .+) this turn", MagicTargetHint.Positive ) { @Override public MagicEventAction getAction(final Matcher matcher) { return PumpGain.getAction(matcher); } @Override public MagicTiming getTiming(final Matcher matcher) { return GainAbility.getTiming(matcher); } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { return GainAbility.getPicker(matcher); } @Override public String getName(final Matcher matcher) { return GainAbility.getName(matcher); } }, ModPTGain( ARG.PERMANENTS + " get(s)? (?<pt>[X0-9+-]+/[X0-9+-]+) and gains (?<ability>.+) until end of turn", MagicTiming.Removal ) { @Override public MagicEventAction getAction(final Matcher matcher) { return PumpGain.getAction(matcher); } @Override public String getName(final Matcher matcher) { return GainAbility.getName(matcher); } }, ModPTGainCan( ARG.PERMANENTS + " get(s)? (?<pt>[X0-9+-]+/[X0-9+-]+) and (?<ability>can('t)? .+) this turn", MagicTiming.Removal ) { @Override public MagicEventAction getAction(final Matcher matcher) { return PumpGain.getAction(matcher); } @Override public String getName(final Matcher matcher) { return GainAbility.getName(matcher); } }, PutCounter( "put " + ARG.AMOUNT + " (?<type>[^ ]+) counter(s)? on " + ARG.PERMANENTS ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAmount count = ARG.amountObj(matcher); final MagicCounterType counterType = MagicCounterType.getCounterRaw(matcher.group("type")); final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { final int amount = count.getAmount(event); for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new ChangeCountersAction( it, counterType, amount )); } }; } @Override public MagicTargetHint getHint(final Matcher matcher) { final MagicCounterType counterType = MagicCounterType.getCounterRaw(matcher.group("type")); if (counterType.getName().contains("-") || counterType.getScore() < 0) { return MagicTargetHint.Negative; } else { return MagicTargetHint.Positive; } } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { final MagicCounterType counterType = MagicCounterType.getCounterRaw(matcher.group("type")); if (counterType.getName().contains("-")) { final String[] pt = counterType.getName().split("/"); return new MagicWeakenTargetPicker(-Integer.parseInt(pt[0]), -Integer.parseInt(pt[1])); } else if (counterType.getName().contains("+")) { return MagicPumpTargetPicker.create(); } else { return MagicDefaultTargetPicker.create(); } } @Override public MagicTiming getTiming(final Matcher matcher) { final MagicCounterType counterType = MagicCounterType.getCounterRaw(matcher.group("type")); if (counterType.getName().contains("-")) { return MagicTiming.Removal; } else { return MagicTiming.Pump; } } @Override public String getName(final Matcher matcher) { return "+Counters"; } }, CounterFromSelfClockwork( "remove a \\+1\\/\\+1 counter from (sn|it) at end of combat", MagicTiming.Pump, "Remove", (game, event) -> game.doAction(new AddTurnTriggerAction(event.getPermanent(), AtEndOfCombatTrigger.Clockwork)) ), RemoveCounter( "remove " + ARG.AMOUNT + " (?<type>[^ ]+) counter(s)? from " + ARG.PERMANENTS, MagicTiming.Pump ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAmount count = ARG.amountObj(matcher); final MagicCounterType counterType = MagicCounterType.getCounterRaw(matcher.group("type")); final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { final int amount = count.getAmount(event); for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new ChangeCountersAction( it, counterType, -amount )); } }; } @Override public String getName(final Matcher matcher) { return "-Counters"; } }, Bolster( "bolster " + ARG.AMOUNT, MagicTiming.Pump, "Bolster" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.amount(matcher); return (game, event) -> { game.addEvent(new MagicBolsterEvent(event, amount)); }; } }, RecoverCardSelf( "return sn from your graveyard to your hand", MagicTiming.Draw, "Return", (game, event) -> game.doAction(new ShiftCardAction( event.getSourceCard(), MagicLocationType.Graveyard, MagicLocationType.OwnersHand )) ), ReanimateCardSelf( "return sn from your graveyard to the battlefield" + ARG.MODS, MagicTiming.Token, "Reanimate" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final List<MagicPlayMod> mods = ARG.mods(matcher); return (game, event) -> game.doAction(new ReanimateAction( event.getSourceCard(), event.getPlayer(), mods )); } }, RecoverSelf( "return sn from the graveyard to its owner's hand", MagicTiming.Draw, "Return", (game, event) -> game.doAction(new ShiftCardAction( event.getSourceCard(), MagicLocationType.Graveyard, MagicLocationType.OwnersHand )) ), ReturnPermOrSpell( "return sn to its owner's hand", MagicTiming.Draw, "Return", (game, event) -> { if (event.getSource().isPermanent()) { game.doAction(new RemoveFromPlayAction(event.getPermanent(), MagicLocationType.OwnersHand)); } else { game.doAction(new ChangeCardDestinationAction(event.getCardOnStack(), MagicLocationType.OwnersHand)); } } ), RecoverRandomCards( "return " + ARG.AMOUNT + " " + ARG.WORDRUN + " at random from your graveyard to your hand", MagicTargetHint.Positive, MagicGraveyardTargetPicker.ReturnToHand, MagicTiming.Draw, "Return" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.amount(matcher); final MagicTargetFilter<MagicCard> filter = MagicTargetFilterFactory.Card(ARG.wordrun(matcher) + " from your graveyard"); return (game, event) -> { final MagicCardList cards = new MagicCardList(filter.filter(event)); for (final MagicCard card : cards.getRandomCards(amount)) { game.doAction(new ShiftCardAction( card, MagicLocationType.Graveyard, MagicLocationType.OwnersHand )); } }; } }, RecoverCards( "return " + ARG.CARDS + " to (your hand|its owner's hand|their owner's hand|their owners' hands)", MagicTargetHint.Positive, MagicGraveyardTargetPicker.ReturnToHand, MagicTiming.Draw, "Return" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicCard> filter = ARG.cardsParse(matcher); return (game, event) -> { for (final MagicCard it : ARG.cards(event, matcher, filter)) { final MagicLocationType from = it.getLocation(); game.doAction(new ShiftCardAction(it, from, MagicLocationType.OwnersHand)); } }; } }, ReclaimCards( "put " + ARG.CARDS + " on top of (your|its owner's) library", MagicTargetHint.Positive, MagicGraveyardTargetPicker.ReturnToHand, MagicTiming.Draw, "Reclaim" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicCard> filter = ARG.cardsParse(matcher); return (game, event) -> { for (final MagicCard it : ARG.cards(event, matcher, filter)) { final MagicLocationType from = it.getLocation(); game.doAction(new ShiftCardAction(it, from, MagicLocationType.TopOfOwnersLibrary)); } }; } }, TuckCards( "put " + ARG.CARDS + " on the bottom of (your|its owner's) library", MagicTargetHint.Negative, MagicGraveyardTargetPicker.ExileOpp, MagicTiming.Draw, "Tuck" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicCard> filter = ARG.cardsParse(matcher); return (game, event) -> { for (final MagicCard it : ARG.cards(event, matcher, filter)) { final MagicLocationType from = it.getLocation(); game.doAction(new ShiftCardAction(it, from, MagicLocationType.BottomOfOwnersLibrary)); } }; } }, Bounce( "return " + ARG.PERMANENTS + " to (your hand|its owner's hand|their owner's hand|their owners' hands)", MagicTargetHint.None, MagicBounceTargetPicker.create(), MagicTiming.Removal, "Bounce" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new RemoveFromPlayAction(it, MagicLocationType.OwnersHand)); } }; } }, BounceEndOfCombat( "return " + ARG.PERMANENTS + " to (your hand|its owner's hand|their owner's hand|their owners' hands) at end of combat", MagicTargetHint.None, MagicBounceTargetPicker.create(), MagicTiming.Removal, "Bounce" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new AddTriggerAction( it, AtEndOfCombatTrigger.Return )); } }; } }, BounceEndOfTurn( "return " + ARG.PERMANENTS + " to (your hand|its owner's hand|their owner's hand|their owners' hands) at the beginning of the next end step", MagicTargetHint.None, MagicBounceTargetPicker.create(), MagicTiming.Removal, "Bounce" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new AddTriggerAction( it, AtEndOfTurnTrigger.Return )); } }; } }, BounceLibTop( "put " + ARG.PERMANENTS + " on top of (its owner's library|his or her library|their owners' libraries)", MagicTargetHint.None, MagicBounceTargetPicker.create(), MagicTiming.Removal, "Bounce" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new RemoveFromPlayAction(it, MagicLocationType.TopOfOwnersLibrary)); } }; } }, BounceLibBottom( "put " + ARG.PERMANENTS + " on the bottom of (your library|its owner's library|his or her library|their owners' libraries)", MagicTargetHint.None, MagicBounceTargetPicker.create(), MagicTiming.Removal, "Bounce" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new RemoveFromPlayAction(it, MagicLocationType.BottomOfOwnersLibrary)); } }; } }, RevealToHand( "reveal the top " + ARG.AMOUNT + " cards of your library\\. Put all " + ARG.WORDRUN + " revealed this way into your hand and the rest ((?<graveyard>into your graveyard)|(?<bottom>on the bottom of your library in any order))", MagicTiming.Draw, "Reveal" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int n = ARG.amount(matcher); final MagicTargetFilter<MagicCard> filter = MagicTargetFilterFactory.Card(ARG.wordrun(matcher) + " from your library"); final MagicLocationType restLocation = matcher.group("graveyard") != null ? MagicLocationType.Graveyard : MagicLocationType.BottomOfOwnersLibrary; return (game, event) -> { final List<MagicCard> topN = event.getPlayer().getLibrary().getCardsFromTop(n); game.doAction(new RevealAction(topN)); for (final MagicCard it : topN) { game.doAction(new ShiftCardAction( it, MagicLocationType.OwnersLibrary, filter.accept(event.getSource(), event.getPlayer(), it) ? MagicLocationType.OwnersHand : restLocation )); } }; } }, RevealToHand2( "reveal the top " + ARG.AMOUNT + " cards of your library\\. You may put a(n)? " + ARG.WORDRUN + " from among them into your hand. Put the rest into your graveyard", MagicTiming.Draw, "Reveal" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.amount(matcher); final MagicTargetFilter<MagicCard> filter = MagicTargetFilterFactory.Card(ARG.wordrun(matcher) + " from your library"); return (game, event) -> game.addEvent(MagicTutorTopEvent.reveal(event, amount, filter)); } }, SearchLibraryToHand( "search your library for (?<card>[^\\.]*?)(?<reveal>, reveal (it|that card))?(, and |, | and )put (it|that card) into your hand(.|,) (If you do, |then )shuffle your library", MagicTiming.Draw, "Search" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetChoice choice = new MagicTargetChoice(getHint(matcher), matcher.group("card") + " from your library"); return (game, event) -> game.addEvent(new MagicSearchToLocationEvent( event, choice, MagicLocationType.OwnersHand, matcher.group("reveal") != null )); } }, SearchMultiLibraryToHand( "search your library for up to " + ARG.AMOUNT + " (?<card>[^\\.]*), reveal (them|those cards), (and )?put them into your hand(.|,) (If you do, |then )shuffle your library", MagicTiming.Draw, "Search" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicCard> filter = MagicTargetFilterFactory.Card(matcher.group("card") + " from your library"); final int amount = ARG.amount(matcher); return (game, event) -> game.addEvent(new MagicSearchToLocationEvent( event, new MagicFromCardFilterChoice( filter, amount, true, "to put into your hand" ), MagicLocationType.OwnersHand )); } }, SearchLibraryToTopLibrary( "search your library for (?<card>[^\\.]*?)(,| and)(?<reveal> reveal (it(,|\\.)|that card\\.))? (If you do, |then )?shuffle your library(, then| and) put (that|the) card on top of it", MagicTiming.Draw, "Search" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetChoice choice = new MagicTargetChoice(getHint(matcher), matcher.group("card") + " from your library"); return (game, event) -> game.addEvent(new MagicSearchToLocationEvent( event, choice, MagicLocationType.TopOfOwnersLibrary, matcher.group("reveal") != null )); } }, SearchLibraryToGraveyard( "search your library for (?<card>[^\\.]*) and put (that card|it) into your graveyard\\. (If you do,|Then) shuffle your library", MagicTiming.Draw, "Search" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetChoice choice = new MagicTargetChoice(getHint(matcher), matcher.group("card") + " from your library"); return (game, event) -> game.addEvent(new MagicSearchToLocationEvent( event, choice, MagicLocationType.Graveyard )); } }, SearchLibraryToBattlefield( "search your library for (?<card>[^\\.]*)(,| and) put (it|that card) onto the battlefield" + ARG.MODS + "(.|,) ((T|t)hen|If you do,) shuffle your library", MagicTiming.Token, "Search" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetChoice choice = new MagicTargetChoice(getHint(matcher), matcher.group("card") + " from your library"); final List<MagicPlayMod> mods = ARG.mods(matcher); return (game, event) -> game.addEvent(new MagicSearchOntoBattlefieldEvent( event, choice, mods )); } }, SearchLibraryToBattlefieldAlt( "search your library for (?<card>[^\\.]*)(,| and) put (it|that card) onto the battlefield\\. Then shuffle your library\\." + ARG.MODS, MagicTiming.Token, "Search" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return SearchLibraryToBattlefield.getAction(matcher); } }, SearchMultiLibraryToBattlefield( "search your library for up to " + ARG.AMOUNT + " (?<card>[^\\.]*)(,| and) put (them|those cards) onto the battlefield" + ARG.MODS + "(.|,) ((T|t)hen|If you do,) shuffle your library", MagicTiming.Token, "Search" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.amount(matcher); final MagicTargetFilter<MagicCard> filter = MagicTargetFilterFactory.Card(matcher.group("card") + " from your library"); final List<MagicPlayMod> mods = ARG.mods(matcher); return (game, event) -> game.addEvent(new MagicSearchOntoBattlefieldEvent( event, new MagicFromCardFilterChoice( filter, amount, true, "to put onto the battlefield" ), mods )); } }, FromHandToBattlefield( "put (?<card>[^\\.]*hand) onto the battlefield" + ARG.MODS, MagicTiming.Token, "Put" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetChoice choice = new MagicTargetChoice(getHint(matcher), matcher.group("card")); final List<MagicPlayMod> mods = ARG.mods(matcher); return (game, event) -> game.addEvent(new MagicPutOntoBattlefieldEvent( event, choice, mods )); } }, Reanimate( "return " + ARG.GRAVEYARD + " to the battlefield" + ARG.MODS, MagicTargetHint.None, MagicGraveyardTargetPicker.PutOntoBattlefield, MagicTiming.Token, "Reanimate" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final List<MagicPlayMod> mods = ARG.mods(matcher); return (game, event) -> event.processTargetCard(game, (final MagicCard card) -> game.doAction(new ReanimateAction( card, event.getPlayer(), mods )) ); } }, Reanimate2( "put " + ARG.GRAVEYARD + " onto the battlefield under your control" + ARG.MODS, MagicTargetHint.None, MagicGraveyardTargetPicker.PutOntoBattlefield, MagicTiming.Token, "Reanimate" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return Reanimate.getAction(matcher); } }, TapOrUntapChosen( "tap or untap " + ARG.CHOICE, MagicTargetHint.None, MagicTapTargetPicker.TapOrUntap, MagicTiming.Tapping, "Tap/Untap", (game, event) -> event.processTargetPermanent(game, perm -> game.addEvent(new MagicTapOrUntapEvent(event.getSource(), perm))) ), TapParalyze( "tap " + ARG.PERMANENTS + "( and it|\\. RN|\\. it|\\. Those creatures|\\. That creature|\\. That permanent) (doesn't|don't) untap during (its|their) controller('s|s') next untap step(s)?", MagicTargetHint.Negative, MagicTapTargetPicker.Tap, MagicTiming.Tapping, "Tap" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new TapAction(it)); game.doAction(ChangeStateAction.Set( it, MagicPermanentState.DoesNotUntapDuringNext )); } }; } }, Tap( "tap " + ARG.PERMANENTS, MagicTargetHint.Negative, MagicTapTargetPicker.Tap, MagicTiming.Tapping, "Tap" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new TapAction(it)); } }; } }, Paralyze( ARG.PERMANENTS + " doesn't untap during (your|its controller's) next untap step", MagicTargetHint.Negative, new MagicNoCombatTargetPicker(true, true, false), MagicTiming.Tapping, "Paralyze" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(ChangeStateAction.Set( it, MagicPermanentState.DoesNotUntapDuringNext )); } }; } }, Untap( "untap " + ARG.PERMANENTS, MagicTargetHint.Positive, MagicTapTargetPicker.Untap, MagicTiming.Tapping, "Untap" ) { public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new UntapAction(it)); } }; } @Override public MagicCondition[] getConditions(final Matcher matcher) { return matcher.group("sn") != null ? new MagicCondition[]{new MagicArtificialCondition(MagicCondition.TAPPED_CONDITION)} : MagicActivation.NO_COND; } }, CreateTokenCopy( "(you )?create a token that's a copy of " + ARG.PERMANENTS + "(?= and that's|,|\\.|$)( and that's)?" + ARG.MODS, MagicTiming.Token, "Token" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); final List<MagicPlayMod> mods = ARG.mods(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new PlayTokenAction( event.getPlayer(), it, mods )); } }; } }, CreateTokens( ARG.PLAYERS + "( )?create(s)? " + ARG.AMOUNT + " (?<name>[^\\.]*token(s)?[^\\.]*?(?= that's| for each|,|\\.|$))( that's)?" + ARG.MODS + "( )?((for each|where X is) " + ARG.WORDRUN + ")?", MagicTiming.Token, "Token" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAmount eachCount = MagicAmountParser.build(ARG.wordrun(matcher)); final MagicAmount tokenCount = ARG.amountObj(matcher); final String tokenName = matcher.group("name").replace("tokens", "token"); final MagicCardDefinition tokenDef = CardDefinitions.getToken(tokenName); final List<MagicPlayMod> mods = ARG.mods(matcher); final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { final int multiplier = eachCount.getAmount(event); int total = (eachCount != MagicAmountFactory.One && tokenCount == MagicAmountFactory.XCost) ? multiplier : tokenCount.getAmount(event) * multiplier; if (eachCount != MagicAmountFactory.One) { game.logAppendValue(event.getPlayer(), total); } for (final MagicPlayer it : ARG.players(event, matcher, filter)) { for (int i = 0; i < total; i++) { game.doAction(new PlayTokenAction( it, tokenDef, mods )); } } }; } }, Mill( ARG.PLAYERS + "( )?put(s)? the top " + ARG.AMOUNT + "?( )?card(s)? of (your|his or her) library into (your|his or her) graveyard", MagicTiming.Draw, "Mill" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAmount count = ARG.amountObj(matcher); final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { final int amount = count.getAmount(event); for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.doAction(new MillLibraryAction(it, amount)); } }; } }, CantCastSpells( ARG.PLAYERS + " can't cast spells this turn", MagicTargetHint.Negative, MagicTiming.FirstMain, "Silence" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.doAction(new AddStaticAction(new MagicStatic(MagicLayer.Player, MagicStatic.UntilEOT) { @Override public void modPlayer(final MagicPermanent source, final MagicPlayer player) { if (player.getId() == it.getId()) { player.setState(MagicPlayerState.CantCastSpells); } } })); } }; } }, Manifest( "manifest the top " + ARG.AMOUNT + "?( )?card(s)? of your library", MagicTiming.Token, "Manifest" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.amount(matcher); return (game, event) -> game.doAction(new ManifestAction(event.getPlayer(), amount)); } }, SacrificeSelf( "(its controller )?sacrifice(s)? sn", MagicTiming.Removal, "Sacrifice", (game, event) -> game.doAction(new SacrificeAction(event.getPermanent())) ), SacrificeEndStep( "(its controller )?sacrifice(s)? " + ARG.PERMANENTS + " at the beginning of the next end step", MagicTiming.Removal, "Sacrifice" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new AddTriggerAction(it, AtEndOfTurnTrigger.Sacrifice)); } }; } }, SacrificeEndCombat( "(its controller )?sacrifice(s)? " + ARG.PERMANENTS + " at end of combat", MagicTiming.Removal, "Sacrifice" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new AddTriggerAction(it, AtEndOfCombatTrigger.Sacrifice)); } }; } }, SacrificeChosen( ARG.PLAYERS + "( )?sacrifice(s)? (?<another>another )?(" + ARG.AMOUNT + " )?" + ARG.WORDRUN, MagicTargetHint.Negative, MagicTiming.Removal, "Sacrifice" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPlayer> pfilter = ARG.playersParse(matcher); final int amt = ARG.amount(matcher); final String chosen = MagicTargetFilterFactory.toSingular(ARG.wordrun(matcher)) + " you control"; final MagicTargetFilter<MagicPermanent> regular = MagicTargetFilterFactory.Permanent(chosen); final MagicTargetFilter<MagicPermanent> filter = matcher.group("another") != null ? new MagicOtherPermanentTargetFilter(regular) : regular; final MagicTargetChoice choice = new MagicTargetChoice( filter, ("aeiou".indexOf(chosen.charAt(0)) >= 0 ? "an " : "a ") + chosen ); return (game, event) -> { for (final MagicPlayer it : ARG.players(event, matcher, pfilter)) { for (int i = 0; i < amt; i++) { game.addEvent(new MagicSacrificePermanentEvent(event.getSource(), it, choice)); } } }; } }, Scry1( "(pn )?scry 1", MagicTiming.Draw, "Scry" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return (game, event) -> game.addEvent(new MagicScryEvent(event)); } }, PseudoScry( "Look at the top card of your library\\. You may put that card on the bottom of your library", MagicTiming.Draw, "Scry" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return (game, event) -> game.addEvent(MagicScryEvent.Pseudo(event)); } }, TutorTopReveal( "Look at the top " + ARG.AMOUNT + " cards of your library. You may reveal a(n)? " + ARG.WORDRUN + " from among them and put it into your hand\\. " + "(Then )?Put the rest on the bottom of your library in any order", MagicTiming.Draw, "Look" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.amount(matcher); final MagicTargetFilter<MagicCard> filter = MagicTargetFilterFactory.Card(ARG.wordrun(matcher) + " from your library"); return (game, event) -> game.addEvent(MagicTutorTopEvent.look(event, amount, filter)); } }, TutorTopBottom( "Look at the top " + ARG.AMOUNT + " cards of your library. Put " + ARG.AMOUNT2 + " of (them|those cards) into your hand and the (other|rest) on the bottom of your library( in any order)?", MagicTiming.Draw, "Look" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int n = ARG.amount(matcher); final int h = ARG.amount2(matcher); return (game, event) -> game.addEvent(MagicTutorTopEvent.toBottom(event, n, h)); } }, TutorTopGraveyard( "Look at the top " + ARG.AMOUNT + " cards of your library. Put " + ARG.AMOUNT2 + " of them into your hand and the (other|rest) into your graveyard", MagicTiming.Draw, "Look" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int n = ARG.amount(matcher); final int h = ARG.amount2(matcher); return (game, event) -> game.addEvent(MagicTutorTopEvent.toGraveyard(event, n, h)); } }, LoseGame( ARG.PLAYERS + " lose(s)? the game", MagicTargetHint.Negative, MagicTiming.Removal, "Lose" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.doAction(new LoseGameAction(it, LoseGameAction.EFFECT_REASON)); } }; } }, PayLose( "At the beginning of your next upkeep, pay " + ARG.MANACOST + "\\. If you don't, you lose the game", MagicTargetHint.Negative, MagicTiming.Removal, "Lose" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return (game, event) -> { game.doAction(new AddTriggerAction( AtYourUpkeepTrigger.PayOrLose( event.getSource(), event.getPlayer(), ARG.manacost(matcher) ) )); }; } }, WinGame( ARG.PLAYERS + " win(s)? the game", MagicTargetHint.Positive, MagicTiming.Removal, "Win" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.doAction(new LoseGameAction(it.getOpponent(), LoseGameAction.EFFECT_REASON)); } }; } }, /* Scry( "(pn )?scry (?<amount>[0-9]+)", MagicTiming.Draw, "Scry" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = Integer.parseInt(matcher.group("amount")); return (final MagicGame game, final MagicEvent event) -> { game.addEvent(new MagicScryXEvent(event.getSource(),event.getPlayer(),amount)); }; } }, LookHand( "look at " + ARG.CHOICE + "'s hand", MagicTargetHint.Negative, MagicTiming.Flash, "Look" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return (final MagicGame game, final MagicEvent event) -> { event.processTargetPlayer(game, (final MagicPlayer player) -> { game.doAction(new MagicRevealAction(player.getHand())); }); }; } }, */ Regenerate( "regenerate " + ARG.PERMANENTS, MagicTargetHint.Positive, MagicRegenerateTargetPicker.create(), MagicTiming.Pump, "Regen" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new RegenerateAction(it)); } }; } @Override public MagicCondition[] getConditions(final Matcher matcher) { return matcher.group("sn") != null ? new MagicCondition[]{MagicCondition.CAN_REGENERATE_CONDITION} : MagicActivation.NO_COND; } }, SwitchPT( "switch " + ARG.PERMANENTS + "'s power and toughness until end of turn", MagicTargetHint.None, MagicDefaultPermanentTargetPicker.create(), MagicTiming.Pump, "Switch" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new AddStaticAction(it, MagicStatic.SwitchPT)); } }; } }, ShuffleSelf( "(shuffle sn|sn's owner shuffles it) into (its owner's|his or her) library", MagicTiming.Removal, "Shuffle", (game, event) -> { final MagicSource source = event.getSource(); if (source.isPermanent()) { game.doAction(new RemoveFromPlayAction(event.getPermanent(), MagicLocationType.OwnersLibrary)); } else { game.doAction(new ChangeCardDestinationAction(event.getCardOnStack(), MagicLocationType.OwnersLibrary)); } } ), ShuffleYourLibrary( "shuffle your library", MagicTiming.None, "Shuffle", (game, event) -> game.doAction(new ShuffleLibraryAction(event.getPlayer())) ), AttachSelf( "attach sn to " + ARG.PERMANENTS, MagicTargetHint.Positive, MagicPumpTargetPicker.create(), MagicTiming.Pump, "Attach" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new AttachAction(event.getPermanent(), it)); } }; } }, TurnFaceDown( "turn " + ARG.PERMANENTS + " face down", MagicTiming.Tapping, "Face Down" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new TurnFaceDownAction(it)); } }; } }, TurnFaceUp( "turn " + ARG.PERMANENTS + " face up", MagicTiming.Tapping, "Face Up" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new TurnFaceUpAction(it)); } }; } }, FlipSelf( "flip sn", MagicTiming.Pump, "Flip", (game, event) -> game.doAction(new FlipAction(event.getPermanent())) ), Transform( "transform " + ARG.PERMANENTS, MagicTiming.Pump, "Transform" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new TransformAction(it)); } }; } }, Populate( "populate", MagicTiming.Token, "Populate", (game, event) -> game.addEvent(new MagicPopulateEvent(event.getSource())) ), Cipher( "cipher", MagicTiming.Main, "Cipher", (game, event) -> game.doAction(new CipherAction(event.getCardOnStack(), event.getPlayer())) ), DetainChosen( "detain " + ARG.CHOICE, MagicTargetHint.Negative, new MagicNoCombatTargetPicker(true, true, false), MagicTiming.FirstMain, "Detain", (game, event) -> event.processTargetPermanent(game, (final MagicPermanent creature) -> game.doAction(new DetainAction(event.getPlayer(), creature))) ), GoadChosen( "goad " + ARG.CHOICE, MagicTargetHint.Negative, MagicMustAttackTargetPicker.create(), MagicTiming.FirstMain, "Goad", (game, event) -> event.processTargetPermanent(game, (final MagicPermanent creature) -> game.doAction(new GoadAction(event.getPlayer(), creature))) ), CopySpell( "copy " + ARG.CHOICE + "\\. You may choose new targets for (the|that) copy", MagicTiming.Spell, "Copy", (game, event) -> event.processTargetCardOnStack(game, (final MagicCardOnStack item) -> game.doAction(new CopyCardOnStackAction(event.getPlayer(), item))) ), Monstrosity( "monstrosity " + ARG.AMOUNT, MagicTiming.Pump, "Monstrous" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.amount(matcher); return (game, event) -> { final MagicPermanent SN = event.getPermanent(); if (MagicCondition.NOT_MONSTROUS_CONDITION.accept(SN)) { game.doAction(new ChangeCountersAction(SN, MagicCounterType.PlusOne, amount)); game.doAction(ChangeStateAction.Set(SN, MagicPermanentState.Monstrous)); } }; } @Override public MagicCondition[] getConditions(final Matcher matcher) { return new MagicCondition[]{ MagicCondition.NOT_MONSTROUS_CONDITION, }; } }, Rebound( "rebound" ) { private final MagicEventAction EVENT_ACTION = (game, event) -> { final MagicCardOnStack spell = event.getCardOnStack(); if (spell.getFromLocation() == MagicLocationType.OwnersHand) { game.doAction(new ChangeCardDestinationAction(spell, MagicLocationType.Exile)); game.doAction(new AddTriggerAction(new ReboundTrigger(spell.getCard()))); } }; @Override public MagicEventAction getAction(final Matcher matcher) { return EVENT_ACTION; } }, Investigate( "investigate", MagicTiming.Token, "Investigate" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return (game, event) -> game.doAction(new PlayTokenAction(event.getPlayer(), CardDefinitions.getToken("colorless Clue artifact token"))); } }, BecomesMonarch( "you become the monarch" ){ @Override public MagicEventAction getAction(final Matcher matcher) { return (game, event) -> game.doAction(new BecomeMonarchAction(event.getPlayer())); } }, BecomeBlocked( ARG.PERMANENTS + " become(s)? blocked", MagicTiming.Block, "Block" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(ChangeStateAction.Set(it, MagicPermanentState.Blocked)); } }; } }, BecomesAlt( "(?<duration>until end of turn, )" + ARG.PERMANENTS + " becomes( a| an)?( )?(?<pt>[0-9]+/[0-9]+)? (?<all>.*?)( (with|and gains) (?<ability>.*?))?(?<additionTo>((\\.)? It's| that's) still [^\\.]*)?", MagicTiming.Animate, "Becomes" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return Becomes.getAction(matcher); } @Override public MagicCondition[] getConditions(final Matcher matcher) { return Becomes.getConditions(matcher); } }, BecomesAddition( ARG.PERMANENTS + " become(s)?( a| an)?( )?(?<pt>[0-9]+/[0-9]+)? (?<all>.*?)( (with|and gains) (?<ability>.*?))?(?<additionTo> in addition to its other [a-z]*)(?<duration> until end of turn)?", MagicTiming.Animate, "Becomes" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return Becomes.getAction(matcher); } @Override public MagicCondition[] getConditions(final Matcher matcher) { return Becomes.getConditions(matcher); } }, Becomes( ARG.PERMANENTS + " become(s)?( a| an)?( )?(?<pt>[0-9]+/[0-9]+)? (?<all>.*?)( (with|and gains) (?<ability>.*?))?(?<duration> until end of turn)?(?<additionTo>(\\. It's| that's) still [^\\.]*)?", MagicTiming.Animate, "Becomes" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final PermanentSpecParser spec = new PermanentSpecParser(matcher); final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new BecomesAction( it, spec.pt, spec.colors, spec.subTypes, spec.types, spec.abilities, spec.duration, spec.additionTo )); } }; } @Override public MagicCondition[] getConditions(final Matcher matcher) { return matcher.group("sn") != null ? new MagicCondition[]{MagicCondition.NOT_EXCLUDE_COMBAT_CONDITION} : MagicActivation.NO_COND; } }, GainProtection( ARG.PERMANENTS + " gain(s)? protection from the color of your choice until end of turn", MagicTargetHint.Positive, MagicTiming.Pump, "Protection" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.addEvent(new MagicGainProtectionFromEvent( event.getSource(), event.getPlayer(), it )); } }; } }, GainAbilityAlt( "(?<ueot>until end of turn), " + ARG.PERMANENTS + " gain(s)? (?<ability>.+)", MagicTargetHint.Positive ) { @Override public MagicEventAction getAction(final Matcher matcher) { return GainAbility.getAction(matcher); } @Override public MagicTiming getTiming(final Matcher matcher) { return GainAbility.getTiming(matcher); } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { return GainAbility.getPicker(matcher); } @Override public String getName(final Matcher matcher) { return GainAbility.getName(matcher); } @Override public MagicCondition[] getConditions(final Matcher matcher) { return GainAbility.getConditions(matcher); } }, GainAbility( ARG.PERMANENTS + " gain(s)? (?<ability>.+?)(?<ueot> until end of turn)?", MagicTargetHint.Positive ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAbilityList abilityList = MagicAbility.getAbilityList(matcher.group("ability")); final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); final boolean duration = matcher.group("ueot") != null ? MagicStatic.UntilEOT : MagicStatic.Forever; return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new GainAbilityAction(it, abilityList, duration)); } }; } @Override public MagicTiming getTiming(final Matcher matcher) { final MagicAbility ability = MagicAbility.getAbilityList(matcher.group("ability")).getFirst(); switch (ability) { case Haste: case Vigilance: return MagicTiming.FirstMain; case FirstStrike: case DoubleStrike: return MagicTiming.Block; default: return MagicTiming.Pump; } } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { final MagicAbility ability = MagicAbility.getAbilityList(matcher.group("ability")).getFirst(); switch (ability) { case Deathtouch: return MagicDeathtouchTargetPicker.create(); case Lifelink: return MagicLifelinkTargetPicker.create(); case FirstStrike: case DoubleStrike: return MagicFirstStrikeTargetPicker.create(); case Haste: return MagicHasteTargetPicker.create(); case Indestructible: return MagicIndestructibleTargetPicker.create(); case Trample: return MagicTrampleTargetPicker.create(); case Flying: return MagicFlyingTargetPicker.create(); default: return MagicPumpTargetPicker.create(); } } @Override public String getName(final Matcher matcher) { return "+" + capitalize(matcher.group("ability")); } @Override public MagicCondition[] getConditions(final Matcher matcher) { if (matcher.group("sn") != null) { final MagicAbility ability = MagicAbility.getAbilityList(matcher.group("ability")).getFirst(); return new MagicCondition[]{ MagicConditionFactory.NoAbility(ability) }; } else { return MagicActivation.NO_COND; } } }, GainAbilityCantBlockSN( ARG.PERMANENTS + " can't block SN this turn", MagicTargetHint.Negative, MagicTiming.Attack, "Can't Block" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { final int pIdx = event.getPlayer().getIndex(); final CantBlockTrigger trigger = CantBlockTrigger.create(event.getPermanent().getId()); game.doAction(new AddStaticAction(new MagicStatic(MagicLayer.Game, MagicStatic.UntilEOT) { @Override public void modGame(final MagicPermanent source, final MagicGame game) { for (final MagicPermanent it : filter.filter(game.getPlayer(pIdx))) { it.addAbility(trigger); } } })); }; } }, GainAbilityCan( ARG.PERMANENTS + " (?<ability>(can('t)?|attack(s)?) .+?)( this turn)?" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAbilityList abilityList = MagicAbility.getAbilityList(matcher.group("ability")); final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { if (filter.isStatic()) { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new GainAbilityAction(it, abilityList)); } } else { final int pIdx = event.getPlayer().getIndex(); game.doAction(new AddStaticAction(new MagicStatic(MagicLayer.Game, MagicStatic.UntilEOT) { @Override public void modGame(final MagicPermanent source, final MagicGame game) { for (final MagicPermanent it : filter.filter(game.getPlayer(pIdx))) { it.addAbility(abilityList); } } })); } }; } @Override public MagicTiming getTiming(final Matcher matcher) { final MagicAbility ability = MagicAbility.getAbilityList(matcher.group("ability")).getFirst(); switch (ability) { case CannotAttack: case AttacksEachTurnIfAble: return MagicTiming.MustAttack; case CannotBlock: case Unblockable: return MagicTiming.Attack; default: return MagicTiming.Pump; } } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { final MagicAbility ability = MagicAbility.getAbilityList(matcher.group("ability")).getFirst(); switch (ability) { case CannotAttack: return new MagicNoCombatTargetPicker(true, false, false); case CannotBlock: return new MagicNoCombatTargetPicker(false, true, false); case CannotAttackOrBlock: return new MagicNoCombatTargetPicker(true, true, false); case AttacksEachTurnIfAble: return MagicMustAttackTargetPicker.create(); case Unblockable: return MagicUnblockableTargetPicker.create(); default: return MagicDefaultTargetPicker.create(); } } @Override public MagicTargetHint getHint(final Matcher matcher) { final MagicAbility ability = MagicAbility.getAbility(matcher.group("ability")); if (ability.getName().contains("be blocked")) { return MagicTargetHint.Positive; } else { return MagicTargetHint.Negative; } } @Override public String getName(final Matcher matcher) { return capitalize(matcher.group("ability")); } @Override public MagicCondition[] getConditions(final Matcher matcher) { return GainAbility.getConditions(matcher); } }, LoseAbility( ARG.PERMANENTS + " lose(s)? (?<ability>.+?)(?<ueot> until end of turn)?", MagicTargetHint.Negative ) { @Override public MagicEventAction getAction(final Matcher matcher) { final MagicAbilityList abilityList = MagicAbility.getAbilityList(matcher.group("ability")); final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); final boolean duration = matcher.group("ueot") != null ? MagicStatic.UntilEOT : MagicStatic.Forever; return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new LoseAbilityAction(it, abilityList, duration)); } }; } @Override public MagicTiming getTiming(final Matcher matcher) { return GainAbility.getTiming(matcher); } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { final MagicAbility ability = MagicAbility.getAbilityList(matcher.group("ability")).getFirst(); return new MagicLoseAbilityTargetPicker(ability); } @Override public String getName(final Matcher matcher) { return "-" + capitalize(matcher.group("ability")); } @Override public MagicCondition[] getConditions(final Matcher matcher) { if (matcher.group("sn") != null) { final MagicAbility ability = MagicAbility.getAbilityList(matcher.group("ability")).getFirst(); return new MagicCondition[]{ MagicConditionFactory.HasAbility(ability) }; } else { return MagicActivation.NO_COND; } } }, NoCombatDamage( ARG.IT + " assigns no combat damage this turn" ) { @Override public MagicEventAction getAction(final Matcher matcher) { return (game, event) -> { game.doAction(ChangeStateAction.Set( ARG.itPermanent(event, matcher), MagicPermanentState.NoCombatDamage )); }; } }, GainControl( "gain control of " + ARG.PERMANENTS + "(?<ueot> until end of turn)?", MagicTargetHint.Negative, MagicExileTargetPicker.create(), MagicTiming.Removal, "Control" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final boolean duration = matcher.group("ueot") != null ? MagicStatic.UntilEOT : MagicStatic.Forever; final MagicTargetFilter<MagicPermanent> filter = ARG.permanentsParse(matcher); return (game, event) -> { for (final MagicPermanent it : ARG.permanents(event, matcher, filter)) { game.doAction(new GainControlAction(event.getPlayer(), it, duration)); } }; } }, Clash( "clash with an opponent. If you win, (?<effect>.*)", MagicTiming.None, "Clash" ) { @Override public MagicChoice getChoice(final Matcher matcher) { final MagicSourceEvent e = MagicRuleEventAction.create(matcher.group("effect")); return e.getChoice(); } @Override public MagicEventAction getAction(final Matcher matcher) { final MagicSourceEvent e = MagicRuleEventAction.create(matcher.group("effect")); final MagicEventAction act = e.getAction(); return (game, event) -> MagicClashEvent.EventAction(act).executeEvent(game, event); } }, FlipCoin( "Flip a coin\\.( If you win the flip, (?<win>.*?))?( If you lose the flip, (?<lose>.*))?" ) { @Override public MagicChoice getChoice(final Matcher matcher) { final String[] alts = {"win", "lose"}; for (final String alt : alts) { final String effect = matcher.group(alt); if (effect != null) { final MagicSourceEvent e = MagicRuleEventAction.create(effect); if (e.getChoice().isValid()) { throw new RuntimeException("flip effect should not have choice: \"" + effect + "\""); } } } return MagicChoice.NONE; } @Override public MagicEventAction getAction(final Matcher matcher) { final MagicEventAction winAction = matcher.group("win") != null ? MagicRuleEventAction.create(matcher.group("win")).getAction() : MagicEventAction.NONE; final MagicEventAction loseAction = matcher.group("lose") != null ? MagicRuleEventAction.create(matcher.group("lose")).getAction() : MagicEventAction.NONE; return (game, event) -> game.addEvent(new MagicCoinFlipEvent(event, winAction, loseAction)); } @Override public MagicTiming getTiming(final Matcher matcher) { final MagicSourceEvent e = matcher.group("win") != null ? MagicRuleEventAction.create(matcher.group("win")) : MagicRuleEventAction.create(matcher.group("lose")); return e.getTiming(); } @Override public MagicTargetPicker<?> getPicker(final Matcher matcher) { final MagicSourceEvent e = matcher.group("win") != null ? MagicRuleEventAction.create(matcher.group("win")) : MagicRuleEventAction.create(matcher.group("lose")); return e.getPicker(); } @Override public String getName(final Matcher matcher) { final MagicSourceEvent e = matcher.group("win") != null ? MagicRuleEventAction.create(matcher.group("win")) : MagicRuleEventAction.create(matcher.group("lose")); return e.getName(); } }, Poison( ARG.PLAYERS + " get(s)? " + ARG.AMOUNT + " poison counter(s)?", MagicTargetHint.Negative, MagicTiming.Removal, "Poison" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.amount(matcher); final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.doAction(new ChangeCountersAction(it, MagicCounterType.Poison, amount)); } }; } }, Energy( ARG.PLAYERS + "( )?get(s)? " + ARG.ENERGY, MagicTargetHint.Positive, MagicTiming.Pump, "Energy" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.energy(matcher); final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.doAction(new ChangeCountersAction(it, MagicCounterType.Energy, amount)); } }; } }, Experience( ARG.PLAYERS + " get(s)? " + ARG.AMOUNT + " experience counter(s)?", MagicTargetHint.Positive, MagicTiming.Pump, "Experience" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.amount(matcher); final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.doAction(new ChangeCountersAction(it, MagicCounterType.Experience, amount)); } }; } }, ExtraTurn( ARG.PLAYERS + "( )?take(s)? " + ARG.AMOUNT + " extra turn(s)? after this one", MagicTargetHint.Positive, MagicTiming.SecondMain, "+Turn" ) { @Override public MagicEventAction getAction(final Matcher matcher) { final int amount = ARG.amount(matcher); final MagicTargetFilter<MagicPlayer> filter = ARG.playersParse(matcher); return (game, event) -> { for (final MagicPlayer it : ARG.players(event, matcher, filter)) { game.doAction(new ChangeExtraTurnsAction(it, amount)); } }; } }, ; private final Pattern pattern; private final MagicTargetHint hint; private final MagicEventAction action; private final MagicTargetPicker<?> picker; private final MagicTiming timing; private final String name; private MagicRuleEventAction(final String aPattern) { this(aPattern, MagicTargetHint.None, MagicDefaultTargetPicker.create(), MagicTiming.None, "", MagicEventAction.NONE); } private MagicRuleEventAction(final String aPattern, final MagicTargetHint aHint) { this(aPattern, aHint, MagicDefaultTargetPicker.create(), MagicTiming.None, "", MagicEventAction.NONE); } private MagicRuleEventAction(final String aPattern, final MagicTiming timing) { this(aPattern, MagicTargetHint.None, MagicDefaultTargetPicker.create(), timing, "", MagicEventAction.NONE); } private MagicRuleEventAction(final String aPattern, final MagicTiming aTiming, final String aName) { this(aPattern, MagicTargetHint.None, MagicDefaultTargetPicker.create(), aTiming, aName, MagicEventAction.NONE); } private MagicRuleEventAction(final String aPattern, final MagicTiming aTiming, final String aName, final MagicEventAction aAction) { this(aPattern, MagicTargetHint.None, MagicDefaultTargetPicker.create(), aTiming, aName, aAction); } private MagicRuleEventAction( final String aPattern, final MagicTargetHint aHint, final MagicTargetPicker<?> aPicker, final MagicTiming aTiming, final String aName ) { this(aPattern, aHint, aPicker, aTiming, aName, MagicEventAction.NONE); } private MagicRuleEventAction( final String aPattern, final MagicTargetHint aHint, final MagicTiming aTiming, final String aName ) { this(aPattern, aHint, MagicDefaultTargetPicker.create(), aTiming, aName, MagicEventAction.NONE); } private MagicRuleEventAction( final String aPattern, final MagicTargetHint aHint, final MagicTiming aTiming, final String aName, final MagicEventAction aAction ) { this(aPattern, aHint, MagicDefaultTargetPicker.create(), aTiming, aName, aAction); } private MagicRuleEventAction( final String aPattern, final MagicTargetHint aHint, final MagicTargetPicker<?> aPicker, final MagicTiming aTiming, final String aName, final MagicEventAction aAction ) { pattern = Pattern.compile(aPattern + "(\\.|,)?", Pattern.CASE_INSENSITIVE); hint = aHint; picker = aPicker; timing = aTiming; name = aName; action = aAction; } public boolean matches(final String rule) { return pattern.matcher(rule).matches(); } public MagicTargetPicker<?> getPicker(final Matcher matcher) { return picker; } public MagicEventAction getAction(final Matcher matcher) { return action; } public MagicTiming getTiming(final Matcher matcher) { return timing; } public String getName(final Matcher matcher) { return name; } public MagicTargetHint getHint(final Matcher matcher) { return hint; } public boolean isIndependent() { return pattern.toString().contains("sn") == false; } public MagicChoice getChoice(final Matcher matcher) { try { return matcher.group("choice") != null ? new MagicTargetChoice(getHint(matcher), matcher.group("choice")) : MagicChoice.NONE; } catch (IllegalArgumentException e) { return MagicChoice.NONE; } } public MagicCondition[] getConditions(final Matcher matcher) { return MagicActivation.NO_COND; } public static MagicRuleEventAction match(final String rule) { for (final MagicRuleEventAction ruleAction : MagicRuleEventAction.values()) { if (ruleAction.matches(rule)) { return ruleAction; } } throw new RuntimeException("unknown effect \"" + rule + "\""); } private static String capitalize(final String text) { return Character.toUpperCase(text.charAt(0)) + text.substring(1); } public Matcher matched(final String rule) { final Matcher matcher = pattern.matcher(rule); final boolean matches = matcher.matches(); if (!matches) { throw new RuntimeException("unknown effect: \"" + rule + "\""); } return matcher; } static final Pattern TARGET_REFERENCE = Pattern.compile("\\*([^*]+)\\*"); private static MagicEventAction computeEventAction(final MagicEventAction main, final String[] part) { if (part.length > 1) { final MagicEventAction[] acts = new MagicEventAction[part.length]; acts[0] = main; for (int i = 1; i < part.length; i++) { final Matcher matcher = TARGET_REFERENCE.matcher(part[i]); final boolean hasReference = matcher.find(); final String rider = (hasReference && matcher.group().contains("player")) ? matcher.replaceAll("target player") : matcher.replaceAll("target permanent"); final String riderWithoutPrefix = rider.replaceAll("^(and|then|Then|If you do,) ", ""); final MagicSourceEvent riderSourceEvent = build(riderWithoutPrefix); //rider cannot have choices if (hasReference == false && riderSourceEvent.getChoice().isValid()) { throw new RuntimeException("rider should not have choice: \"" + part[i] + "\""); } if (riderSourceEvent.getEventChoice() instanceof MagicMayChoice) { throw new RuntimeException("rider should not have choice: \"" + part[i] + "\""); } acts[i] = riderSourceEvent.getEventAction(); part[i] = matcher.replaceAll("$1"); } return MagicEventActionFactory.compose(acts); } else { return main; } } public static String personalizeWithChoice(final MagicChoice choice, final String text) { return personalize(addChoiceIndicator(choice, text)); } public static String personalize(final String text) { return text .replaceAll("(Y|y)ou discard ", "PN discards ") .replaceAll("discard ", "discards ") .replaceAll("(?<!may )reveal ", "reveals ") .replaceAll("(S|s)earch your ", "PN searches his or her ") .replaceAll("(S|s)huffle your ", "PN shuffles his or her ") .replaceAll("(Y|y)ou draw ", "PN draws ") .replaceAll("(?<!may )(D|d)raw ", "PN draws ") .replaceAll("(Y|y)ou put ", "PN puts ") .replaceAll("(?<!may )(P|p)ut ", "PN puts ") .replaceAll("((Y|y)ou )?(C|c)reate ", "PN creates ") .replaceAll("(S|s)acrifice ", "PN sacrifices ") .replaceAll("(Y|y)ou don't\\b", "PN doesn't") .replaceAll("(Y|y)ou do\\b", "PN does") .replaceAll("(Y|y)ou win\\b", "PN wins") .replaceAll("(Y|y)ou have ", "PN has ") .replaceAll("(Y|y)ou cast ", "PN casts ") .replaceAll("(Y|y)ou gain ", "PN gains ") .replaceAll("(Y|y)ou lose ", "PN loses ") .replaceAll("(Y|y)ou become ", "PN becomes ") .replaceAll("(Y|y)ou own\\b", "PN owns") .replaceAll("(Y|y)ou control\\b", "PN controls") .replaceAll("(Y|y)ou both own and control\\b", "PN both owns and controls") .replaceAll("(Y|y)ou gained ", "PN gained ") .replaceAll("(Y|y)ou attacked ", "PN attacked ") .replaceAll("(Y|y)ou controlled\\b", "PN controlled") .replaceAll("(Y|y)ou may ", "PN may ") .replaceAll("(Y|y)ou get ", "PN gets ") .replaceAll("(Y|y)ou pay ", "PN pays ") .replaceAll("to you\\b", "to PN") .replaceAll("than you\\b", "than PN") .replaceAll("(Y|y)our ", "PN's ") .replaceAll("(Y|y)ou're ", "PN is ") .replaceAll("(C|c)hoose one ", "$1hoose one\\$ ") ; } public static String mayTense(final String text) { return text .replaceAll("PN's hand ", "his or her hand ") .replaceAll("PN searches ", "search ") .replaceAll("PN shuffles ", "shuffle ") .replaceAll("PN draws ", "draw ") .replaceAll("(D|d)iscards ", "$1iscard ") .replaceAll("(D|d)raws ", "$1raw ") .replaceAll("(S|s)acrifices ", "$1acrifice ") .replaceAll("(G|g)ains ", "$1ain ") .replaceAll("(L|l)oses ", "$1lose ") .replaceAll("PN puts ", "put ") .replaceAll("reveals ", "reveal ") .replaceAll("you don't", "he or she doesn't") .replaceFirst("^PN ", "") ; } public static String addChoiceIndicator(final MagicChoice choice, final String text) { final MagicTargetChoice tchoice = choice.getTargetChoice(); if (tchoice.isValid()) { final Pattern p = Pattern.compile(Pattern.quote(tchoice.getTargetDescription()) + "('s)?", Pattern.CASE_INSENSITIVE); final Matcher m = p.matcher(text); return m.replaceFirst("$0\\$"); } else if (choice.isValid()) { //replace final period with target indicator and period return text.replaceAll("\\.$", "\\$."); } else { return text; } } private static String renameThisThat(final String text) { final String replaceThis = text.replaceAll("\\b(T|t)his " + ARG.THING + "( |\\.|'s|\\b)" + ARG.EVENQUOTES, "SN$3"); // only perform 'that' replacement for part of text before before first occurrence of 'target' final String[] parts = replaceThis.replaceAll("\\b(T|t)arget\\b", "|$1arget|").split("\\|"); parts[0] = parts[0].replaceAll("\\b(T|t)hat " + ARG.THING + "( |\\.|'s|\\b)" + ARG.EVENQUOTES, "RN$3"); final String result = String.join("", parts); return result; } private static String concat(final String part0, final String[] parts) { final StringBuilder res = new StringBuilder(part0); for (int i = 1; i < parts.length; i++) { res.append(' ').append(parts[i]); } return res.toString(); } static final Pattern INTERVENING_IF = Pattern.compile("if " + ARG.COND + ", " + ARG.ANY, Pattern.CASE_INSENSITIVE); static final Pattern EFFECT_UNLESS = Pattern.compile(ARG.WORDRUN + " unless you " + ARG.COST, Pattern.CASE_INSENSITIVE); static final Pattern MAY_DO = Pattern.compile("you may " + ARG.MAY_COST + "\\. if you do, .+", Pattern.CASE_INSENSITIVE); static final Pattern MAY_DONT = Pattern.compile("you may " + ARG.COST + "\\. if you don't, .+", Pattern.CASE_INSENSITIVE); public static MagicSourceEvent create(final String text) { return build(renameThisThat(text)); } private static MagicSourceEvent build(final String text) { final String[] part = text.split("~"); final String rule = part[0]; // handle intervening if clause final Matcher ifMatcher = INTERVENING_IF.matcher(rule); final boolean ifMatched = ifMatcher.matches(); final MagicCondition ifCond = ifMatched ? MagicConditionParser.buildCompose(ARG.cond(ifMatcher)) : MagicCondition.NONE; String ruleWithoutIf = ifMatched ? ARG.any(ifMatcher) : rule; // rewrite effect unless clause into "you may <cost>, if you don't, <effect>" final Matcher unlessMatcher = EFFECT_UNLESS.matcher(ruleWithoutIf); if (unlessMatcher.matches()) { ruleWithoutIf = "You may " + ARG.cost(unlessMatcher) + " If you don't, " + ARG.wordrun(unlessMatcher) + "."; } // handle you may <cost>. if you do, <effect> final Matcher mayDoMatcher = MAY_DO.matcher(ruleWithoutIf); final boolean mayDoMatched = mayDoMatcher.matches(); final MagicMatchedCostEvent mayDoCost = mayDoMatched ? new MagicRegularCostEvent(ARG.cost(mayDoMatcher)) : MagicRegularCostEvent.NONE; String prefix = mayDoMatched ? "^(Y|y)ou may [^\\.]+\\. If you do, " : "^(Y|y)ou may "; // handle you may <cost>. if you don't, <effect> final Matcher mayDontMatcher = MAY_DONT.matcher(ruleWithoutIf); final boolean mayDontMatched = mayDontMatcher.matches(); final MagicMatchedCostEvent mayDontCost = mayDontMatched ? new MagicRegularCostEvent(ARG.cost(mayDontMatcher)) : MagicRegularCostEvent.NONE; prefix = mayDontMatched ? "^(Y|y)ou may [^\\.]+\\. If you don't, " : prefix; final String ruleWithoutMay = ruleWithoutIf.replaceFirst(prefix, ""); final boolean optional = ruleWithoutMay.length() < ruleWithoutIf.length(); final String effect = ruleWithoutMay.replaceFirst("^have ", ""); final MagicRuleEventAction ruleAction = match(effect); final Matcher matcher = ruleAction.matched(effect); // action may be composed from rule and riders final MagicEventAction action = computeEventAction(ruleAction.getAction(matcher), part); final MagicTargetPicker<?> picker = ruleAction.getPicker(matcher); final MagicChoice choice = ruleAction.getChoice(matcher); final String pnMayChoice = capitalize(ruleWithoutMay).replaceFirst("\\.", "?"); final String contextRule = personalize(addChoiceIndicator(choice, concat(ruleWithoutMay, part))); final String playerRule = personalize(addChoiceIndicator(choice, concat(rule, part))); //'If you don't' effect cannot have a choice as MagicMayChoice only prompts for choice in "Yes" case if (mayDontMatched && choice.isValid()) { throw new RuntimeException("'If you don't' effect should not have choice: \"" + effect + "\""); } final MagicChoiceFactory choiceFact = (source, player, ref) -> { if (mayDoMatched) { return new MagicMayChoice( MagicMessage.replaceName(capitalize(ARG.cost(mayDoMatcher)) + "? If you do, " + effect, source, player, ref), choice ); } else if (mayDontMatched) { return new MagicMayChoice( MagicMessage.replaceName(capitalize(ARG.cost(mayDontMatcher)) + "? If you don't, " + effect, source, player, ref) ); } else if (optional) { return new MagicMayChoice( MagicMessage.replaceName(pnMayChoice, source, player, ref), choice ); } else { return choice; } }; final String eventDesc = mayDoMatched ? "PN may$ " + mayTense(personalize(ARG.cost(mayDoMatcher))) + ". If PN does, " + contextRule : mayDontMatched ? "PN may$ " + mayTense(personalize(ARG.cost(mayDontMatcher))) + ". If PN doesn't, " + contextRule : optional ? "PN may$ " + mayTense(contextRule) : capitalize(playerRule); return new MagicSourceEvent( ruleAction, matcher, ifCond, choiceFact, picker, (game, event) -> { if (ifCond.accept(event) == false) { return; } final MagicMatchedCostEvent matchedCost = mayDoMatched ? mayDoCost : mayDontMatched ? mayDontCost : MagicRegularCostEvent.NONE; final MagicEvent costEvent = matchedCost.getEvent(event.getSource()); if (optional == false || (event.isYes() && costEvent.isSatisfied())) { if (matchedCost != MagicRegularCostEvent.NONE) { game.addEvent(costEvent); } if (mayDontMatched == false) { action.executeEvent(game, event); } } else { if (mayDontMatched) { action.executeEvent(game, event); } } }, eventDesc ); } }