make batchs for milling cards (per player, all)

Rework and test the couple of existing cards triggering on mill.
This commit is contained in:
Susucre 2024-05-01 20:55:50 +02:00
parent 4edb9ce270
commit 7c3bbed8f3
17 changed files with 452 additions and 107 deletions

View file

@ -2,8 +2,9 @@ package mage.cards.m;
import mage.MageInt; import mage.MageInt;
import mage.abilities.Ability; import mage.abilities.Ability;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.TriggeredAbility;
import mage.abilities.common.EntersBattlefieldTriggeredAbility; import mage.abilities.common.EntersBattlefieldTriggeredAbility;
import mage.abilities.common.OneOrMoreMilledTriggeredAbility;
import mage.abilities.effects.common.DrawCardSourceControllerEffect; import mage.abilities.effects.common.DrawCardSourceControllerEffect;
import mage.abilities.effects.common.counter.AddCountersSourceEffect; import mage.abilities.effects.common.counter.AddCountersSourceEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect;
@ -12,13 +13,10 @@ import mage.cards.CardImpl;
import mage.cards.CardSetInfo; import mage.cards.CardSetInfo;
import mage.constants.CardType; import mage.constants.CardType;
import mage.constants.SubType; import mage.constants.SubType;
import mage.constants.Zone;
import mage.counters.CounterType; import mage.counters.CounterType;
import mage.game.Game; import mage.filter.StaticFilters;
import mage.game.events.GameEvent;
import mage.target.TargetPlayer; import mage.target.TargetPlayer;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
/** /**
@ -43,7 +41,16 @@ public final class MirelurkQueen extends CardImpl {
this.addAbility(ability); 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. // 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) { private MirelurkQueen(final MirelurkQueen card) {
@ -55,36 +62,3 @@ public final class MirelurkQueen extends CardImpl {
return new MirelurkQueen(this); 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);
}
}

View file

@ -1,20 +1,25 @@
package mage.cards.t; package mage.cards.t;
import mage.MageInt; import mage.MageInt;
import mage.abilities.TriggeredAbilityImpl; import mage.abilities.Ability;
import mage.abilities.common.EntersBattlefieldOrAttacksSourceTriggeredAbility; 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.AddCountersPlayersEffect;
import mage.abilities.effects.common.counter.AddCountersTargetEffect; import mage.abilities.effects.common.counter.AddCountersTargetEffect;
import mage.abilities.keyword.FlyingAbility; import mage.abilities.keyword.FlyingAbility;
import mage.cards.CardImpl; import mage.cards.CardImpl;
import mage.cards.CardSetInfo; 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.counters.CounterType;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent;
import mage.game.events.MilledCardsEvent;
import mage.target.common.TargetCreaturePermanent; import mage.target.common.TargetCreaturePermanent;
import mage.target.targetadjustment.TargetAdjuster;
import java.util.UUID; 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. // 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) { 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() { TheWiseMothmanAdjuster(DynamicValue value) {
super(Zone.BATTLEFIELD, new AddCountersTargetEffect(CounterType.P1P1.createInstance()) this.dynamicValue = value;
.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);
} }
@Override @Override
public TheWiseMothmanTriggeredAbility copy() { public void adjustTargets(Ability ability, Game game) {
return new TheWiseMothmanTriggeredAbility(this); int count = dynamicValue.calculate(game, ability, ability.getEffects().get(0));
} ability.getTargets().clear();
if (count > 0) {
@Override ability.addTarget(new TargetCreaturePermanent(0, count));
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;
} }
this.getTargets().clear();
this.getTargets().add(new TargetCreaturePermanent(0, count));
return true;
} }
} }

View file

@ -18,7 +18,7 @@ import mage.constants.Zone;
import mage.filter.StaticFilters; import mage.filter.StaticFilters;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.MilledCardsEvent; import mage.game.events.MilledBatchForOnePlayerEvent;
import mage.game.permanent.token.Horror2Token; import mage.game.permanent.token.Horror2Token;
import mage.target.TargetPlayer; import mage.target.TargetPlayer;
@ -79,11 +79,11 @@ class ZellixSanityFlayerTriggeredAbility extends TriggeredAbilityImpl {
@Override @Override
public boolean checkEventType(GameEvent event, Game game) { 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 @Override
public boolean checkTrigger(GameEvent event, Game game) { 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;
} }
} }

View file

@ -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, "<i>Hive Mind</i>"); // 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);
}
}

View file

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

View file

@ -9,6 +9,7 @@ import mage.constants.Zone;
import mage.filter.FilterCard; import mage.filter.FilterCard;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.MilledCardEvent;
import java.util.UUID; import java.util.UUID;
@ -58,7 +59,7 @@ public class MillTriggeredAbility extends TriggeredAbilityImpl {
default: default:
throw new IllegalArgumentException("Wrong code usage. targetController not yet supported: " + targetController); 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); return card != null && filter.match(card, getControllerId(), this, game);
} }

View file

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

View file

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

View file

@ -901,6 +901,33 @@ public class GameState implements Serializable, Copyable<GameState> {
} }
} }
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) { public void addSimultaneousLifeLossToBatch(LifeLostEvent lifeLossEvent, Game game) {
// Combine multiple life loss events in the single event (batch) // Combine multiple life loss events in the single event (batch)
// see GameEvent.LOST_LIFE_BATCH // see GameEvent.LOST_LIFE_BATCH

View file

@ -17,6 +17,7 @@ public abstract class BatchEvent<T extends GameEvent> extends GameEvent {
private final Set<T> events = new HashSet<>(); private final Set<T> events = new HashSet<>();
private final boolean singleTargetId; private final boolean singleTargetId;
private final boolean singleSourceId; private final boolean singleSourceId;
private final boolean singlePlayerId;
/** /**
* @param eventType specific type of event * @param eventType specific type of event
@ -25,9 +26,25 @@ public abstract class BatchEvent<T extends GameEvent> extends GameEvent {
* @param firstEvent added to initialize the batch (batch is never empty) * @param firstEvent added to initialize the batch (batch is never empty)
*/ */
protected BatchEvent(EventType eventType, boolean singleTargetId, boolean singleSourceId, T firstEvent) { 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.singleTargetId = singleTargetId;
this.singleSourceId = singleSourceId; this.singleSourceId = singleSourceId;
this.singlePlayerId = singlePlayerId;
this.setSourceId(singleSourceId ? firstEvent.getSourceId() : null); this.setSourceId(singleSourceId ? firstEvent.getSourceId() : null);
if (firstEvent instanceof BatchEvent) { // sanity check, if you need it then think twice and research carefully 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"); throw new UnsupportedOperationException("Wrong code usage: nesting batch events not supported");
@ -42,6 +59,7 @@ public abstract class BatchEvent<T extends GameEvent> extends GameEvent {
super(eventType, null, null, null); super(eventType, null, null, null);
this.singleTargetId = false; this.singleTargetId = false;
this.singleSourceId = false; this.singleSourceId = false;
this.singlePlayerId = false;
} }
public void addEvent(T event) { public void addEvent(T event) {
@ -104,8 +122,10 @@ public abstract class BatchEvent<T extends GameEvent> extends GameEvent {
} }
@Override // events can store a diff value, so search it from events list instead @Override // events can store a diff value, so search it from events list instead
@Deprecated // no use case currently supported
public UUID getPlayerId() { public UUID getPlayerId() {
if (singlePlayerId) {
return super.getPlayerId();
}
throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list"); throw new IllegalStateException("Wrong code usage. Must search value from a getEvents list");
} }

View file

@ -102,13 +102,27 @@ public class GameEvent implements Serializable {
*/ */
CLASH, CLASHED, CLASH, CLASHED,
DAMAGE_PLAYER, DAMAGE_PLAYER,
/* MILL_CARDS /* MILL_CARDS
playerId the id of the player milling the card (not the source's controller) playerId the id of the player milling the card (not the source's controller)
targetId the id of the card milled targetId the id of the card milled
*/ */
MILL_CARDS, 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_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 /* DAMAGED_PLAYER
targetId the id of the damaged player targetId the id of the damaged player
sourceId sourceId of the ability which caused the damage sourceId sourceId of the ability which caused the damage

View file

@ -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<MilledCardEvent> {
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())
);
}
}

View file

@ -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<MilledCardEvent> {
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())
);
}
}

View file

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

View file

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

View file

@ -5093,9 +5093,10 @@ public abstract class PlayerImpl implements Player, Serializable {
Cards cards = new CardsImpl(this.getLibrary().getTopCards(game, event.getAmount())); Cards cards = new CardsImpl(this.getLibrary().getTopCards(game, event.getAmount()));
this.moveCards(cards, Zone.GRAVEYARD, source, game); this.moveCards(cards, Zone.GRAVEYARD, source, game);
for (Card card : cards.getCards(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; return cards;
} }

View file

@ -6,6 +6,7 @@ import mage.constants.WatcherScope;
import mage.constants.Zone; import mage.constants.Zone;
import mage.game.Game; import mage.game.Game;
import mage.game.events.GameEvent; import mage.game.events.GameEvent;
import mage.game.events.MilledCardEvent;
import mage.watchers.Watcher; import mage.watchers.Watcher;
import java.util.*; import java.util.*;
@ -29,7 +30,7 @@ public class CardsMilledWatcher extends Watcher {
if (event.getType() != GameEvent.EventType.MILLED_CARD) { if (event.getType() != GameEvent.EventType.MILLED_CARD) {
return; return;
} }
Card card = game.getCard(event.getTargetId()); Card card = ((MilledCardEvent) event).getCard();
if (card == null) { if (card == null) {
return; return;
} }