From c9450c16f7c03ec92eddd49bead18db52e1329b2 Mon Sep 17 00:00:00 2001 From: Maxmtg Date: Wed, 29 May 2013 10:00:54 +0000 Subject: [PATCH] A propper implementation of Rule 514 - Cleanup phase --- src/main/java/forge/game/ai/AiController.java | 17 -- .../java/forge/game/phase/PhaseHandler.java | 193 ++++++++++-------- .../forge/game/player/PlayerController.java | 1 + .../forge/game/player/PlayerControllerAi.java | 5 + .../game/player/PlayerControllerHuman.java | 40 ++-- src/main/java/forge/game/zone/MagicStack.java | 6 - 6 files changed, 123 insertions(+), 139 deletions(-) diff --git a/src/main/java/forge/game/ai/AiController.java b/src/main/java/forge/game/ai/AiController.java index ed2997fb889..82e8bbfa7a7 100644 --- a/src/main/java/forge/game/ai/AiController.java +++ b/src/main/java/forge/game/ai/AiController.java @@ -801,23 +801,6 @@ public class AiController { playSpellAbilities(game); } else { switch(phase) { - case CLEANUP: - if ( game.getPhaseHandler().getPlayerTurn() == player ) { - final int size = player.getCardsIn(ZoneType.Hand).size(); - - if (!player.isUnlimitedHandSize()) { - int max = Math.min(player.getZone(ZoneType.Hand).size(), size - player.getMaxHandSize()); - if( max > 0) { - final List toDiscard = getCardsToDiscard(max, (String[])null, null); - for (int i = 0; i < toDiscard.size(); i++) { - player.discard(toDiscard.get(i), null); - } - } - game.getStack().chooseOrderOfSimultaneousStackEntryAll(); - } - } - break; - case COMBAT_DECLARE_ATTACKERS: declareAttackers(); break; diff --git a/src/main/java/forge/game/phase/PhaseHandler.java b/src/main/java/forge/game/phase/PhaseHandler.java index f585dfd0e15..1cd3299b2de 100644 --- a/src/main/java/forge/game/phase/PhaseHandler.java +++ b/src/main/java/forge/game/phase/PhaseHandler.java @@ -172,15 +172,6 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable { return this.bCombat; } - /** - *

- * repeatPhase. - *

- */ - public final void repeatPhase() { - this.bRepeatCleanup = true; - } - private void advanceToNextPhase() { PhaseType oldPhase = phase; @@ -213,83 +204,6 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable { } - private void onPhaseEnd() { - // If the Stack isn't empty why is nextPhase being called? - if (!game.getStack().isEmpty()) { - throw new IllegalStateException("Phase.nextPhase() is called, but Stack isn't empty."); - } - - for (Player p : game.getPlayers()) { - int burn = p.getManaPool().clearPool(true); - if (Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_MANABURN)) { - p.loseLife(burn); - - // Play the Mana Burn sound - game.getEvents().post(new ManaBurnEvent()); - } - p.updateObservers(); - } - - switch (this.phase) { - case UNTAP: - this.nCombatsThisTurn = 0; - break; - - case COMBAT_DECLARE_ATTACKERS: - this.bCombat = !game.getCombat().getAttackers().isEmpty(); - game.getStack().unfreezeStack(); - this.nCombatsThisTurn++; - break; - - case COMBAT_DECLARE_BLOCKERS: - game.getStack().unfreezeStack(); - break; - - case COMBAT_END: - game.getCombat().reset(playerTurn); - this.getPlayerTurn().resetAttackedThisCombat(); - this.bCombat = false; - break; - - case CLEANUP: - this.bPreventCombatDamageThisTurn = false; - if (!this.bRepeatCleanup) { - this.setPlayerTurn(this.handleNextTurn()); - } - this.planarDiceRolledthisTurn = 0; - // Play the End Turn sound - game.getEvents().post(new EndOfTurnEvent()); - break; - default: // no action - } - - } - - private boolean isSkippingPhase(PhaseType phase) { - switch(phase) { - case UPKEEP: - return getPlayerTurn().hasKeyword("Skip your upkeep step."); - - case DRAW: - return getPlayerTurn().isSkippingDraw() || getTurn() == 1 && game.getPlayers().size() == 2; - - case COMBAT_BEGIN: - case COMBAT_DECLARE_ATTACKERS: - return playerTurn.isSkippingCombat(); - - case COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY: - case COMBAT_DECLARE_BLOCKERS: - case COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY: - case COMBAT_FIRST_STRIKE_DAMAGE: - case COMBAT_DAMAGE: - return !this.inCombat(); - - default: - return false; - } - - } - private final void onPhaseBegin() { if ( isSkippingPhase(phase) ) { @@ -404,6 +318,18 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable { break; case CLEANUP: + // Rule 514.1 + final int handSize = playerTurn.getZone(ZoneType.Hand).size(); + final int max = playerTurn.getMaxHandSize(); + int numDiscard = playerTurn.isUnlimitedHandSize() || handSize <= max || handSize == 0 ? 0 : handSize - max; + + if ( numDiscard > 0 ) { + for(Card c : playerTurn.getController().chooseCardsToDiscardToMaximumHandSize(numDiscard)){ + playerTurn.discard(c, null); + } + } + + // Rule 514.2 // Reset Damage received map for (final Card c : game.getCardsIn(ZoneType.Battlefield)) { c.onCleanupPhase(playerTurn); @@ -419,20 +345,23 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable { this.getPlayerTurn().removeKeyword("Skip all combat phases of this turn."); game.getCleanup().executeUntil(this.getNextTurn()); this.nUpkeepsThisTurn = 0; + + // Rule 514.3 + givePriorityToPlayer = false; break; default: break; } - + if (inCombat()) { CombatUtil.showCombat(game); } } - + // Handle effects that happen at the beginning of phases game.getAction().checkStateEffects(); - + if (this.givePriorityToPlayer) { // Run triggers if phase isn't being skipped final HashMap runParams = new HashMap(); @@ -440,11 +369,95 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable { runParams.put("Player", this.getPlayerTurn()); game.getTriggerHandler().runTrigger(TriggerType.Phase, runParams, false); } - + // This line fixes Combat Damage triggers not going off when they should game.getStack().unfreezeStack(); + + // Rule 514.3a + if( phase == PhaseType.CLEANUP && !game.getStack().isEmpty() ) { + bRepeatCleanup = true; + givePriorityToPlayer = true; + } } + + private void onPhaseEnd() { + // If the Stack isn't empty why is nextPhase being called? + if (!game.getStack().isEmpty()) { + throw new IllegalStateException("Phase.nextPhase() is called, but Stack isn't empty."); + } + + for (Player p : game.getPlayers()) { + int burn = p.getManaPool().clearPool(true); + if (Singletons.getModel().getPreferences().getPrefBoolean(FPref.UI_MANABURN)) { + p.loseLife(burn); + + // Play the Mana Burn sound + game.getEvents().post(new ManaBurnEvent()); + } + p.updateObservers(); + } + + switch (this.phase) { + case UNTAP: + this.nCombatsThisTurn = 0; + break; + + case COMBAT_DECLARE_ATTACKERS: + this.bCombat = !game.getCombat().getAttackers().isEmpty(); + game.getStack().unfreezeStack(); + this.nCombatsThisTurn++; + break; + + case COMBAT_DECLARE_BLOCKERS: + game.getStack().unfreezeStack(); + break; + + case COMBAT_END: + game.getCombat().reset(playerTurn); + this.getPlayerTurn().resetAttackedThisCombat(); + this.bCombat = false; + break; + + case CLEANUP: + this.bPreventCombatDamageThisTurn = false; + if (!this.bRepeatCleanup) { + this.setPlayerTurn(this.handleNextTurn()); + } + this.planarDiceRolledthisTurn = 0; + // Play the End Turn sound + game.getEvents().post(new EndOfTurnEvent()); + break; + default: // no action + } + + } + + private boolean isSkippingPhase(PhaseType phase) { + switch(phase) { + case UPKEEP: + return getPlayerTurn().hasKeyword("Skip your upkeep step."); + + case DRAW: + return getPlayerTurn().isSkippingDraw() || getTurn() == 1 && game.getPlayers().size() == 2; + + case COMBAT_BEGIN: + case COMBAT_DECLARE_ATTACKERS: + return playerTurn.isSkippingCombat(); + + case COMBAT_DECLARE_ATTACKERS_INSTANT_ABILITY: + case COMBAT_DECLARE_BLOCKERS: + case COMBAT_DECLARE_BLOCKERS_INSTANT_ABILITY: + case COMBAT_FIRST_STRIKE_DAMAGE: + case COMBAT_DAMAGE: + return !this.inCombat(); + + default: + return false; + } + + } + /** * Checks if is prevent combat damage this turn. * diff --git a/src/main/java/forge/game/player/PlayerController.java b/src/main/java/forge/game/player/PlayerController.java index af3c271c609..99add61bcf3 100644 --- a/src/main/java/forge/game/player/PlayerController.java +++ b/src/main/java/forge/game/player/PlayerController.java @@ -126,4 +126,5 @@ public abstract class PlayerController { public abstract List getCardsToMulligan(boolean isCommander, Player firstPlayer); public abstract void takePriority(); + public abstract List chooseCardsToDiscardToMaximumHandSize(int numDiscard); } diff --git a/src/main/java/forge/game/player/PlayerControllerAi.java b/src/main/java/forge/game/player/PlayerControllerAi.java index 455bdebae7a..187eb1ad43c 100644 --- a/src/main/java/forge/game/player/PlayerControllerAi.java +++ b/src/main/java/forge/game/player/PlayerControllerAi.java @@ -282,4 +282,9 @@ public class PlayerControllerAi extends PlayerController { brains.onPriorityRecieved(); // use separate thread for AI? } + + @Override + public List chooseCardsToDiscardToMaximumHandSize(int numDiscard) { + return brains.getCardsToDiscard(numDiscard, (String[])null, null); + } } diff --git a/src/main/java/forge/game/player/PlayerControllerHuman.java b/src/main/java/forge/game/player/PlayerControllerHuman.java index f05c5a9a52a..05c54e32ee9 100644 --- a/src/main/java/forge/game/player/PlayerControllerHuman.java +++ b/src/main/java/forge/game/player/PlayerControllerHuman.java @@ -33,7 +33,6 @@ import forge.deck.DeckSection; import forge.game.GameState; import forge.game.GameType; import forge.game.phase.PhaseType; -import forge.game.zone.MagicStack; import forge.game.zone.ZoneType; import forge.gui.GuiChoose; import forge.gui.GuiDialog; @@ -483,7 +482,6 @@ public class PlayerControllerHuman extends PlayerController { @Override public void takePriority() { PhaseType phase = game.getPhaseHandler().getPhase(); - MagicStack stack = game.getStack(); boolean maySkipPriority = mayAutoPass(phase) || isUiSetToSkipPhase(game.getPhaseHandler().getPlayerTurn(), phase); if (game.getStack().isEmpty() && maySkipPriority) { @@ -504,32 +502,22 @@ public class PlayerControllerHuman extends PlayerController { Singletons.getControl().getInputQueue().setInputAndWait(inpBlock); return; - case CLEANUP: - if( stack.isEmpty() ) { - final int n = player.getZone(ZoneType.Hand).size(); - final int max = player.getMaxHandSize(); - // goes to the next phase - int nDiscard = player.isUnlimitedHandSize() || n <= max || n == 0 ? 0 : n - max; - - - if ( nDiscard > 0 ) { - InputSelectCardsFromList inp = new InputSelectCardsFromList(nDiscard, nDiscard, player.getZone(ZoneType.Hand).getCards()); - String msgFmt = "Cleanup Phase: You can only have a maximum of %d cards, you currently have %d cards in your hand - select %d card(s) to discard"; - String message = String.format(msgFmt, max, n, nDiscard); - inp.setMessage(message); - inp.setCancelAllowed(false); - Singletons.getControl().getInputQueue().setInputAndWait(inp); - for(Card c : inp.getSelected()){ - player.discard(c, null); - } - } - break; - } - - // fallthough - default: showDefaultInput(); } } + + @Override + public List chooseCardsToDiscardToMaximumHandSize(int nDiscard) { + final int n = player.getZone(ZoneType.Hand).size(); + final int max = player.getMaxHandSize(); + + InputSelectCardsFromList inp = new InputSelectCardsFromList(nDiscard, nDiscard, player.getZone(ZoneType.Hand).getCards()); + String msgFmt = "Cleanup Phase: You can only have a maximum of %d cards, you currently have %d cards in your hand - select %d card(s) to discard"; + String message = String.format(msgFmt, max, n, nDiscard); + inp.setMessage(message); + inp.setCancelAllowed(false); + Singletons.getControl().getInputQueue().setInputAndWait(inp); + return inp.getSelected(); + } } diff --git a/src/main/java/forge/game/zone/MagicStack.java b/src/main/java/forge/game/zone/MagicStack.java index 86a66d5b4f8..b8e28be1575 100644 --- a/src/main/java/forge/game/zone/MagicStack.java +++ b/src/main/java/forge/game/zone/MagicStack.java @@ -59,7 +59,6 @@ import forge.game.ai.ComputerUtil; import forge.game.ai.ComputerUtilCard; import forge.game.ai.ComputerUtilCost; import forge.game.event.SpellResolvedEvent; -import forge.game.phase.PhaseType; import forge.game.player.HumanPlay; import forge.game.player.Player; import forge.gui.GuiChoose; @@ -367,11 +366,6 @@ public class MagicStack extends MyObservable implements Iterable