diff --git a/Mage.Tests/src/test/java/org/mage/test/rollback/ExtraTurnTest.java b/Mage.Tests/src/test/java/org/mage/test/rollback/ExtraTurnTest.java new file mode 100644 index 00000000000..c4d74a7653e --- /dev/null +++ b/Mage.Tests/src/test/java/org/mage/test/rollback/ExtraTurnTest.java @@ -0,0 +1,34 @@ +package org.mage.test.rollback; + +import mage.constants.PhaseStep; +import mage.constants.Zone; +import org.junit.Test; +import org.mage.test.serverside.base.CardTestPlayerBase; + +/** + * + * @author Quercitron + */ +public class ExtraTurnTest extends CardTestPlayerBase { + + // Test that rollback works correctly when extra turn is taken during an opponent turn. + @Test + public void testThatRollbackWorksCorrectlyWithExtraTurn() { + addCard(Zone.BATTLEFIELD, playerA, "Island", 3); + // The next sorcery card you cast this turn can be cast as though it had flash. + addCard(Zone.HAND, playerA, "Quicken"); + // Take an extra turn after this one. + addCard(Zone.HAND, playerA, "Time Walk"); + + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Quicken"); + castSpell(2, PhaseStep.PRECOMBAT_MAIN, playerA, "Time Walk"); + + rollbackTurns(3, PhaseStep.PRECOMBAT_MAIN, playerA, 0); + + setStopAt(4, PhaseStep.PRECOMBAT_MAIN); + execute(); + + assertActivePlayer(playerA); + } + +} diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index eecb0914418..a7bdbb62eaa 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -572,7 +572,6 @@ public abstract class GameImpl implements Game, Serializable { public void saveState(boolean bookmark) { if (!simulation && gameStates != null) { if (bookmark || saveGame) { - state.getPlayerList().setCurrent(playerList.get()); gameStates.save(state); } } @@ -678,7 +677,7 @@ public abstract class GameImpl implements Game, Serializable { GameState restore = gameStates.rollback(stateNum); if (restore != null) { state.restore(restore); - playerList.setCurrent(state.getPlayerList().get()); + playerList.setCurrent(state.getPlayerByOrderId()); } } } @@ -758,6 +757,7 @@ public abstract class GameImpl implements Game, Serializable { if (!isPaused() && !gameOver(null)) { playerList = state.getPlayerList(nextPlayerId); Player playerByOrder = getPlayer(playerList.get()); + state.setPlayerByOrderId(playerByOrder.getId()); while (!isPaused() && !gameOver(null)) { playExtraTurns(); GameEvent event = new GameEvent(GameEvent.EventType.PLAY_TURN, null, null, playerByOrder.getId()); @@ -768,6 +768,7 @@ public abstract class GameImpl implements Game, Serializable { } playExtraTurns(); playerByOrder = playerList.getNext(this); + state.setPlayerByOrderId(playerByOrder.getId()); } } if (gameOver(null) && !isSimulation()) { @@ -2820,7 +2821,7 @@ public abstract class GameImpl implements Game, Serializable { if (restore != null) { informPlayers(GameLog.getPlayerRequestColoredText("Player request: Rolling back to start of turn " + restore.getTurnNum())); state.restoreForRollBack(restore); - playerList.setCurrent(state.getPlayerList().get()); + playerList.setCurrent(state.getPlayerByOrderId()); // because restore uses the objects without copy each copy the state again gameStatesRollBack.put(getTurnNum(), state.copy()); executingRollback = true; diff --git a/Mage/src/main/java/mage/game/GameState.java b/Mage/src/main/java/mage/game/GameState.java index 661b206e934..aee5d4e47b7 100644 --- a/Mage/src/main/java/mage/game/GameState.java +++ b/Mage/src/main/java/mage/game/GameState.java @@ -109,6 +109,7 @@ public class GameState implements Serializable, Copyable { private TurnMods turnMods; private UUID activePlayerId; // playerId which turn it is private UUID priorityPlayerId; // player that has currently priority + private UUID playerByOrderId; // player that has currently priority private SpellStack stack; private Command command; private Exile exile; @@ -162,6 +163,7 @@ public class GameState implements Serializable, Copyable { this.activePlayerId = state.activePlayerId; this.priorityPlayerId = state.priorityPlayerId; + this.playerByOrderId = state.playerByOrderId; this.turn = state.turn.copy(); this.stack = state.stack.copy(); @@ -209,7 +211,8 @@ public class GameState implements Serializable, Copyable { public void restore(GameState state) { this.activePlayerId = state.activePlayerId; - this.playerList.setCurrent(state.playerList.get()); + this.playerList.setCurrent(state.activePlayerId); + this.playerByOrderId = state.playerByOrderId; this.priorityPlayerId = state.priorityPlayerId; this.stack = state.stack; this.command = state.command; @@ -255,7 +258,7 @@ public class GameState implements Serializable, Copyable { StringBuilder sb = threadLocalBuilder.get(); sb.append(turn.getValue(turnNum)); - sb.append(activePlayerId).append(priorityPlayerId); + sb.append(activePlayerId).append(priorityPlayerId).append(playerByOrderId); for (Player player : players.values()) { sb.append("player").append(player.getLife()).append("hand"); @@ -293,7 +296,7 @@ public class GameState implements Serializable, Copyable { StringBuilder sb = threadLocalBuilder.get(); sb.append(turn.getValue(turnNum)); - sb.append(activePlayerId).append(priorityPlayerId); + sb.append(activePlayerId).append(priorityPlayerId).append(playerByOrderId); for (Player player : players.values()) { sb.append("player").append(player.isPassed()).append(player.getLife()).append("hand"); @@ -346,7 +349,7 @@ public class GameState implements Serializable, Copyable { StringBuilder sb = threadLocalBuilder.get(); sb.append(turn.getValue(turnNum)); - sb.append(activePlayerId).append(priorityPlayerId); + sb.append(activePlayerId).append(priorityPlayerId).append(playerByOrderId); for (Player player : players.values()) { sb.append("player").append(player.isPassed()).append(player.getLife()).append("hand"); @@ -411,6 +414,14 @@ public class GameState implements Serializable, Copyable { this.activePlayerId = activePlayerId; } + public UUID getPlayerByOrderId() { + return playerByOrderId; + } + + public void setPlayerByOrderId(UUID playerByOrderId) { + this.playerByOrderId = playerByOrderId; + } + public UUID getPriorityPlayerId() { return priorityPlayerId; }