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);
|
||||
} 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<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:
|
||||
declareAttackers();
|
||||
break;
|
||||
|
||||
@@ -172,15 +172,6 @@ public class PhaseHandler extends MyObservable implements java.io.Serializable {
|
||||
return this.bCombat;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* repeatPhase.
|
||||
* </p>
|
||||
*/
|
||||
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<String, Object> runParams = new HashMap<String, Object>();
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -126,4 +126,5 @@ public abstract class PlayerController {
|
||||
public abstract List<Card> getCardsToMulligan(boolean isCommander, Player firstPlayer);
|
||||
|
||||
public abstract void takePriority();
|
||||
public abstract List<Card> chooseCardsToDiscardToMaximumHandSize(int numDiscard);
|
||||
}
|
||||
|
||||
@@ -282,4 +282,9 @@ public class PlayerControllerAi extends PlayerController {
|
||||
brains.onPriorityRecieved();
|
||||
// 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.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<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.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<SpellAbilitySta
|
||||
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)) {
|
||||
// TODO: make working triggered ability
|
||||
sp.resolve();
|
||||
|
||||
Reference in New Issue
Block a user