From 7c3bbed8f3f9441b1e3db7da17ccac6a9a449dcd Mon Sep 17 00:00:00 2001 From: Susucre <34709007+Susucre@users.noreply.github.com> Date: Wed, 1 May 2024 20:55:50 +0200 Subject: [PATCH] make batchs for milling cards (per player, all) Rework and test the couple of existing cards triggering on mill. --- Mage.Sets/src/mage/cards/m/MirelurkQueen.java | 54 +++--------- .../src/mage/cards/t/TheWiseMothman.java | 62 +++++++------ .../src/mage/cards/z/ZellixSanityFlayer.java | 6 +- .../single/clb/ZellixSanityFlayerTest.java | 86 +++++++++++++++++++ .../cards/single/pip/TheWiseMothmanTest.java | 73 ++++++++++++++++ .../common/MillTriggeredAbility.java | 3 +- .../OneOrMoreMilledTriggeredAbility.java | 54 ++++++++++++ .../dynamicvalue/common/SavedMilledValue.java | 44 ++++++++++ Mage/src/main/java/mage/game/GameState.java | 27 ++++++ .../java/mage/game/events/BatchEvent.java | 24 +++++- .../main/java/mage/game/events/GameEvent.java | 16 +++- .../mage/game/events/MilledBatchAllEvent.java | 26 ++++++ .../events/MilledBatchForOnePlayerEvent.java | 26 ++++++ .../mage/game/events/MilledCardEvent.java | 26 ++++++ .../mage/game/events/MilledCardsEvent.java | 24 ------ .../main/java/mage/players/PlayerImpl.java | 5 +- .../watchers/common/CardsMilledWatcher.java | 3 +- 17 files changed, 452 insertions(+), 107 deletions(-) create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/clb/ZellixSanityFlayerTest.java create mode 100644 Mage.Tests/src/test/java/org/mage/test/cards/single/pip/TheWiseMothmanTest.java create mode 100644 Mage/src/main/java/mage/abilities/common/OneOrMoreMilledTriggeredAbility.java create mode 100644 Mage/src/main/java/mage/abilities/dynamicvalue/common/SavedMilledValue.java create mode 100644 Mage/src/main/java/mage/game/events/MilledBatchAllEvent.java create mode 100644 Mage/src/main/java/mage/game/events/MilledBatchForOnePlayerEvent.java create mode 100644 Mage/src/main/java/mage/game/events/MilledCardEvent.java delete mode 100644 Mage/src/main/java/mage/game/events/MilledCardsEvent.java diff --git a/Mage.Sets/src/mage/cards/m/MirelurkQueen.java b/Mage.Sets/src/mage/cards/m/MirelurkQueen.java index 67f04227133..a5895ac6cc9 100644 --- a/Mage.Sets/src/mage/cards/m/MirelurkQueen.java +++ b/Mage.Sets/src/mage/cards/m/MirelurkQueen.java @@ -2,8 +2,9 @@ package mage.cards.m; import mage.MageInt; import mage.abilities.Ability; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.TriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility; +import mage.abilities.common.OneOrMoreMilledTriggeredAbility; import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; @@ -12,13 +13,10 @@ import mage.cards.CardImpl; import mage.cards.CardSetInfo; import mage.constants.CardType; import mage.constants.SubType; -import mage.constants.Zone; import mage.counters.CounterType; -import mage.game.Game; -import mage.game.events.GameEvent; +import mage.filter.StaticFilters; import mage.target.TargetPlayer; -import java.util.Optional; import java.util.UUID; /** @@ -43,7 +41,16 @@ public final class MirelurkQueen extends CardImpl { this.addAbility(ability); // Whenever one or more nonland cards are milled, draw a card, then put a +1/+1 counter on Mirelurk Queen. This ability triggers only once each turn. - this.addAbility(new MirelurkQueenTriggeredAbility()); + TriggeredAbility triggeredAbility = new OneOrMoreMilledTriggeredAbility( + StaticFilters.FILTER_CARDS_NON_LAND, + new DrawCardSourceControllerEffect(1) + ); + triggeredAbility.addEffect( + new AddCountersSourceEffect(CounterType.P1P1.createInstance()) + .concatBy(", then") + ); + triggeredAbility.setTriggersOnceEachTurn(true); + this.addAbility(triggeredAbility); } private MirelurkQueen(final MirelurkQueen card) { @@ -54,37 +61,4 @@ public final class MirelurkQueen extends CardImpl { public MirelurkQueen copy() { return new MirelurkQueen(this); } -} - -class MirelurkQueenTriggeredAbility extends TriggeredAbilityImpl { - - MirelurkQueenTriggeredAbility() { - super(Zone.BATTLEFIELD, new DrawCardSourceControllerEffect(1)); - this.addEffect(new AddCountersSourceEffect(CounterType.P1P1.createInstance()).concatBy(", then")); - this.setTriggerPhrase("Whenever one or more nonland cards are milled, "); - this.setTriggersOnceEachTurn(true); - } - - private MirelurkQueenTriggeredAbility(final MirelurkQueenTriggeredAbility ability) { - super(ability); - } - - @Override - public MirelurkQueenTriggeredAbility copy() { - return new MirelurkQueenTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.MILLED_CARD; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - return Optional - .ofNullable(event.getTargetId()) - .map(game::getCard) - .map(card -> !card.isLand(game)) - .orElse(false); - } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/t/TheWiseMothman.java b/Mage.Sets/src/mage/cards/t/TheWiseMothman.java index 1def6993fee..1326bf34ee5 100644 --- a/Mage.Sets/src/mage/cards/t/TheWiseMothman.java +++ b/Mage.Sets/src/mage/cards/t/TheWiseMothman.java @@ -1,20 +1,25 @@ package mage.cards.t; import mage.MageInt; -import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.Ability; import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; +import mage.abilities.common.OneOrMoreMilledTriggeredAbility; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.dynamicvalue.common.SavedMilledValue; import mage.abilities.effects.common.counter.AddCountersPlayersEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.keyword.FlyingAbility; import mage.cards.CardImpl; import mage.cards.CardSetInfo; -import mage.constants.*; +import mage.constants.CardType; +import mage.constants.SubType; +import mage.constants.SuperType; +import mage.constants.TargetController; import mage.counters.CounterType; import mage.filter.StaticFilters; import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.MilledCardsEvent; import mage.target.common.TargetCreaturePermanent; +import mage.target.targetadjustment.TargetAdjuster; import java.util.UUID; @@ -41,7 +46,15 @@ public final class TheWiseMothman extends CardImpl { )); // Whenever one or more nonland cards are milled, put a +1/+1 counter on each of up to X target creatures, where X is the number of nonland cards milled this way. - this.addAbility(new TheWiseMothmanTriggeredAbility()); + Ability ability = new OneOrMoreMilledTriggeredAbility( + StaticFilters.FILTER_CARDS_NON_LAND, + new AddCountersTargetEffect(CounterType.P1P1.createInstance()) + .setText("put a +1/+1 counter on each of up to X target creatures, " + + "where X is the number of nonland cards milled this way") + ); + ability.addTarget(new TargetCreaturePermanent(0, 0)); + ability.setTargetAdjuster(new TheWiseMothmanAdjuster(SavedMilledValue.MUCH)); + this.addAbility(ability); } private TheWiseMothman(final TheWiseMothman card) { @@ -54,37 +67,20 @@ public final class TheWiseMothman extends CardImpl { } } -class TheWiseMothmanTriggeredAbility extends TriggeredAbilityImpl { +// TODO: cleanup after #12107 +class TheWiseMothmanAdjuster implements TargetAdjuster { + private final DynamicValue dynamicValue; - TheWiseMothmanTriggeredAbility() { - super(Zone.BATTLEFIELD, new AddCountersTargetEffect(CounterType.P1P1.createInstance()) - .setText("put a +1/+1 counter on each of up to X target creatures, " + - "where X is the number of nonland cards milled this way")); - this.setTriggerPhrase("Whenever one or more nonland cards are milled, "); - } - - private TheWiseMothmanTriggeredAbility(final TheWiseMothmanTriggeredAbility ability) { - super(ability); + TheWiseMothmanAdjuster(DynamicValue value) { + this.dynamicValue = value; } @Override - public TheWiseMothmanTriggeredAbility copy() { - return new TheWiseMothmanTriggeredAbility(this); - } - - @Override - public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.MILLED_CARDS; - } - - @Override - public boolean checkTrigger(GameEvent event, Game game) { - int count = ((MilledCardsEvent) event).getCards().count(StaticFilters.FILTER_CARD_NON_LAND, game); - if (count < 1) { - return false; + public void adjustTargets(Ability ability, Game game) { + int count = dynamicValue.calculate(game, ability, ability.getEffects().get(0)); + ability.getTargets().clear(); + if (count > 0) { + ability.addTarget(new TargetCreaturePermanent(0, count)); } - this.getTargets().clear(); - this.getTargets().add(new TargetCreaturePermanent(0, count)); - return true; } -} +} \ No newline at end of file diff --git a/Mage.Sets/src/mage/cards/z/ZellixSanityFlayer.java b/Mage.Sets/src/mage/cards/z/ZellixSanityFlayer.java index 50209cd6777..a6ec53671f3 100644 --- a/Mage.Sets/src/mage/cards/z/ZellixSanityFlayer.java +++ b/Mage.Sets/src/mage/cards/z/ZellixSanityFlayer.java @@ -18,7 +18,7 @@ import mage.constants.Zone; import mage.filter.StaticFilters; import mage.game.Game; import mage.game.events.GameEvent; -import mage.game.events.MilledCardsEvent; +import mage.game.events.MilledBatchForOnePlayerEvent; import mage.game.permanent.token.Horror2Token; import mage.target.TargetPlayer; @@ -79,11 +79,11 @@ class ZellixSanityFlayerTriggeredAbility extends TriggeredAbilityImpl { @Override public boolean checkEventType(GameEvent event, Game game) { - return event.getType() == GameEvent.EventType.MILLED_CARDS; + return event.getType() == GameEvent.EventType.MILLED_CARDS_BATCH_FOR_ONE_PLAYER; } @Override public boolean checkTrigger(GameEvent event, Game game) { - return ((MilledCardsEvent) event).getCards().count(StaticFilters.FILTER_CARD_CREATURE, game) > 0; + return ((MilledBatchForOnePlayerEvent) event).getCards().count(StaticFilters.FILTER_CARD_CREATURE, game) > 0; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/ZellixSanityFlayerTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/ZellixSanityFlayerTest.java new file mode 100644 index 00000000000..2de20ed7e90 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/clb/ZellixSanityFlayerTest.java @@ -0,0 +1,86 @@ +package org.mage.test.cards.single.clb; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class ZellixSanityFlayerTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.z.ZellixSanityFlayer Zellix, Sanity Flayer} {2}{U} + * Legendary Creature — Horror + * Hive Mind — Whenever a player mills one or more creature cards, you create a 1/1 black Horror creature token. + * {1}, {T}: Target player mills three cards. + * Choose a Background (You can have a Background as a second commander.) + * 2/3 + */ + private static final String zellix = "Zellix, Sanity Flayer"; + + @Test + public void test_Trigger_2Player_Milling() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, zellix); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.BATTLEFIELD, playerA, "Whetstone"); // {3}: Each player mills two cards. + addCard(Zone.LIBRARY, playerA, "Memnite", 2); + addCard(Zone.LIBRARY, playerB, "Memnite", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + setChoice(playerA, "Hive Mind"); // stacking triggers + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, 2); + assertGraveyardCount(playerA, 2); + assertPermanentCount(playerA, "Horror Token", 2); + } + + @Test + public void test_Trigger_1Player_MillingCreature() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, zellix); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.BATTLEFIELD, playerA, "Whetstone"); // {3}: Each player mills two cards. + addCard(Zone.LIBRARY, playerA, "Memnite", 2); + addCard(Zone.LIBRARY, playerB, "Taiga", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, 2); + assertGraveyardCount(playerA, 2); + assertPermanentCount(playerA, "Horror Token", 1); + } + + @Test + public void test_NoTrigger_0CreatureMilled() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, zellix); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.BATTLEFIELD, playerA, "Whetstone"); // {3}: Each player mills two cards. + addCard(Zone.LIBRARY, playerA, "Taiga", 2); + addCard(Zone.LIBRARY, playerB, "Taiga", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, 2); + assertGraveyardCount(playerA, 2); + assertPermanentCount(playerA, "Horror Token", 0); + } +} diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/pip/TheWiseMothmanTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/pip/TheWiseMothmanTest.java new file mode 100644 index 00000000000..342cbde4d91 --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/pip/TheWiseMothmanTest.java @@ -0,0 +1,73 @@ +package org.mage.test.cards.single.pip; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import mage.counters.CounterType; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * @author Susucr + */ +public class TheWiseMothmanTest extends CardTestPlayerBase { + + /** + * {@link mage.cards.t.TheWiseMothman The Wise Mothman} {1}{B}{G}{U} + * Legendary Creature — Insect Mutant + * Flying + * Whenever The Wise Mothman enters the battlefield or attacks, each player gets a rad counter. + * Whenever one or more nonland cards are milled, put a +1/+1 counter on each of up to X target creatures, where X is the number of nonland cards milled this way. + * 3/3 + */ + private static final String mothman = "The Wise Mothman"; + + @Test + public void test_Trigger_3NonLand_1Land() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, mothman); + addCard(Zone.BATTLEFIELD, playerA, "Grizzly Bears"); + addCard(Zone.BATTLEFIELD, playerA, "Elite Vanguard"); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.BATTLEFIELD, playerA, "Whetstone"); // {3}: Each player mills two cards. + addCard(Zone.LIBRARY, playerA, "Taiga", 1); + addCard(Zone.LIBRARY, playerA, "Baneslayer Angel", 1); + addCard(Zone.LIBRARY, playerB, "Memnite", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + addTarget(playerA, mothman + "^Grizzly Bears"); // up to three targets => choosing 2 + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, "Taiga", 1); + assertGraveyardCount(playerA, "Baneslayer Angel", 1); + assertGraveyardCount(playerB, "Memnite", 2); + assertCounterCount(playerA, mothman, CounterType.P1P1, 1); + assertCounterCount(playerA, "Grizzly Bears", CounterType.P1P1, 1); + assertCounterCount(playerA, "Elite Vanguard", CounterType.P1P1, 0); + } + + @Test + public void test_NoTrigger_AllLands() { + setStrictChooseMode(true); + skipInitShuffling(); + + addCard(Zone.BATTLEFIELD, playerA, mothman); + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + addCard(Zone.BATTLEFIELD, playerA, "Whetstone"); // {3}: Each player mills two cards. + addCard(Zone.LIBRARY, playerA, "Taiga", 2); + addCard(Zone.LIBRARY, playerA, "Taiga", 2); + + activateAbility(1, PhaseStep.PRECOMBAT_MAIN, playerA, "{3}"); + // no trigger + + setStopAt(1, PhaseStep.BEGIN_COMBAT); + execute(); + + assertGraveyardCount(playerA, 2); + assertGraveyardCount(playerB, 2); + assertCounterCount(playerA, mothman, CounterType.P1P1, 0); + } +} diff --git a/Mage/src/main/java/mage/abilities/common/MillTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/MillTriggeredAbility.java index 328e49964c2..e3503312e31 100644 --- a/Mage/src/main/java/mage/abilities/common/MillTriggeredAbility.java +++ b/Mage/src/main/java/mage/abilities/common/MillTriggeredAbility.java @@ -9,6 +9,7 @@ import mage.constants.Zone; import mage.filter.FilterCard; import mage.game.Game; import mage.game.events.GameEvent; +import mage.game.events.MilledCardEvent; import java.util.UUID; @@ -58,7 +59,7 @@ public class MillTriggeredAbility extends TriggeredAbilityImpl { default: throw new IllegalArgumentException("Wrong code usage. targetController not yet supported: " + targetController); } - Card card = game.getCard(event.getTargetId()); + Card card = ((MilledCardEvent) event).getCard(); return card != null && filter.match(card, getControllerId(), this, game); } diff --git a/Mage/src/main/java/mage/abilities/common/OneOrMoreMilledTriggeredAbility.java b/Mage/src/main/java/mage/abilities/common/OneOrMoreMilledTriggeredAbility.java new file mode 100644 index 00000000000..408c8681aca --- /dev/null +++ b/Mage/src/main/java/mage/abilities/common/OneOrMoreMilledTriggeredAbility.java @@ -0,0 +1,54 @@ + +package mage.abilities.common; + +import mage.abilities.TriggeredAbilityImpl; +import mage.abilities.dynamicvalue.common.SavedMilledValue; +import mage.abilities.effects.Effect; +import mage.constants.Zone; +import mage.filter.FilterCard; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.MilledBatchAllEvent; + +/** + * @author Susucr + */ +public class OneOrMoreMilledTriggeredAbility extends TriggeredAbilityImpl { + + private final FilterCard filter; + + public OneOrMoreMilledTriggeredAbility(FilterCard filter, Effect effect) { + this(filter, effect, false); + } + + public OneOrMoreMilledTriggeredAbility(FilterCard filter, Effect effect, boolean optional) { + super(Zone.BATTLEFIELD, effect, optional); + this.setTriggerPhrase("Whenever one or more " + filter.getMessage() + " are milled, "); + this.filter = filter; + } + + private OneOrMoreMilledTriggeredAbility(final OneOrMoreMilledTriggeredAbility ability) { + super(ability); + this.filter = ability.filter; + } + + @Override + public OneOrMoreMilledTriggeredAbility copy() { + return new OneOrMoreMilledTriggeredAbility(this); + } + + @Override + public boolean checkEventType(GameEvent event, Game game) { + return event.getType() == GameEvent.EventType.MILLED_CARDS_BATCH_FOR_ALL; + } + + @Override + public boolean checkTrigger(GameEvent event, Game game) { + int count = ((MilledBatchAllEvent) event).getCards().count(filter, getControllerId(), this, game); + if (count <= 0) { + return false; + } + getEffects().setValue(SavedMilledValue.VALUE_KEY, count); + return true; + } +} diff --git a/Mage/src/main/java/mage/abilities/dynamicvalue/common/SavedMilledValue.java b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SavedMilledValue.java new file mode 100644 index 00000000000..06b8aefd1b3 --- /dev/null +++ b/Mage/src/main/java/mage/abilities/dynamicvalue/common/SavedMilledValue.java @@ -0,0 +1,44 @@ +package mage.abilities.dynamicvalue.common; + +import mage.abilities.Ability; +import mage.abilities.dynamicvalue.DynamicValue; +import mage.abilities.effects.Effect; +import mage.game.Game; + +import java.util.Optional; + +/** + * @author Susucr + */ +public enum SavedMilledValue implements DynamicValue { + MANY("many"), + MUCH("much"); + + private final String message; + + public static final String VALUE_KEY = "SavedMilled"; + + SavedMilledValue(String message) { + this.message = "that " + message; + } + + @Override + public int calculate(Game game, Ability sourceAbility, Effect effect) { + return Optional.ofNullable((Integer) effect.getValue(VALUE_KEY)).orElse(0); + } + + @Override + public SavedMilledValue copy() { + return this; + } + + @Override + public String toString() { + return message; + } + + @Override + public String getMessage() { + return ""; + } +} diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 8b00464843e..581451503e0 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -901,6 +901,33 @@ public class GameState implements Serializable, Copyable { } } + public void addSimultaneousMilledCardToBatch(MilledCardEvent milledEvent, Game game) { + // Combine multiple mill cards events in the single event (batch) + // see GameEvent.MILLED_CARDS_BATCH_FOR_ONE_PLAYER and GameEvent.MILLED_CARDS_BATCH_FOR_ALL + + // existing batch + boolean isBatchUsed = false; + boolean isBatchForPlayerUsed = false; + for (GameEvent event : simultaneousEvents) { + if (event instanceof MilledBatchAllEvent) { + ((MilledBatchAllEvent) event).addEvent(milledEvent); + isBatchUsed = true; + } else if (event instanceof MilledBatchForOnePlayerEvent + && event.getPlayerId().equals(milledEvent.getPlayerId())) { + ((MilledBatchForOnePlayerEvent) event).addEvent(milledEvent); + isBatchForPlayerUsed = true; + } + } + + // new batch + if (!isBatchUsed) { + addSimultaneousEvent(new MilledBatchAllEvent(milledEvent), game); + } + if (!isBatchForPlayerUsed) { + addSimultaneousEvent(new MilledBatchForOnePlayerEvent(milledEvent), game); + } + } + public void addSimultaneousLifeLossToBatch(LifeLostEvent lifeLossEvent, Game game) { // Combine multiple life loss events in the single event (batch) // see GameEvent.LOST_LIFE_BATCH diff --git a/Mage/src/main/java/mage/game/events/BatchEvent.java b/Mage/src/main/java/mage/game/events/BatchEvent.java index 718d4caa206..6460b5b69b4 100644 --- a/Mage/src/main/java/mage/game/events/BatchEvent.java +++ b/Mage/src/main/java/mage/game/events/BatchEvent.java @@ -17,6 +17,7 @@ public abstract class BatchEvent extends GameEvent { private final Set events = new HashSet<>(); private final boolean singleTargetId; private final boolean singleSourceId; + private final boolean singlePlayerId; /** * @param eventType specific type of event @@ -25,9 +26,25 @@ public abstract class BatchEvent extends GameEvent { * @param firstEvent added to initialize the batch (batch is never empty) */ protected BatchEvent(EventType eventType, boolean singleTargetId, boolean singleSourceId, T firstEvent) { - super(eventType, (singleTargetId ? firstEvent.getTargetId() : null), null, null); + this(eventType, singleTargetId, singleSourceId, false, firstEvent); + } + + /** + * @param eventType specific type of event + * @param singleSourceId if true, all included events must have same source id + * @param singleTargetId if true, all included events must have same target id + * @param singlePlayerId if true, all included events must have same player id + * @param firstEvent added to initialize the batch (batch is never empty) + */ + protected BatchEvent(EventType eventType, boolean singleTargetId, boolean singleSourceId, boolean singlePlayerId, T firstEvent) { + super(eventType, + (singleTargetId ? firstEvent.getTargetId() : null), + null, + (singlePlayerId ? firstEvent.getPlayerId() : null) + ); this.singleTargetId = singleTargetId; this.singleSourceId = singleSourceId; + this.singlePlayerId = singlePlayerId; this.setSourceId(singleSourceId ? firstEvent.getSourceId() : null); if (firstEvent instanceof BatchEvent) { // sanity check, if you need it then think twice and research carefully throw new UnsupportedOperationException("Wrong code usage: nesting batch events not supported"); @@ -42,6 +59,7 @@ public abstract class BatchEvent extends GameEvent { super(eventType, null, null, null); this.singleTargetId = false; this.singleSourceId = false; + this.singlePlayerId = false; } public void addEvent(T event) { @@ -104,8 +122,10 @@ public abstract class BatchEvent extends GameEvent { } @Override // events can store a diff value, so search it from events list instead - @Deprecated // no use case currently supported public UUID getPlayerId() { + if (singlePlayerId) { + return super.getPlayerId(); + } throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list"); } diff --git a/Mage/src/main/java/mage/game/events/GameEvent.java b/Mage/src/main/java/mage/game/events/GameEvent.java index d1f601a648b..f80cd1702a2 100644 --- a/Mage/src/main/java/mage/game/events/GameEvent.java +++ b/Mage/src/main/java/mage/game/events/GameEvent.java @@ -102,13 +102,27 @@ public class GameEvent implements Serializable { */ CLASH, CLASHED, DAMAGE_PLAYER, + /* MILL_CARDS playerId the id of the player milling the card (not the source's controller) targetId the id of the card milled */ MILL_CARDS, + /* MILLED_CARDS + playerId the id of the player milling the card (not the source's controller) + targetId the id of the card milled + */ MILLED_CARD, - MILLED_CARDS, + /* MILLED_CARDS_BATCH_FOR_ONE_PLAYER, + combines all MILLED_CARD events for a player milling card at the same time in a single batch + playerId the id of the player whose batch it is + */ + MILLED_CARDS_BATCH_FOR_ONE_PLAYER, + /* MILLED_CARDS_BATCH_FOR_ALL, + combines all MILLED_CARD events for any player in a single batch + */ + MILLED_CARDS_BATCH_FOR_ALL, + /* DAMAGED_PLAYER targetId the id of the damaged player sourceId sourceId of the ability which caused the damage diff --git a/Mage/src/main/java/mage/game/events/MilledBatchAllEvent.java b/Mage/src/main/java/mage/game/events/MilledBatchAllEvent.java new file mode 100644 index 00000000000..7e828623b35 --- /dev/null +++ b/Mage/src/main/java/mage/game/events/MilledBatchAllEvent.java @@ -0,0 +1,26 @@ +package mage.game.events; + +import mage.cards.Cards; +import mage.cards.CardsImpl; + +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author Susucr + */ +public class MilledBatchAllEvent extends BatchEvent { + + public MilledBatchAllEvent(MilledCardEvent event) { + super(EventType.MILLED_CARDS_BATCH_FOR_ALL, false, false, false, event); + } + + public Cards getCards() { + return new CardsImpl(getEvents() + .stream() + .map(MilledCardEvent::getCard) + .filter(Objects::nonNull) + .collect(Collectors.toSet()) + ); + } +} diff --git a/Mage/src/main/java/mage/game/events/MilledBatchForOnePlayerEvent.java b/Mage/src/main/java/mage/game/events/MilledBatchForOnePlayerEvent.java new file mode 100644 index 00000000000..be40f515563 --- /dev/null +++ b/Mage/src/main/java/mage/game/events/MilledBatchForOnePlayerEvent.java @@ -0,0 +1,26 @@ +package mage.game.events; + +import mage.cards.Cards; +import mage.cards.CardsImpl; + +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * @author Susucr + */ +public class MilledBatchForOnePlayerEvent extends BatchEvent { + + public MilledBatchForOnePlayerEvent(MilledCardEvent event) { + super(EventType.MILLED_CARDS_BATCH_FOR_ONE_PLAYER, false, false, true, event); + } + + public Cards getCards() { + return new CardsImpl(getEvents() + .stream() + .map(MilledCardEvent::getCard) + .filter(Objects::nonNull) + .collect(Collectors.toSet()) + ); + } +} diff --git a/Mage/src/main/java/mage/game/events/MilledCardEvent.java b/Mage/src/main/java/mage/game/events/MilledCardEvent.java new file mode 100644 index 00000000000..f038b66e186 --- /dev/null +++ b/Mage/src/main/java/mage/game/events/MilledCardEvent.java @@ -0,0 +1,26 @@ +package mage.game.events; + +import mage.abilities.Ability; +import mage.cards.Card; + +import java.util.UUID; + +/** + * Event for an individual card being milled. + * Stores the card at the moment it is milled. + * + * @author Susucr + */ +public class MilledCardEvent extends GameEvent { + + private final Card card; + + public MilledCardEvent(Card card, UUID playerId, Ability source) { + super(EventType.MILLED_CARD, card.getId(), source, playerId); + this.card = card; + } + + public Card getCard() { + return card; + } +} diff --git a/Mage/src/main/java/mage/game/events/MilledCardsEvent.java b/Mage/src/main/java/mage/game/events/MilledCardsEvent.java deleted file mode 100644 index 12c837b0469..00000000000 --- a/Mage/src/main/java/mage/game/events/MilledCardsEvent.java +++ /dev/null @@ -1,24 +0,0 @@ -package mage.game.events; - -import mage.abilities.Ability; -import mage.cards.Cards; -import mage.cards.CardsImpl; - -import java.util.UUID; - -/** - * @author TheElk801 - */ -public class MilledCardsEvent extends GameEvent { - - private final Cards cards = new CardsImpl(); - - public MilledCardsEvent(Ability source, UUID playerId, Cards cards) { - super(EventType.MILLED_CARDS, playerId, source, playerId); - this.cards.addAll(cards); - } - - public Cards getCards() { - return cards; - } -} diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 939e8a2ae70..7daffcc8fd0 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -5093,9 +5093,10 @@ public abstract class PlayerImpl implements Player, Serializable { Cards cards = new CardsImpl(this.getLibrary().getTopCards(game, event.getAmount())); this.moveCards(cards, Zone.GRAVEYARD, source, game); for (Card card : cards.getCards(game)) { - game.fireEvent(GameEvent.getEvent(GameEvent.EventType.MILLED_CARD, card.getId(), source, getId())); + MilledCardEvent milledEvent = new MilledCardEvent(card, getId(), source); + game.fireEvent(milledEvent); + game.getState().addSimultaneousMilledCardToBatch(milledEvent, game); } - game.fireEvent(new MilledCardsEvent(source, getId(), cards)); return cards; } diff --git a/Mage/src/main/java/mage/watchers/common/CardsMilledWatcher.java b/Mage/src/main/java/mage/watchers/common/CardsMilledWatcher.java index 9aa2cbc1b57..d9fea38340e 100644 --- a/Mage/src/main/java/mage/watchers/common/CardsMilledWatcher.java +++ b/Mage/src/main/java/mage/watchers/common/CardsMilledWatcher.java @@ -6,6 +6,7 @@ import mage.constants.WatcherScope; import mage.constants.Zone; import mage.game.Game; import mage.game.events.GameEvent; +import mage.game.events.MilledCardEvent; import mage.watchers.Watcher; import java.util.*; @@ -29,7 +30,7 @@ public class CardsMilledWatcher extends Watcher { if (event.getType() != GameEvent.EventType.MILLED_CARD) { return; } - Card card = game.getCard(event.getTargetId()); + Card card = ((MilledCardEvent) event).getCard(); if (card == null) { return; }