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;
}