diff --git a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/TheGitrogMonsterTest.java b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/TheGitrogMonsterTest.java index 6327a43e623..4373220e34e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/TheGitrogMonsterTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/cards/single/soi/TheGitrogMonsterTest.java @@ -1,75 +1,71 @@ package org.mage.test.cards.single.soi; -import java.util.Set; -import mage.cards.Card; import mage.constants.PhaseStep; import mage.constants.Zone; import org.junit.Test; import org.mage.test.serverside.base.CardTestPlayerBase; /** - * 3BG - Legendary Creature - Frog Horror - Deathtouch - - At the beginning of your upkeep, sacrifice The Gitrog Monster unless you sacrifice a land. - - You may play an additional land on each of your turns. - - Whenever one or more land cards are put into your graveyard from anywhere, draw a card. + * 3BG Legendary Creature - Frog Horror Deathtouch + * + * At the beginning of your upkeep, sacrifice The Gitrog Monster unless you + * sacrifice a land. + * + * You may play an additional land on each of your turns. + * + * Whenever one or more land cards are put into your graveyard from anywhere, + * draw a card. * * @author escplan9 (Derek Monturo - dmontur1 at gmail dot com) */ -public class TheGitrogMonsterTest extends CardTestPlayerBase { - +public class TheGitrogMonsterTest extends CardTestPlayerBase { + /** * Basic sacrifice test when no lands are present */ @Test public void noLandsSacrificeGitrog() { - + addCard(Zone.HAND, playerA, "The Gitrog Monster", 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); - + addCard(Zone.HAND, playerB, "Armageddon", 1); // destroy all lands addCard(Zone.BATTLEFIELD, playerB, "Plains", 4); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Gitrog Monster"); castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerB, "Armageddon"); setStopAt(3, PhaseStep.DRAW); execute(); - - Set hand = playerA.getHand().getCards(currentGame); + assertGraveyardCount(playerA, "Swamp", 3); assertGraveyardCount(playerA, "Forest", 2); assertGraveyardCount(playerA, "The Gitrog Monster", 1); assertGraveyardCount(playerB, "Plains", 4); assertGraveyardCount(playerB, "Armageddon", 1); assertPermanentCount(playerA, "The Gitrog Monster", 0); - assertHandCount(playerA, 6); // 1 for turn, 5 more for lands that hit the grave + assertHandCount(playerA, 2); // 1 for turn, 1 more for lands that hit the grave } - + /** * Basic sacrifice test when there is a land */ @Test public void hasLandsSacrificeLand() { - + addCard(Zone.HAND, playerA, "The Gitrog Monster", 1); addCard(Zone.BATTLEFIELD, playerA, "Swamp", 3); addCard(Zone.BATTLEFIELD, playerA, "Forest", 2); - + castSpell(1, PhaseStep.PRECOMBAT_MAIN, playerA, "The Gitrog Monster"); - + // on 3rd turn during upkeep opt to sacrifice a land setChoice(playerA, "Yes"); addTarget(playerA, "Swamp"); setStopAt(3, PhaseStep.DRAW); execute(); - - Set hand = playerA.getHand().getCards(currentGame); + assertGraveyardCount(playerA, "Swamp", 1); assertPermanentCount(playerA, "The Gitrog Monster", 1); assertHandCount(playerA, 2); // 1 for turn, 1 more for land sacrificed diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 752c7ed53fb..acbcb8db002 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -32,8 +32,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import mage.MageObject; import mage.abilities.Abilities; @@ -59,6 +63,8 @@ import mage.game.combat.CombatGroup; import mage.game.command.Command; import mage.game.command.CommandObject; import mage.game.events.GameEvent; +import mage.game.events.ZoneChangeEvent; +import mage.game.events.ZoneChangeGroupEvent; import mage.game.permanent.Battlefield; import mage.game.permanent.Permanent; import mage.game.stack.SpellStack; @@ -655,7 +661,9 @@ public class GameState implements Serializable, Copyable { if (!simultaneousEvents.isEmpty() && !getTurn().isEndTurnRequested()) { // it can happen, that the events add new simultaneous events, so copy the list before List eventsToHandle = new ArrayList<>(); + List eventGroups = createEventGroups(simultaneousEvents, game); eventsToHandle.addAll(simultaneousEvents); + eventsToHandle.addAll(eventGroups); simultaneousEvents.clear(); for (GameEvent event : eventsToHandle) { this.handleEvent(event, game); @@ -684,6 +692,76 @@ public class GameState implements Serializable, Copyable { return effects.replaceEvent(event, game); } + public List createEventGroups(List events, Game game) { + + class ZoneChangeData { + + private final Zone fromZone; + private final Zone toZone; + private final UUID sourceId; + private final UUID playerId; + + public ZoneChangeData(UUID sourceId, UUID playerId, Zone fromZone, Zone toZone) { + this.sourceId = sourceId; + this.playerId = playerId; + this.fromZone = fromZone; + this.toZone = toZone; + } + + @Override + public int hashCode() { + return (this.fromZone.ordinal() + 1) * 1 + + (this.toZone.ordinal() + 1) * 10 + + (this.sourceId != null ? this.sourceId.hashCode() : 0) + + (this.playerId != null ? this.playerId.hashCode() : 0); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof ZoneChangeData) { + ZoneChangeData data = (ZoneChangeData) obj; + return this.fromZone == data.fromZone + && this.toZone == data.toZone + && this.sourceId == data.sourceId + && this.playerId == data.playerId; + } + return false; + } + } + + Map> eventsByKey = new HashMap<>(); + List groupEvents = new LinkedList<>(); + for (GameEvent event : events) { + if (event instanceof ZoneChangeEvent) { + ZoneChangeEvent castEvent = (ZoneChangeEvent) event; + ZoneChangeData key = new ZoneChangeData(castEvent.getSourceId(), castEvent.getPlayerId(), castEvent.getFromZone(), castEvent.getToZone()); + if (eventsByKey.containsKey(key)) { + eventsByKey.get(key).add(event); + } else { + List list = new LinkedList<>(); + list.add(event); + eventsByKey.put(key, list); + } + } + } + for (Map.Entry> entry : eventsByKey.entrySet()) { + Set movedCards = new LinkedHashSet<>(); + for (Iterator it = entry.getValue().iterator(); it.hasNext();) { + GameEvent event = it.next(); + ZoneChangeEvent castEvent = (ZoneChangeEvent) event; + UUID targetId = castEvent.getTargetId(); + Card card = game.getCard(targetId); + movedCards.add(card); + } + ZoneChangeData eventData = entry.getKey(); + if (!movedCards.isEmpty()) { + ZoneChangeGroupEvent event = new ZoneChangeGroupEvent(movedCards, eventData.sourceId, eventData.playerId, eventData.fromZone, eventData.toZone); + groupEvents.add(event); + } + } + return groupEvents; + } + public void addCard(Card card) { setZone(card.getId(), Zone.OUTSIDE); for (Ability ability : card.getAbilities()) { diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index d2b86ab37ac..0039396a8ec 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -123,7 +123,6 @@ import mage.game.events.DamagedPlayerEvent; import mage.game.events.GameEvent; import mage.game.events.GameEvent.EventType; import mage.game.events.ZoneChangeEvent; -import mage.game.events.ZoneChangeGroupEvent; import mage.game.match.MatchPlayer; import mage.game.permanent.Permanent; import mage.game.permanent.PermanentCard; @@ -3249,9 +3248,6 @@ public abstract class PlayerImpl implements Player, Serializable { default: throw new UnsupportedOperationException("to Zone" + toZone.toString() + " not supported yet"); } - if (!successfulMovedCards.isEmpty()) { - game.fireEvent(new ZoneChangeGroupEvent(successfulMovedCards, source == null ? null : source.getSourceId(), this.getId(), fromZone, toZone)); - } return successfulMovedCards.size() > 0; } @@ -3267,7 +3263,6 @@ public abstract class PlayerImpl implements Player, Serializable { if (cards.isEmpty()) { return true; } - game.fireEvent(new ZoneChangeGroupEvent(cards, source == null ? null : source.getSourceId(), this.getId(), null, Zone.EXILED)); boolean result = false; for (Card card : cards) { Zone fromZone = game.getState().getZone(card.getId()); @@ -3368,7 +3363,6 @@ public abstract class PlayerImpl implements Player, Serializable { } } } - game.fireEvent(new ZoneChangeGroupEvent(movedCards, source == null ? null : source.getSourceId(), this.getId(), fromZone, Zone.GRAVEYARD)); return movedCards; }