From 3d3358cd05268be3996bb2ce1b973de5717586be Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Tue, 1 Aug 2023 16:20:12 +0400 Subject: [PATCH] game: turn modification improves: - fixed miss phase changed events and logs in some use cases; - added source info in turn modification logs; - added game logs for take and lost control of the spell (example: Word of Command) - added game logs for skip step; - added game logs for extra step; - added game logs for skip phase; --- .../src/mage/cards/m/MoraugFuryOfAkoum.java | 10 +- .../mage/cards/s/SphinxOfTheSecondSun.java | 10 +- Mage.Sets/src/mage/cards/w/WordOfCommand.java | 12 +- .../java/org/mage/test/player/TestPlayer.java | 4 +- .../java/org/mage/test/stub/PlayerStub.java | 2 +- Mage/src/main/java/mage/game/GameImpl.java | 14 +- Mage/src/main/java/mage/game/stack/Spell.java | 37 +++-- Mage/src/main/java/mage/game/turn/Phase.java | 58 ++++++-- Mage/src/main/java/mage/game/turn/Turn.java | 136 ++++++++++++------ .../src/main/java/mage/game/turn/TurnMod.java | 37 +++-- .../main/java/mage/game/turn/TurnMods.java | 58 ++++---- Mage/src/main/java/mage/players/Player.java | 5 +- .../main/java/mage/players/PlayerImpl.java | 25 ++-- Mage/src/main/java/mage/util/CardUtil.java | 20 +-- 14 files changed, 279 insertions(+), 149 deletions(-) diff --git a/Mage.Sets/src/mage/cards/m/MoraugFuryOfAkoum.java b/Mage.Sets/src/mage/cards/m/MoraugFuryOfAkoum.java index f427a479f81..d02ea14be07 100644 --- a/Mage.Sets/src/mage/cards/m/MoraugFuryOfAkoum.java +++ b/Mage.Sets/src/mage/cards/m/MoraugFuryOfAkoum.java @@ -115,17 +115,17 @@ class MoraugFuryOfAkoumCombatEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { TurnPhase turnPhase = game.getTurnPhaseType(); for (TurnMod turnMod : game.getState().getTurnMods()) { - if ("moraug".equals(turnMod.getNote()) + if ("moraug".equals(turnMod.getTag()) && turnMod.getPlayerId().equals(source.getControllerId()) && turnMod.getAfterPhase() == turnPhase) { turnPhase = TurnPhase.COMBAT; - turnMod.withNote("moraugIgnore"); + turnMod.withTag("moraugIgnore"); break; } } TurnMod combat = new TurnMod(source.getControllerId()) .withExtraPhase(TurnPhase.COMBAT, turnPhase) - .withNote("moraug"); + .withTag("moraug"); game.getState().getTurnMods().add(combat); game.addDelayedTriggeredAbility(new MoraugFuryOfAkoumDelayedTriggeredAbility(combat.getId()), source); return true; @@ -193,8 +193,8 @@ class MoraugFuryOfAkoumWatcher extends Watcher { return; } for (TurnMod turnMod : game.getState().getTurnMods()) { - if ("moraug".equals(turnMod.getNote())) { - turnMod.withNote("moraugIgnore"); + if ("moraug".equals(turnMod.getTag())) { + turnMod.withTag("moraugIgnore"); } } } diff --git a/Mage.Sets/src/mage/cards/s/SphinxOfTheSecondSun.java b/Mage.Sets/src/mage/cards/s/SphinxOfTheSecondSun.java index cfef3c17617..16ed483a24f 100644 --- a/Mage.Sets/src/mage/cards/s/SphinxOfTheSecondSun.java +++ b/Mage.Sets/src/mage/cards/s/SphinxOfTheSecondSun.java @@ -66,17 +66,17 @@ class SphinxOfTheSecondSunEffect extends OneShotEffect { public boolean apply(Game game, Ability source) { TurnPhase turnPhase = game.getTurnPhaseType(); for (TurnMod turnMod : game.getState().getTurnMods()) { - if ("sphinxSecondSun".equals(turnMod.getNote()) + if ("sphinxSecondSun".equals(turnMod.getTag()) && turnMod.getPlayerId().equals(source.getControllerId()) && turnMod.getAfterPhase() == turnPhase) { turnPhase = TurnPhase.BEGINNING; - turnMod.withNote("sphinxSecondSunIgnore"); + turnMod.withTag("sphinxSecondSunIgnore"); break; } } TurnMod newPhase = new TurnMod(source.getControllerId()) .withExtraPhase(TurnPhase.BEGINNING, turnPhase) - .withNote("sphinxSecondSun"); + .withTag("sphinxSecondSun"); game.getState().getTurnMods().add(newPhase); return true; } @@ -94,8 +94,8 @@ class SphinxOfTheSecondSunWatcher extends Watcher { return; } for (TurnMod turnMod : game.getState().getTurnMods()) { - if ("sphinxSecondSun".equals(turnMod.getNote())) { - turnMod.withNote("sphinxSecondSunIgnore"); + if ("sphinxSecondSun".equals(turnMod.getTag())) { + turnMod.withTag("sphinxSecondSunIgnore"); } } } diff --git a/Mage.Sets/src/mage/cards/w/WordOfCommand.java b/Mage.Sets/src/mage/cards/w/WordOfCommand.java index 5f0e2c62e88..c3cf4dabc22 100644 --- a/Mage.Sets/src/mage/cards/w/WordOfCommand.java +++ b/Mage.Sets/src/mage/cards/w/WordOfCommand.java @@ -80,8 +80,8 @@ class WordOfCommandEffect extends OneShotEffect { Player controller = null; Spell wordOfCommand = game.getSpell(source.getSourceId()); if (wordOfCommand != null) { - if (wordOfCommand.getCommandedBy() != null) { - controller = game.getPlayer(wordOfCommand.getCommandedBy()); + if (wordOfCommand.getCommandedByPlayerId() != null) { + controller = game.getPlayer(wordOfCommand.getCommandedByPlayerId()); } else { controller = game.getPlayer(sourceController.getTurnControlledBy()); } @@ -97,7 +97,7 @@ class WordOfCommandEffect extends OneShotEffect { } // You control that player until Word of Command finishes resolving - CardUtil.takeControlUnderPlayerStart(game, controller, targetPlayer, true); + CardUtil.takeControlUnderPlayerStart(game, source, controller, targetPlayer, true); // The player plays that card if able if (card != null) { @@ -144,15 +144,15 @@ class WordOfCommandEffect extends OneShotEffect { game.getContinuousEffects().removeInactiveEffects(game); Spell spell = game.getSpell(card.getId()); if (spell != null) { - spell.setCommandedBy(controller.getId()); // If the chosen card is cast as a spell, you control the player while that spell is resolving + spell.setCommandedBy(controller.getId(), CardUtil.getSourceLogName(game, source)); // If the chosen card is cast as a spell, you control the player while that spell is resolving } } wordOfCommand = game.getSpell(source.getSourceId()); if (wordOfCommand != null) { - wordOfCommand.setCommandedBy(controller.getId()); // You control the player until Word of Command finishes resolving + wordOfCommand.setCommandedBy(controller.getId(), CardUtil.getSourceLogName(game, source)); // You control the player until Word of Command finishes resolving } else { - CardUtil.takeControlUnderPlayerEnd(game, controller, targetPlayer); + CardUtil.takeControlUnderPlayerEnd(game, source, controller, targetPlayer); } return true; } diff --git a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java index 5a5357206d5..09185b14f26 100644 --- a/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java +++ b/Mage.Tests/src/test/java/org/mage/test/player/TestPlayer.java @@ -2962,8 +2962,8 @@ public class TestPlayer implements Player { } @Override - public void controlPlayersTurn(Game game, UUID playerId) { - computerPlayer.controlPlayersTurn(game, playerId); + public void controlPlayersTurn(Game game, UUID playerUnderControlId, String info) { + computerPlayer.controlPlayersTurn(game, playerUnderControlId, info); } @Override diff --git a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java index 52b0b6b62ef..9120dd7023e 100644 --- a/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java +++ b/Mage.Tests/src/test/java/org/mage/test/stub/PlayerStub.java @@ -465,7 +465,7 @@ public class PlayerStub implements Player { } @Override - public void controlPlayersTurn(Game game, UUID playerId) { + public void controlPlayersTurn(Game game, UUID playerUnderControlId, String info) { } diff --git a/Mage/src/main/java/mage/game/GameImpl.java b/Mage/src/main/java/mage/game/GameImpl.java index a2c00a7e012..c23875e440d 100644 --- a/Mage/src/main/java/mage/game/GameImpl.java +++ b/Mage/src/main/java/mage/game/GameImpl.java @@ -1049,7 +1049,10 @@ public abstract class GameImpl implements Game { Player extraPlayer = this.getPlayer(extraTurn.getPlayerId()); if (extraPlayer != null && extraPlayer.canRespond()) { state.setExtraTurnId(extraTurn.getId()); - informPlayers(extraPlayer.getLogName() + " takes an extra turn"); + informPlayers(String.format("%s takes an extra turn%s", + extraPlayer.getLogName(), + extraTurn.getInfo() + )); if (!playTurn(extraPlayer)) { return false; } @@ -1066,7 +1069,8 @@ public abstract class GameImpl implements Game { private TurnMod useNextExtraTurn() { boolean checkForExtraTurn = true; while (checkForExtraTurn) { - TurnMod extraTurn = getState().getTurnMods().getNextExtraTurn(); + // user's logs generated in parent method + TurnMod extraTurn = getState().getTurnMods().useNextExtraTurn(); if (extraTurn != null) { GameEvent event = new GameEvent(GameEvent.EventType.EXTRA_TURN, extraTurn.getId(), null, extraTurn.getPlayerId()); if (!replaceEvent(event)) { @@ -1715,8 +1719,8 @@ public abstract class GameImpl implements Game { // for Word of Command Spell spell = getSpellOrLKIStack(topId); if (spell != null) { - if (spell.getCommandedBy() != null) { - UUID commandedBy = spell.getCommandedBy(); + if (spell.getCommandedByPlayerId() != null) { + UUID commandedBy = spell.getCommandedByPlayerId(); UUID spellControllerId; if (commandedBy.equals(spell.getControllerId())) { spellControllerId = spell.getSpellAbility().getFirstTarget(); // i.e. resolved spell is Word of Command @@ -1736,7 +1740,7 @@ public abstract class GameImpl implements Game { } } } - spell.setCommandedBy(null); + spell.setCommandedBy(null, null); } } } diff --git a/Mage/src/main/java/mage/game/stack/Spell.java b/Mage/src/main/java/mage/game/stack/Spell.java index 6d8314f40bf..327064770c3 100644 --- a/Mage/src/main/java/mage/game/stack/Spell.java +++ b/Mage/src/main/java/mage/game/stack/Spell.java @@ -61,7 +61,8 @@ public class Spell extends StackObjectImpl implements Card { private boolean faceDown; private boolean countered; private boolean resolving = false; - private UUID commandedBy = null; // for Word of Command + private UUID commandedByPlayerId = null; // controller of the spell resolve, example: Word of Command + private String commandedByInfo; // info about spell commanded, e.g. source private int startingLoyalty; private int startingDefense; @@ -132,7 +133,8 @@ public class Spell extends StackObjectImpl implements Card { this.faceDown = spell.faceDown; this.countered = spell.countered; this.resolving = spell.resolving; - this.commandedBy = spell.commandedBy; + this.commandedByPlayerId = spell.commandedByPlayerId; + this.commandedByInfo = spell.commandedByInfo; this.currentActivatingManaAbilitiesStep = spell.currentActivatingManaAbilitiesStep; this.targetChanged = spell.targetChanged; @@ -240,12 +242,16 @@ public class Spell extends StackObjectImpl implements Card { return false; } this.resolving = true; - if (commandedBy != null && !commandedBy.equals(getControllerId())) { - Player turnController = game.getPlayer(commandedBy); - if (turnController != null) { - turnController.controlPlayersTurn(game, controller.getId()); + + // setup new turn controller for spell's resolve, example: Word of Command + // original controller will be reset after spell's resolve + if (commandedByPlayerId != null && !commandedByPlayerId.equals(getControllerId())) { + Player newTurnController = game.getPlayer(commandedByPlayerId); + if (newTurnController != null) { + newTurnController.controlPlayersTurn(game, controller.getId(), commandedByInfo); } } + if (this.isInstantOrSorcery(game)) { int index = 0; result = false; @@ -1122,12 +1128,23 @@ public class Spell extends StackObjectImpl implements Card { throw new UnsupportedOperationException("Not supported."); //To change body of generated methods, choose Tools | Templates. } - public void setCommandedBy(UUID playerId) { - this.commandedBy = playerId; + /** + * Add temporary turn controller while resolving (e.g. all choices will be made by another player) + * Example: Word of Command + * @param newTurnControllerId + * @param info additional info for game logs + */ + public void setCommandedBy(UUID newTurnControllerId, String info) { + this.commandedByPlayerId = newTurnControllerId; + this.commandedByInfo = info; } - public UUID getCommandedBy() { - return commandedBy; + public UUID getCommandedByPlayerId() { + return commandedByPlayerId; + } + + public String getCommandedByInfo() { + return commandedByInfo == null ? "" : commandedByInfo; } @Override diff --git a/Mage/src/main/java/mage/game/turn/Phase.java b/Mage/src/main/java/mage/game/turn/Phase.java index 823b5f2cf92..53b7f38928d 100644 --- a/Mage/src/main/java/mage/game/turn/Phase.java +++ b/Mage/src/main/java/mage/game/turn/Phase.java @@ -1,19 +1,19 @@ - package mage.game.turn; +import mage.constants.PhaseStep; +import mage.constants.TurnPhase; +import mage.game.Game; +import mage.game.events.GameEvent; +import mage.game.events.GameEvent.EventType; +import mage.players.Player; + import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.UUID; -import mage.constants.PhaseStep; -import mage.constants.TurnPhase; -import mage.game.Game; -import mage.game.events.GameEvent; -import mage.game.events.GameEvent.EventType; /** - * * @author BetaSteward_at_googlemail.com */ public abstract class Phase implements Serializable { @@ -81,15 +81,25 @@ public abstract class Phase implements Serializable { if (game.isPaused() || game.checkIfGameIsOver()) { return false; } - if (game.getTurn().isEndTurnRequested() && step.getType()!=PhaseStep.CLEANUP) { + if (game.getTurn().isEndTurnRequested() && step.getType() != PhaseStep.CLEANUP) { continue; } currentStep = step; - if (!game.getState().getTurnMods().skipStep(activePlayerId, getStep().getType())) { + TurnMod skipStepMod = game.getState().getTurnMods().useNextSkipStep(activePlayerId, getStep().getType()); + if (skipStepMod == null) { playStep(game); if (game.executingRollback()) { return true; } + } else { + Player player = game.getPlayer(skipStepMod.getPlayerId()); + if (player != null) { + game.informPlayers(String.format("%s skips %s step%s", + player.getLogName(), + skipStepMod.getSkipStep().toString(), + skipStepMod.getInfo() + )); + } } if (!game.isSimulation() && checkStopOnStepOption(game)) { return false; @@ -135,11 +145,21 @@ public abstract class Phase implements Serializable { return false; } currentStep = step; - if (!game.getState().getTurnMods().skipStep(activePlayerId, currentStep.getType())) { + TurnMod skipStepMod = game.getState().getTurnMods().useNextSkipStep(activePlayerId, currentStep.getType()); + if (skipStepMod == null) { playStep(game); if (game.executingRollback()) { return true; } + } else { + Player player = game.getPlayer(skipStepMod.getPlayerId()); + if (player != null) { + game.informPlayers(String.format("%s skips %s step%s", + player.getLogName(), + skipStepMod.getSkipStep().toString(), + skipStepMod.getInfo() + )); + } } } @@ -219,12 +239,22 @@ public abstract class Phase implements Serializable { private void playExtraSteps(Game game, PhaseStep afterStep) { while (true) { - Step extraStep = game.getState().getTurnMods().extraStep(activePlayerId, afterStep); - if (extraStep == null) { + TurnMod extraStepMod = game.getState().getTurnMods().useNextExtraStep(activePlayerId, afterStep); + if (extraStepMod == null) { + return; + } + currentStep = extraStepMod.getExtraStep(); + Player player = game.getPlayer(extraStepMod.getPlayerId()); + if (player != null && player.canRespond()) { + game.informPlayers(String.format("%s takes an extra %s step%s", + player.getLogName(), + extraStepMod.getExtraStep().toString(), + extraStepMod.getInfo() + )); + playStep(game); + } else { return; } - currentStep = extraStep; - playStep(game); } } diff --git a/Mage/src/main/java/mage/game/turn/Turn.java b/Mage/src/main/java/mage/game/turn/Turn.java index 3355a740646..01ffc5134a7 100644 --- a/Mage/src/main/java/mage/game/turn/Turn.java +++ b/Mage/src/main/java/mage/game/turn/Turn.java @@ -99,9 +99,12 @@ public class Turn implements Serializable { return false; } - - if (game.getState().getTurnMods().skipTurn(activePlayer.getId())) { - game.informPlayers(activePlayer.getLogName() + " skips their turn."); + TurnMod skipTurnMod = game.getState().getTurnMods().useNextSkipTurn(activePlayer.getId()); + if (skipTurnMod != null) { + game.informPlayers(String.format("%s skips their turn%s", + activePlayer.getLogName(), + skipTurnMod.getInfo() + )); return true; } logStartOfTurn(game, activePlayer); @@ -119,15 +122,25 @@ public class Turn implements Serializable { continue; } currentPhase = phase; + + TurnMod skipPhaseMod = game.getState().getTurnMods().useNextSkipPhase(activePlayer.getId(), currentPhase.getType()); + if (skipPhaseMod != null) { + game.informPlayers(String.format("%s skips %s phase%s", + activePlayer.getLogName(), + currentPhase.getType(), + skipPhaseMod.getInfo() + )); + continue; + } + game.fireEvent(new PhaseChangedEvent(activePlayer.getId(), null)); - if (game.getState().getTurnMods().skipPhase( - activePlayer.getId(), currentPhase.getType() - ) || !phase.play(game, activePlayer.getId())) { + if (!phase.play(game, activePlayer.getId())) { continue; } if (game.executingRollback()) { return false; } + //20091005 - 500.4/703.4n game.emptyManaPools(null); game.saveState(false); @@ -140,39 +153,66 @@ public class Turn implements Serializable { public void resumePlay(Game game, boolean wasPaused) { activePlayerId = game.getActivePlayerId(); + Player activePlayer = game.getPlayer(activePlayerId); UUID priorityPlayerId = game.getPriorityPlayerId(); - TurnPhase phaseType = game.getTurnPhaseType(); - PhaseStep stepType = game.getTurnStepType(); + TurnPhase needPhaseType = game.getTurnPhaseType(); + PhaseStep needStepType = game.getTurnStepType(); Iterator it = phases.iterator(); - Phase phase; + Phase nextPhase; do { - phase = it.next(); - currentPhase = phase; - } while (phase.type != phaseType); - if (phase.resumePlay(game, stepType, wasPaused)) { - //20091005 - 500.4/703.4n - game.emptyManaPools(null); - //game.saveState(); - //20091005 - 500.8 - playExtraPhases(game, phase.getType()); - } - while (it.hasNext()) { - phase = it.next(); + nextPhase = it.next(); + } while (nextPhase.type != needPhaseType); + + // play first phase + TurnMod skipPhaseMod = game.getState().getTurnMods().useNextSkipPhase(activePlayerId, nextPhase.getType()); + if (skipPhaseMod != null && activePlayer != null) { + game.informPlayers(String.format("%s skips %s phase%s", + activePlayer.getLogName(), + nextPhase.getType(), + skipPhaseMod.getInfo() + )); + } else { if (game.isPaused() || game.checkIfGameIsOver()) { return; } - currentPhase = phase; - if (!game.getState().getTurnMods().skipPhase(activePlayerId, currentPhase.getType())) { - if (phase.play(game, activePlayerId)) { + currentPhase = nextPhase; + game.fireEvent(new PhaseChangedEvent(activePlayerId, null)); + if (nextPhase.resumePlay(game, needStepType, wasPaused)) { + //20091005 - 500.4/703.4n + game.emptyManaPools(null); + //20091005 - 500.8 + playExtraPhases(game, nextPhase.getType()); + } + } + + // play all other phases + while (it.hasNext()) { + nextPhase = it.next(); + if (game.isPaused() || game.checkIfGameIsOver()) { + return; + } + skipPhaseMod = game.getState().getTurnMods().useNextSkipPhase(activePlayerId, nextPhase.getType()); + if (skipPhaseMod != null && activePlayer != null) { + game.informPlayers(String.format("%s skips %s phase%s", + activePlayer.getLogName(), + nextPhase.getType(), + skipPhaseMod.getInfo() + )); + } else { + currentPhase = nextPhase; + game.fireEvent(new PhaseChangedEvent(activePlayerId, null)); + if (nextPhase.play(game, activePlayerId)) { //20091005 - 500.4/703.4n game.emptyManaPools(null); - //game.saveState(); //20091005 - 500.8 - playExtraPhases(game, phase.getType()); + playExtraPhases(game, nextPhase.getType()); } } - if (!currentPhase.equals(phase)) { // phase was changed from the card + + // TODO: old code, can't find any usage of turn's phase change by events/cards + // so it must be research and removed as outdated (maybe rollback or playExtraPhases related?) + if (!currentPhase.equals(nextPhase)) { // phase was changed from the card game.fireEvent(new PhaseChangedEvent(activePlayerId, null)); break; } @@ -180,9 +220,10 @@ public class Turn implements Serializable { } private void checkTurnIsControlledByOtherPlayer(Game game, UUID activePlayerId) { - UUID newControllerId = game.getState().getTurnMods().controlsTurn(activePlayerId); - if (newControllerId != null && !newControllerId.equals(activePlayerId)) { - game.getPlayer(newControllerId).controlPlayersTurn(game, activePlayerId); + TurnMod newControllerMod = game.getState().getTurnMods().useNextNewController(activePlayerId); + if (newControllerMod != null && !newControllerMod.getNewControllerId().equals(activePlayerId)) { + // game logs added in child's call (controlPlayersTurn) + game.getPlayer(newControllerMod.getNewControllerId()).controlPlayersTurn(game, activePlayerId, newControllerMod.getInfo()); } } @@ -194,13 +235,13 @@ public class Turn implements Serializable { private boolean playExtraPhases(Game game, TurnPhase afterPhase) { while (true) { - TurnMod extraPhaseTurnMod = game.getState().getTurnMods().extraPhase(activePlayerId, afterPhase); - if (extraPhaseTurnMod == null) { + TurnMod extraPhaseMod = game.getState().getTurnMods().useNextExtraPhase(activePlayerId, afterPhase); + if (extraPhaseMod == null) { return false; } - TurnPhase extraPhase = extraPhaseTurnMod.getExtraPhase(); + TurnPhase extraPhase = extraPhaseMod.getExtraPhase(); if (extraPhase == null) { - return false; + throw new IllegalStateException("Wrong code usage: miss data in turn mod's extra phase - " + extraPhaseMod.getInfo()); } Phase phase; switch (extraPhase) { @@ -216,26 +257,33 @@ public class Turn implements Serializable { case POSTCOMBAT_MAIN: phase = new PostCombatMainPhase(); break; - default: + case END: phase = new EndPhase(); + break; + default: + throw new IllegalArgumentException("Unknown phase type: " + extraPhase); } currentPhase = phase; - game.fireEvent(new PhaseChangedEvent(activePlayerId, extraPhaseTurnMod)); + game.fireEvent(new PhaseChangedEvent(activePlayerId, extraPhaseMod)); Player activePlayer = game.getPlayer(activePlayerId); - if (activePlayer != null && !game.isSimulation()) { - game.informPlayers(activePlayer.getLogName() + " starts an additional " + phase.getType().toString() + " phase"); + if (activePlayer != null) { + game.informPlayers(String.format("%s starts an additional %s phase%s", + activePlayer.getLogName(), + phase.getType().toString(), + extraPhaseMod.getInfo() + )); } phase.play(game, activePlayerId); + + // TODO: is it lost extra phase on multiple phases here? + // example: + // - mods contains 2 mods for same main phases + // - one played and afterPhase take main phase value + // - so it can't find a second mod afterPhase = extraPhase; } } - /*protected void playExtraTurns(Game game) { - while (game.getState().getTurnMods().extraTurn(activePlayerId)) { - this.play(game, activePlayerId); - } - }*/ - /** * Used for some spells with end turn effect (e.g. Time Stop). * diff --git a/Mage/src/main/java/mage/game/turn/TurnMod.java b/Mage/src/main/java/mage/game/turn/TurnMod.java index 2b8ce7f6462..62b30a1ab03 100644 --- a/Mage/src/main/java/mage/game/turn/TurnMod.java +++ b/Mage/src/main/java/mage/game/turn/TurnMod.java @@ -1,7 +1,10 @@ package mage.game.turn; +import mage.abilities.Ability; import mage.constants.PhaseStep; import mage.constants.TurnPhase; +import mage.game.Game; +import mage.util.CardUtil; import mage.util.Copyable; import java.io.Serializable; @@ -11,12 +14,13 @@ import java.util.UUID; * Creates a signle turn modification for turn, phase or step *

* For one time usage only + * For current turn only *

* If you need it in continuous effect then use ContinuousRuleModifyingEffectImpl * with game events like UNTAP_STEP (example: Sands of Time) *

* Supports: - * - new controller + * - new turn controller * - turn: extra and skip * - phase: extra and skip * - step: extra and skip @@ -43,7 +47,8 @@ public class TurnMod implements Serializable, Copyable { private PhaseStep afterStep; private boolean locked = false; // locked for modification, used for wrong code usage protection - private String note; + private String tag; // for inner usage like enable/disable mod in effects + private String info; // for GUI usage like additional info in logs // Turn mod that should be applied after current turn mod // Implemented only for new controller turn mod @@ -66,7 +71,8 @@ public class TurnMod implements Serializable, Copyable { if (mod.subsequentTurnMod != null) { this.subsequentTurnMod = mod.subsequentTurnMod.copy(); } - this.note = mod.note; + this.tag = mod.tag; + this.info = mod.info; this.locked = mod.locked; } @@ -144,11 +150,24 @@ public class TurnMod implements Serializable, Copyable { return this; } - public TurnMod withNote(String note) { - this.note = note; + public TurnMod withTag(String tag) { + this.tag = tag; return this; } + public String getTag() { + return tag; + } + + public TurnMod withInfo(String info) { + this.info = info; + return this; + } + + public String getInfo() { + return info == null ? "" : info; + } + public UUID getPlayerId() { return playerId; } @@ -197,11 +216,11 @@ public class TurnMod implements Serializable, Copyable { return subsequentTurnMod; } - public String getNote() { - return note; - } - public boolean isLocked() { return locked; } + + private void addSourceAsInfo(Game game, Ability source) { + this.info = CardUtil.getSourceLogName(game, source); + } } \ No newline at end of file diff --git a/Mage/src/main/java/mage/game/turn/TurnMods.java b/Mage/src/main/java/mage/game/turn/TurnMods.java index 552c95d4d32..c127110ced2 100644 --- a/Mage/src/main/java/mage/game/turn/TurnMods.java +++ b/Mage/src/main/java/mage/game/turn/TurnMods.java @@ -1,12 +1,13 @@ package mage.game.turn; +import mage.constants.PhaseStep; +import mage.constants.TurnPhase; +import mage.util.Copyable; + import java.io.Serializable; import java.util.ArrayList; import java.util.ListIterator; import java.util.UUID; -import mage.constants.PhaseStep; -import mage.constants.TurnPhase; -import mage.util.Copyable; /** * Turn, phase and step modification for extra/skip (use it for one time mod only) @@ -36,7 +37,7 @@ public class TurnMods extends ArrayList implements Serializable, Copyab return super.add(turnMod); } - public TurnMod getNextExtraTurn() { + public TurnMod useNextExtraTurn() { ListIterator it = this.listIterator(this.size()); while (it.hasPrevious()) { TurnMod turnMod = it.previous(); @@ -48,29 +49,32 @@ public class TurnMods extends ArrayList implements Serializable, Copyab return null; } - public boolean skipTurn(UUID playerId) { + public TurnMod useNextSkipTurn(UUID playerId) { ListIterator it = this.listIterator(this.size()); while (it.hasPrevious()) { TurnMod turnMod = it.previous(); if (turnMod.isSkipTurn() && turnMod.getPlayerId().equals(playerId)) { it.remove(); - return true; + return turnMod; } } - return false; + return null; } - public UUID controlsTurn(UUID playerId) { + public TurnMod useNextNewController(UUID playerId) { + TurnMod lastNewControllerMod = null; + + // find last/actual mod ListIterator it = this.listIterator(this.size()); - TurnMod controlPlayerTurnMod = null; while (it.hasPrevious()) { TurnMod turnMod = it.previous(); if (turnMod.getNewControllerId() != null && turnMod.getPlayerId().equals(playerId)) { - controlPlayerTurnMod = turnMod; + lastNewControllerMod = turnMod; it.remove(); } } - // now delete all other effects that control current active player - control next turn of player effects are not cumulative + + // delete all other outdated mods it = this.listIterator(this.size()); while (it.hasPrevious()) { TurnMod turnMod = it.previous(); @@ -78,26 +82,28 @@ public class TurnMods extends ArrayList implements Serializable, Copyab it.remove(); } } - // apply subsequent turn mod - if (controlPlayerTurnMod != null && controlPlayerTurnMod.getSubsequentTurnMod() != null) { - this.add(controlPlayerTurnMod.getSubsequentTurnMod()); + + // add subsequent turn mod to execute after current + if (lastNewControllerMod != null && lastNewControllerMod.getSubsequentTurnMod() != null) { + this.add(lastNewControllerMod.getSubsequentTurnMod()); } - return controlPlayerTurnMod != null ? controlPlayerTurnMod.getNewControllerId() : null; + + return lastNewControllerMod; } - public Step extraStep(UUID playerId, PhaseStep afterStep) { + public TurnMod useNextExtraStep(UUID playerId, PhaseStep afterStep) { ListIterator it = this.listIterator(this.size()); while (it.hasPrevious()) { TurnMod turnMod = it.previous(); if (turnMod.getExtraStep() != null && turnMod.getPlayerId().equals(playerId) && (turnMod.getAfterStep() == null || turnMod.getAfterStep() == afterStep)) { it.remove(); - return turnMod.getExtraStep(); + return turnMod; } } return null; } - public boolean skipStep(UUID playerId, PhaseStep step) { + public TurnMod useNextSkipStep(UUID playerId, PhaseStep step) { if (step != null) { ListIterator it = this.listIterator(this.size()); while (it.hasPrevious()) { @@ -106,20 +112,22 @@ public class TurnMods extends ArrayList implements Serializable, Copyab if (turnMod.getPlayerId() != null && turnMod.getPlayerId().equals(playerId)) { if (turnMod.getSkipStep() == step) { it.remove(); - return true; + return turnMod; } } } } } - return false; + return null; } - public TurnMod extraPhase(UUID playerId, TurnPhase afterPhase) { + public TurnMod useNextExtraPhase(UUID playerId, TurnPhase afterPhase) { ListIterator it = this.listIterator(this.size()); while (it.hasPrevious()) { TurnMod turnMod = it.previous(); - if (turnMod.getExtraPhase() != null && turnMod.getPlayerId().equals(playerId) && (turnMod.getAfterPhase() == null || turnMod.getAfterPhase() == afterPhase)) { + if (turnMod.getExtraPhase() != null + && turnMod.getPlayerId().equals(playerId) + && (turnMod.getAfterPhase() == null || turnMod.getAfterPhase() == afterPhase)) { it.remove(); return turnMod; } @@ -127,15 +135,15 @@ public class TurnMods extends ArrayList implements Serializable, Copyab return null; } - public boolean skipPhase(UUID playerId, TurnPhase phase) { + public TurnMod useNextSkipPhase(UUID playerId, TurnPhase phase) { ListIterator it = this.listIterator(this.size()); while (it.hasPrevious()) { TurnMod turnMod = it.previous(); if (turnMod.getSkipPhase() != null && turnMod.getPlayerId().equals(playerId) && turnMod.getSkipPhase() == phase) { it.remove(); - return true; + return turnMod; } } - return false; + return null; } } diff --git a/Mage/src/main/java/mage/players/Player.java b/Mage/src/main/java/mage/players/Player.java index da497976b74..d3faa9c7a4b 100644 --- a/Mage/src/main/java/mage/players/Player.java +++ b/Mage/src/main/java/mage/players/Player.java @@ -321,9 +321,10 @@ public interface Player extends MageItem, Copyable { * Defines player whose turn this player controls at the moment. * * @param game - * @param playerId + * @param playerUnderControlId + * @param info additional info to show in game logs like source */ - void controlPlayersTurn(Game game, UUID playerId); + void controlPlayersTurn(Game game, UUID playerUnderControlId, String info); /** * Sets player {@link UUID} who controls this player's turn. diff --git a/Mage/src/main/java/mage/players/PlayerImpl.java b/Mage/src/main/java/mage/players/PlayerImpl.java index 5454d412472..1dc56cc0338 100644 --- a/Mage/src/main/java/mage/players/PlayerImpl.java +++ b/Mage/src/main/java/mage/players/PlayerImpl.java @@ -547,17 +547,17 @@ public abstract class PlayerImpl implements Player, Serializable { } @Override - public void controlPlayersTurn(Game game, UUID playerId) { - Player player = game.getPlayer(playerId); - player.setTurnControlledBy(this.getId()); - game.informPlayers(getLogName() + " controls the turn of " + player.getLogName()); - if (!playerId.equals(this.getId())) { - this.playersUnderYourControl.add(playerId); - if (!player.hasLeft() && !player.hasLost()) { - player.setGameUnderYourControl(false); + public void controlPlayersTurn(Game game, UUID playerUnderControlId, String info) { + Player playerUnderControl = game.getPlayer(playerUnderControlId); + playerUnderControl.setTurnControlledBy(this.getId()); + game.informPlayers(getLogName() + " taken turn control of " + playerUnderControl.getLogName() + info); + if (!playerUnderControlId.equals(this.getId())) { + this.playersUnderYourControl.add(playerUnderControlId); + if (!playerUnderControl.hasLeft() && !playerUnderControl.hasLost()) { + playerUnderControl.setGameUnderYourControl(false); } DelayedTriggeredAbility ability = new AtTheEndOfTurnStepPostDelayedTriggeredAbility( - new LoseControlOnOtherPlayersControllerEffect(this.getLogName(), player.getLogName())); + new LoseControlOnOtherPlayersControllerEffect(this.getLogName(), playerUnderControl.getLogName())); ability.setSourceId(getId()); ability.setControllerId(getId()); game.addDelayedTriggeredAbility(ability, null); @@ -2666,7 +2666,8 @@ public abstract class PlayerImpl implements Player, Serializable { // P.S. no needs in searchingController, but it helps with unit tests, see TakeControlWhileSearchingLibraryTest boolean takeControl = false; if (!searchingPlayer.getId().equals(searchingController.getId())) { - CardUtil.takeControlUnderPlayerStart(game, searchingController, searchingPlayer, true); + // game logs added in child's call + CardUtil.takeControlUnderPlayerStart(game, source, searchingController, searchingPlayer, true); takeControl = true; } @@ -2711,8 +2712,8 @@ public abstract class PlayerImpl implements Player, Serializable { // END SEARCH if (takeControl) { - CardUtil.takeControlUnderPlayerEnd(game, searchingController, searchingPlayer); - game.informPlayers("Control of " + searchingPlayer.getLogName() + " is back" + CardUtil.getSourceLogName(game, source)); + // game logs added in child's call + CardUtil.takeControlUnderPlayerEnd(game, source, searchingController, searchingPlayer); } LibrarySearchedEvent searchedEvent = new LibrarySearchedEvent(targetPlayer.getId(), source, searchingPlayer.getId(), target); diff --git a/Mage/src/main/java/mage/util/CardUtil.java b/Mage/src/main/java/mage/util/CardUtil.java index ab3e781e743..0652a9baab4 100644 --- a/Mage/src/main/java/mage/util/CardUtil.java +++ b/Mage/src/main/java/mage/util/CardUtil.java @@ -1157,14 +1157,15 @@ public final class CardUtil { * * @param game * @param controller - * @param targetPlayer + * @param playerUnderControl * @param givePauseForResponse if you want to give controller time to watch opponent's hand (if you remove control effect in the end of code) */ - public static void takeControlUnderPlayerStart(Game game, Player controller, Player targetPlayer, boolean givePauseForResponse) { - controller.controlPlayersTurn(game, targetPlayer.getId()); + public static void takeControlUnderPlayerStart(Game game, Ability source, Player controller, Player playerUnderControl, boolean givePauseForResponse) { + // game logs added in child's call + controller.controlPlayersTurn(game, playerUnderControl.getId(), CardUtil.getSourceLogName(game, source)); if (givePauseForResponse) { while (controller.canRespond()) { - if (controller.chooseUse(Outcome.Benefit, "You got control of " + targetPlayer.getLogName() + if (controller.chooseUse(Outcome.Benefit, "You got control of " + playerUnderControl.getLogName() + ". Use switch hands button to view opponent's hand.", null, "Continue", "Wait", null, game)) { break; @@ -1178,12 +1179,13 @@ public final class CardUtil { * * @param game * @param controller - * @param targetPlayer + * @param playerUnderControl */ - public static void takeControlUnderPlayerEnd(Game game, Player controller, Player targetPlayer) { - targetPlayer.setGameUnderYourControl(true, false); - if (!targetPlayer.getTurnControlledBy().equals(controller.getId())) { - controller.getPlayersUnderYourControl().remove(targetPlayer.getId()); + public static void takeControlUnderPlayerEnd(Game game, Ability source, Player controller, Player playerUnderControl) { + playerUnderControl.setGameUnderYourControl(true, false); + if (!playerUnderControl.getTurnControlledBy().equals(controller.getId())) { + game.informPlayers(controller + " return control of the turn to " + playerUnderControl.getLogName() + CardUtil.getSourceLogName(game, source)); + controller.getPlayersUnderYourControl().remove(playerUnderControl.getId()); } }