mirror of
https://github.com/Card-Forge/forge.git
synced 2025-11-18 11:48:02 +00:00
A propper implementation of Rule 514 - Cleanup phase
This commit is contained in:
@@ -801,23 +801,6 @@ public class AiController {
|
|||||||
playSpellAbilities(game);
|
playSpellAbilities(game);
|
||||||
} else {
|
} else {
|
||||||
switch(phase) {
|
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<Card> 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:
|
case COMBAT_DECLARE_ATTACKERS:
|
||||||
declareAttackers();
|
declareAttackers();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -172,15 +172,6 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable {
|
|||||||
return this.bCombat;
|
return this.bCombat;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* repeatPhase.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
public final void repeatPhase() {
|
|
||||||
this.bRepeatCleanup = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void advanceToNextPhase() {
|
private void advanceToNextPhase() {
|
||||||
PhaseType oldPhase = phase;
|
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() {
|
private final void onPhaseBegin() {
|
||||||
|
|
||||||
if ( isSkippingPhase(phase) ) {
|
if ( isSkippingPhase(phase) ) {
|
||||||
@@ -404,6 +318,18 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case CLEANUP:
|
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
|
// Reset Damage received map
|
||||||
for (final Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
for (final Card c : game.getCardsIn(ZoneType.Battlefield)) {
|
||||||
c.onCleanupPhase(playerTurn);
|
c.onCleanupPhase(playerTurn);
|
||||||
@@ -419,6 +345,9 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable {
|
|||||||
this.getPlayerTurn().removeKeyword("Skip all combat phases of this turn.");
|
this.getPlayerTurn().removeKeyword("Skip all combat phases of this turn.");
|
||||||
game.getCleanup().executeUntil(this.getNextTurn());
|
game.getCleanup().executeUntil(this.getNextTurn());
|
||||||
this.nUpkeepsThisTurn = 0;
|
this.nUpkeepsThisTurn = 0;
|
||||||
|
|
||||||
|
// Rule 514.3
|
||||||
|
givePriorityToPlayer = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -443,6 +372,90 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable {
|
|||||||
|
|
||||||
// This line fixes Combat Damage triggers not going off when they should
|
// This line fixes Combat Damage triggers not going off when they should
|
||||||
game.getStack().unfreezeStack();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -126,4 +126,5 @@ public abstract class PlayerController {
|
|||||||
public abstract List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer);
|
public abstract List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer);
|
||||||
|
|
||||||
public abstract void takePriority();
|
public abstract void takePriority();
|
||||||
|
public abstract List<Card> chooseCardsToDiscardToMaximumHandSize(int numDiscard);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,4 +282,9 @@ public class PlayerControllerAi extends PlayerController {
|
|||||||
brains.onPriorityRecieved();
|
brains.onPriorityRecieved();
|
||||||
// use separate thread for AI?
|
// use separate thread for AI?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Card> chooseCardsToDiscardToMaximumHandSize(int numDiscard) {
|
||||||
|
return brains.getCardsToDiscard(numDiscard, (String[])null, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import forge.deck.DeckSection;
|
|||||||
import forge.game.GameState;
|
import forge.game.GameState;
|
||||||
import forge.game.GameType;
|
import forge.game.GameType;
|
||||||
import forge.game.phase.PhaseType;
|
import forge.game.phase.PhaseType;
|
||||||
import forge.game.zone.MagicStack;
|
|
||||||
import forge.game.zone.ZoneType;
|
import forge.game.zone.ZoneType;
|
||||||
import forge.gui.GuiChoose;
|
import forge.gui.GuiChoose;
|
||||||
import forge.gui.GuiDialog;
|
import forge.gui.GuiDialog;
|
||||||
@@ -483,7 +482,6 @@ public class PlayerControllerHuman extends PlayerController {
|
|||||||
@Override
|
@Override
|
||||||
public void takePriority() {
|
public void takePriority() {
|
||||||
PhaseType phase = game.getPhaseHandler().getPhase();
|
PhaseType phase = game.getPhaseHandler().getPhase();
|
||||||
MagicStack stack = game.getStack();
|
|
||||||
|
|
||||||
boolean maySkipPriority = mayAutoPass(phase) || isUiSetToSkipPhase(game.getPhaseHandler().getPlayerTurn(), phase);
|
boolean maySkipPriority = mayAutoPass(phase) || isUiSetToSkipPhase(game.getPhaseHandler().getPlayerTurn(), phase);
|
||||||
if (game.getStack().isEmpty() && maySkipPriority) {
|
if (game.getStack().isEmpty() && maySkipPriority) {
|
||||||
@@ -504,32 +502,22 @@ public class PlayerControllerHuman extends PlayerController {
|
|||||||
Singletons.getControl().getInputQueue().setInputAndWait(inpBlock);
|
Singletons.getControl().getInputQueue().setInputAndWait(inpBlock);
|
||||||
return;
|
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:
|
default:
|
||||||
showDefaultInput();
|
showDefaultInput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Card> 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ import forge.game.ai.ComputerUtil;
|
|||||||
import forge.game.ai.ComputerUtilCard;
|
import forge.game.ai.ComputerUtilCard;
|
||||||
import forge.game.ai.ComputerUtilCost;
|
import forge.game.ai.ComputerUtilCost;
|
||||||
import forge.game.event.SpellResolvedEvent;
|
import forge.game.event.SpellResolvedEvent;
|
||||||
import forge.game.phase.PhaseType;
|
|
||||||
import forge.game.player.HumanPlay;
|
import forge.game.player.HumanPlay;
|
||||||
import forge.game.player.Player;
|
import forge.game.player.Player;
|
||||||
import forge.gui.GuiChoose;
|
import forge.gui.GuiChoose;
|
||||||
@@ -367,11 +366,6 @@ public class MagicStack extends MyObservable implements Iterable<SpellAbilitySta
|
|||||||
System.out.println(sp.getSourceCard().getName() + " - activatingPlayer not set before adding to stack.");
|
System.out.println(sp.getSourceCard().getName() + " - activatingPlayer not set before adding to stack.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (game.getPhaseHandler().is(PhaseType.CLEANUP)) {
|
|
||||||
// If something triggers during Cleanup, need to repeat
|
|
||||||
game.getPhaseHandler().repeatPhase();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((sp instanceof AbilityTriggered) || (sp instanceof AbilityStatic)) {
|
if ((sp instanceof AbilityTriggered) || (sp instanceof AbilityStatic)) {
|
||||||
// TODO: make working triggered ability
|
// TODO: make working triggered ability
|
||||||
sp.resolve();
|
sp.resolve();
|
||||||
|
|||||||
Reference in New Issue
Block a user