package magic.ui.screen.duel.game;
import java.awt.Dimension;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import magic.ai.MagicAI;
import magic.data.DuelConfig;
import magic.data.GeneralConfig;
import magic.exception.InvalidDeckException;
import magic.exception.UndoClickedException;
import magic.game.state.GameState;
import magic.game.state.GameStateFileWriter;
import magic.game.state.GameStateSnapshot;
import magic.model.IUIGameController;
import magic.model.MagicCardDefinition;
import magic.model.MagicCardList;
import magic.model.MagicColor;
import magic.model.MagicGame;
import magic.model.MagicManaCost;
import magic.model.MagicObject;
import magic.model.MagicPlayer;
import magic.model.MagicPlayerZone;
import magic.model.MagicSource;
import magic.model.MagicSubType;
import magic.model.choice.MagicPlayChoice;
import magic.model.choice.MagicPlayChoiceResult;
import magic.model.event.MagicEvent;
import magic.model.event.MagicPriorityEvent;
import magic.model.phase.MagicPhaseType;
import magic.model.target.MagicTarget;
import magic.model.target.MagicTargetNone;
import magic.translate.MText;
import magic.translate.StringContext;
import magic.ui.IChoiceViewer;
import magic.ui.IPlayerZoneListener;
import magic.ui.MagicFileChoosers;
import magic.ui.MagicSound;
import magic.ui.ScreenController;
import magic.ui.duel.viewerinfo.CardViewerInfo;
import magic.ui.duel.viewerinfo.GameViewerInfo;
import magic.ui.duel.viewerinfo.PlayerViewerInfo;
import magic.ui.helpers.KeyEventAction;
import magic.ui.screen.duel.mulligan.MulliganScreen;
import magic.ui.widget.card.AnnotatedCardPanel;
import magic.ui.widget.duel.animation.DrawCardAnimation;
import magic.ui.widget.duel.animation.MagicAnimation;
import magic.ui.widget.duel.animation.MagicAnimations;
import magic.ui.widget.duel.animation.PlayCardAnimation;
import magic.ui.widget.duel.choice.ColorChoicePanel;
import magic.ui.widget.duel.choice.ManaCostXChoicePanel;
import magic.ui.widget.duel.choice.MayChoicePanel;
import magic.ui.widget.duel.choice.ModeChoicePanel;
import magic.ui.widget.duel.choice.MulliganChoicePanel;
import magic.ui.widget.duel.choice.MultiKickerChoicePanel;
import magic.ui.widget.duel.choice.PlayChoicePanel;
import magic.ui.widget.duel.sidebar.LogStackViewer;
import magic.ui.widget.duel.viewer.PlayerZoneViewer;
import magic.ui.widget.duel.viewer.UserActionPanel;
import magic.utility.MagicFileSystem;
import magic.utility.MagicSystem;
public class SwingGameController implements IUIGameController {
// translatable strings
private static final String _S1 = "conceded";
private static final String _S2 = "lost";
@StringContext(eg = "Player1 conceded/lost the game.")
private static final String _S3 = "%s %s the game.";
private static final String _S4 = "You may pay the buyback %s.";
@StringContext(eg = "get single kicker count choice")
private static final String _S5 = "You may pay the %s %s.";
private static final String _S6 = "You may take a mulligan.";
private static final GeneralConfig CONFIG = GeneralConfig.getInstance();
private final DuelLayeredPane duelPane;
private final DuelPanel gamePanel;
private LogStackViewer logStackViewer;
private final MagicGame game;
private final AtomicBoolean running = new AtomicBoolean(false);
private final AtomicBoolean isPaused = new AtomicBoolean(false);
private final AtomicBoolean gameConceded = new AtomicBoolean(false);
private final AtomicBoolean isStackFastForward = new AtomicBoolean(false);
private final AtomicBoolean isPauseCancelled = new AtomicBoolean(false);
private final Collection<IChoiceViewer> choiceViewers = new ArrayList<>();
private Set<?> validChoices = Collections.emptySet();
private AnnotatedCardPanel cardPopup;
private UserActionPanel userActionPanel;
private boolean actionClicked;
private boolean combatChoice;
private boolean resetGame;
private MagicTarget choiceClicked = MagicTargetNone.getInstance();
private MagicCardDefinition sourceCardDefinition = MagicCardDefinition.UNKNOWN;
private final BlockingQueue<Boolean> input = new SynchronousQueue<>();
private GameViewerInfo gameViewerInfo;
private PlayerZoneViewer playerZoneViewer;
private final List<IPlayerZoneListener> playerZoneListeners = new ArrayList<>();
private MagicAnimation animation = null;
private static boolean isControlKeyDown = false;
private static final KeyEventDispatcher keyEventDispatcher = new KeyEventDispatcher() {
@Override
public boolean dispatchKeyEvent(KeyEvent e) {
isControlKeyDown = e.isControlDown();
return false;
}
};
public SwingGameController(final DuelLayeredPane aDuelPane, final MagicGame aGame) {
this.duelPane = aDuelPane;
this.game = aGame;
gameViewerInfo = new GameViewerInfo(game);
gamePanel = duelPane.getDuelPanel();
gamePanel.setController(this);
duelPane.getCardViewer().setController(this);
clearValidChoices();
setControlKeyMonitor();
setKeyEventActions();
}
private void setKeyEventActions() {
KeyEventAction.doAction(gamePanel, this::actionKeyPressed)
.on(0, KeyEvent.VK_RIGHT, KeyEvent.VK_SPACE);
KeyEventAction.doAction(gamePanel, this::undoKeyPressed)
.on(0, KeyEvent.VK_LEFT, KeyEvent.VK_BACK_SPACE, KeyEvent.VK_DELETE);
KeyEventAction.doAction(gamePanel, this::passKeyPressed)
.on(InputEvent.SHIFT_MASK, KeyEvent.VK_RIGHT, KeyEvent.VK_SPACE);
KeyEventAction.doAction(gamePanel, this::switchPlayerZone)
.on(0, KeyEvent.VK_S);
KeyEventAction.doAction(gamePanel, this::showLogScreen)
.on(0, KeyEvent.VK_L);
KeyEventAction.doAction(gamePanel, this::showKeywordsScreen)
.on(0, KeyEvent.VK_K);
KeyEventAction.doAction(gamePanel, this::switchLogStackLayout)
.on(0, KeyEvent.VK_M);
}
private void setControlKeyMonitor() {
isControlKeyDown = false;
final KeyboardFocusManager kbFocusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
kbFocusManager.removeKeyEventDispatcher(keyEventDispatcher);
kbFocusManager.addKeyEventDispatcher(keyEventDispatcher);
}
public MagicGame getGame() {
return game;
}
@Override
public void enableForwardButton() {
SwingUtilities.invokeLater(() -> {
userActionPanel.enableButton();
});
}
@Override
public void disableActionButton(final boolean thinking) {
SwingUtilities.invokeLater(() -> {
userActionPanel.disableButton(thinking);
});
}
private void disableActionUndoButtons() {
SwingUtilities.invokeLater(() -> {
userActionPanel.disableButton(false);
userActionPanel.enableUndoButton(true);
});
}
@Override
public void pause(final int t) {
assert !SwingUtilities.isEventDispatchThread();
disableActionUndoButtons();
int tick = 0;
while (tick < t && isPauseCancelled.get() == false) {
try {
Thread.sleep(10);
} catch (final InterruptedException ex) {
throw new RuntimeException(ex);
}
tick += 10;
}
isPauseCancelled.set(false);
}
private static void invokeAndWait(final Runnable task) {
try { //invoke and wait
SwingUtilities.invokeAndWait(task);
} catch (final InterruptedException | InvocationTargetException ex) {
throw new RuntimeException(ex);
}
}
private void waitForUIUpdates() {
invokeAndWait(() -> {
//do nothing, ensure that event dispatch queue is cleared
});
}
/** Returns true when undo was clicked. */
private boolean waitForInputOrUndo() {
try {
waitForUIUpdates();
input.clear();
return input.take();
} catch (final InterruptedException ex) {
throw new RuntimeException(ex);
}
}
@Override
public void waitForInput() throws UndoClickedException {
final boolean undoClicked = waitForInputOrUndo();
if (undoClicked) {
throw new UndoClickedException();
}
}
private <E extends JComponent> E waitForInput(final Callable<E> func) throws UndoClickedException {
final AtomicReference<E> ref = new AtomicReference<>();
final AtomicReference<Exception> except = new AtomicReference<>();
SwingUtilities.invokeLater(() -> {
try {
final E content = func.call();
ref.set(content);
userActionPanel.setContentPanel(content);
} catch (Exception ex) {
except.set(ex);
}
});
waitForInput();
if (except.get() != null) {
throw new RuntimeException(except.get());
} else {
return ref.get();
}
}
private void resume(final boolean undoClicked) {
input.offer(undoClicked);
}
public void switchPlayerZone() {
playerZoneViewer.switchPlayerZone();
}
public void passKeyPressed() {
if (gamePanel.canClickAction()) {
actionClicked();
game.skipTurnTill(MagicPhaseType.EndOfTurn);
}
}
public void actionKeyPressed() {
if (duelPane.getDialogPanel().isVisible()) {
duelPane.getDialogPanel().setVisible(false);
} else if (gamePanel.canClickAction()) {
actionClicked();
}
}
public void actionClicked() {
hideInfo();
userActionPanel.clearContentPanel();
actionClicked = true;
choiceClicked = MagicTargetNone.getInstance();
resume(false);
}
public void undoKeyPressed() {
if (gamePanel.canClickUndo()) {
undoClicked();
}
}
public void undoClicked() {
hideInfo();
if (game.hasUndoPoints()) {
actionClicked = false;
choiceClicked = MagicTargetNone.getInstance();
setSourceCardDefinition(MagicSource.NONE);
clearValidChoices();
resume(true);
}
}
public void processClick(final MagicTarget choice) {
if (validChoices.contains(choice)) {
actionClicked = false;
choiceClicked = choice;
hideInfo();
resume(false);
}
}
@Override
public boolean isActionClicked() {
return actionClicked;
}
@SuppressWarnings("unchecked")
@Override
public <T> T getChoiceClicked() {
return (T)choiceClicked;
}
public void setImageCardViewer(final AnnotatedCardPanel cardViewer) {
this.cardPopup = cardViewer;
}
public void setUserActionPanel(final UserActionPanel userActionPanel) {
this.userActionPanel = userActionPanel;
}
private void viewCardPopupCentered(CardViewerInfo cardInfo, final int popupDelay) {
final Rectangle containerZone = gamePanel.getBattlefieldPanelBounds();
// set popup image and size.
cardPopup.setCard(cardInfo, containerZone.getSize());
final int x = containerZone.x + (int)((containerZone.getWidth() / 2) - (cardPopup.getWidth() / 2));
final int y = containerZone.y + (int)((containerZone.getHeight() / 2) - (cardPopup.getHeight() / 2));
cardPopup.setLocation(x,y);
cardPopup.showDelayed(popupDelay);
}
public void viewCardPopupCentered(final MagicObject cardObject, final int popupDelay) {
// mouse wheel rotation event can fire more than once
// so ignore all but the first event.
if (cardObject == cardPopup.getMagicObject()) {
return;
}
// ignore if user wants current popup to remain open
// so they can view ability icon tooltips.
if (isControlKeyDown && cardPopup.isVisible()) {
return;
}
final Rectangle containerZone = gamePanel.getBattlefieldPanelBounds();
// set popup image and size.
cardPopup.setCard(cardObject, containerZone.getSize());
final int x = containerZone.x + (int)((containerZone.getWidth() / 2) - (cardPopup.getWidth() / 2));
final int y = containerZone.y + (int)((containerZone.getHeight() / 2) - (cardPopup.getHeight() / 2));
cardPopup.setLocation(x,y);
cardPopup.showDelayed(popupDelay);
}
/**
*
* @param cardObject
* @param index
* @param cardRect : screen position & size of selected card on battlefield.
* @param popupAboveBelowOnly : if true then the popup will restrict its height to always fit above/below the selected card.
*/
public void viewCardPopup(
final MagicObject cardObject,
final Rectangle cardRect,
final boolean popupAboveBelowOnly,
final int popupDelay) {
// mouse wheel rotation event can fire more than once
// so ignore all but the first event.
if (cardObject == cardPopup.getMagicObject()) {
return;
}
// ignore if user wants current popup to remain open
// so they can view ability icon tooltips.
if (isControlKeyDown && cardPopup.isVisible()) {
return;
}
final boolean isAutoPopup = !CONFIG.isMouseWheelPopup();
final int VERTICAL_INSET = 4; // pixels
final int PAD2 = 0;
final Dimension gamePanelSize = gamePanel.getSize();
// update selected card position so it is relative to container instead of screen.
final Point gamePanelScreenPosition = gamePanel.getLocationOnScreen();
cardRect.x -= gamePanelScreenPosition.x;
cardRect.y -= gamePanelScreenPosition.y;
// set popup image and size.
if (popupAboveBelowOnly) {
final int spaceAbove = cardRect.y;
final int spaceBelow = gamePanelSize.height - cardRect.y - cardRect.height;
final int height = Math.max(spaceAbove, spaceBelow) - (VERTICAL_INSET * 2);
cardPopup.setCard(cardObject, new Dimension(gamePanelSize.width, height));
} else {
cardPopup.setCard(cardObject, gamePanelSize);
}
final int popupWidth = cardPopup.getWidth();
final int popupHeight = cardPopup.getHeight();
int x = cardRect.x + (cardRect.width - popupWidth) / 2;
final int y1 = cardRect.y - popupHeight - VERTICAL_INSET;
final int y2 = cardRect.y + cardRect.height + VERTICAL_INSET;
final int dy2 = gamePanelSize.height - y2 - popupHeight;
if (x + popupWidth >= gamePanelSize.width) {
x = cardRect.x + cardRect.width - popupWidth;
}
int y;
// Position is next to card?
if (y1 < PAD2 && dy2 < PAD2) {
if (isAutoPopup) {
x = cardRect.x - popupWidth - VERTICAL_INSET;
} else {
x = cardRect.x - popupWidth + cardRect.width;
}
if (x < 0) {
if (isAutoPopup) {
x = cardRect.x + cardRect.width + VERTICAL_INSET;
} else {
x = cardRect.x;
}
}
if (y1 >= dy2) {
y = cardRect.y + cardRect.height - popupHeight;
if (y < PAD2) {
y = PAD2;
}
} else {
y = cardRect.y;
if (y + popupHeight + PAD2 > gamePanelSize.height) {
y = gamePanelSize.height - PAD2 - popupHeight;
}
}
// Position is above card?
} else if (y1 >= PAD2) {
y = y1;
// Position if beneath card.
} else {
y = y2;
}
cardPopup.setLocation(x,y);
cardPopup.showDelayed(popupDelay);
}
public void viewCardPopup(final MagicObject cardObject, final Rectangle cardRect, final boolean popupAboveBelowOnly) {
viewCardPopup(cardObject, cardRect, popupAboveBelowOnly, getPopupDelay());
}
public boolean isPopupVisible() {
return cardPopup.isVisible();
}
/**
*
* @param cardObject
* @param cardRect : screen position & size of selected card on battlefield.
*/
public void viewCardPopup(final MagicObject cardObject, final Rectangle cardRect) {
viewCardPopup(cardObject, cardRect, false);
}
public void viewInfoRight(final MagicCardDefinition cardDefinition,final int index,final Rectangle rect) {
final Dimension gamePanelSize = gamePanel.getSize();
// update rect position so it is relative to container instead of screen.
final Point pointOnScreen = gamePanel.getLocationOnScreen();
rect.x -= pointOnScreen.x;
rect.y -= pointOnScreen.y;
final int x = rect.x + rect.width + 10;
cardPopup.setCardForPrompt(cardDefinition, gamePanelSize);
final int maxY = gamePanelSize.height - cardPopup.getHeight();
int y = rect.y + (rect.height-cardPopup.getHeight()) / 2;
if (y < 0) {
y = 0;
} else if (y > maxY) {
y = maxY;
}
cardPopup.setLocation(x,y);
cardPopup.showDelayed(getPopupDelay());
}
private int getPopupDelay() {
return CONFIG.isMouseWheelPopup() ? 0 : CONFIG.getPopupDelay();
}
public void hideInfo() {
if (!isControlKeyDown) {
cardPopup.hideDelayed();
}
}
public void hideInfoNoDelay() {
if (!isControlKeyDown) {
cardPopup.hideNoDelay();
}
}
@Override
public void setSourceCardDefinition(final MagicSource source) {
sourceCardDefinition=source.getCardDefinition();
}
public MagicCardDefinition getSourceCardDefinition() {
return sourceCardDefinition;
}
@Override
public void focusViewers(final int handGraveyard) {
SwingUtilities.invokeLater(() -> {
gamePanel.focusViewers(handGraveyard);
});
}
@Override
public void clearCards() {
showCards(new MagicCardList());
}
@Override
public void showCards(final MagicCardList cards) {
SwingUtilities.invokeLater(() -> {
gamePanel.showCards(cards);
});
}
public void registerChoiceViewer(final IChoiceViewer choiceViewer) {
choiceViewers.add(choiceViewer);
}
private void showValidChoices() {
assert SwingUtilities.isEventDispatchThread();
for (final IChoiceViewer choiceViewer : choiceViewers) {
choiceViewer.showValidChoices(validChoices);
}
}
public boolean isCombatChoice() {
return combatChoice;
}
@Override
public void clearValidChoices() {
// called from both edt and application threads.
SwingUtilities.invokeLater(() -> {
clearDisplayedValidChoices();
});
showMessage(MagicSource.NONE, "");
}
private void clearDisplayedValidChoices() {
assert SwingUtilities.isEventDispatchThread();
if (!validChoices.isEmpty()) {
validChoices.clear();
combatChoice=false;
showValidChoices();
}
}
@Override
public void setValidChoices(final Set<?> aValidChoices, final boolean aCombatChoice) {
assert !SwingUtilities.isEventDispatchThread();
SwingUtilities.invokeLater(() -> {
clearDisplayedValidChoices();
validChoices = new HashSet<>(aValidChoices);
combatChoice = aCombatChoice;
showValidChoices();
});
}
public Set<?> getValidChoices() {
return validChoices;
}
public GameViewerInfo getViewerInfo() {
return gameViewerInfo;
}
// private void debugAnimation(MagicAnimation animation) {
// if (animation != null) {
// MagicAnimations.debugPrint(animation);
//// pause(2000);
// }
// }
private void doFlashPlayerZoneButton(GameViewerInfo newGameInfo) {
if (animation instanceof PlayCardAnimation) {
gamePanel.doFlashPlayerHandZoneButton(newGameInfo.getTurnPlayer());
} else if (animation instanceof DrawCardAnimation) {
gamePanel.doFlashLibraryZoneButton(newGameInfo.getTurnPlayer());
}
}
private boolean isReadyToAnimate() {
return CONFIG.showGameplayAnimations() && (animation == null || animation.isRunning.get() == false);
}
private void doPlayAnimationAndWait(final GameViewerInfo oldGameInfo, final GameViewerInfo newGameInfo) {
if (isReadyToAnimate() == false) {
return;
}
// skip animation when newGameInfo is result of undo
if (newGameInfo.getUndoPoints() < oldGameInfo.getUndoPoints()) {
return;
}
animation = MagicAnimations.getGameplayAnimation(oldGameInfo, newGameInfo, gamePanel);
if (animation != null) {
animation.isRunning.set(true);
SwingUtilities.invokeLater(() -> {
doFlashPlayerZoneButton(newGameInfo);
duelPane.getAnimationPanel().playAnimation(animation);
});
while (animation.isRunning.get() == true) {
pause(100);
}
}
}
/**
* Update/render the gui based on the model state.
*/
@Override
public void updateGameView() {
assert !SwingUtilities.isEventDispatchThread();
final GameViewerInfo oldGameInfo = gameViewerInfo;
gameViewerInfo = new GameViewerInfo(game);
doPlayAnimationAndWait(oldGameInfo, gameViewerInfo);
SwingUtilities.invokeLater(() -> {
gamePanel.update(gameViewerInfo);
});
waitForUIUpdates();
}
public static String getMessageWithSource(final MagicSource source,final String message) {
if (source == null) {
throw new RuntimeException("source is null");
}
if (source == MagicSource.NONE) {
return message;
} else {
return "("+source+")|"+message;
}
}
@Override
public void showMessage(final MagicSource source, final String message) {
SwingUtilities.invokeLater(() -> {
userActionPanel.showMessage(getMessageWithSource(source, MText.get(message)));
});
}
private Object[] getArtificialNextEventChoiceResults(final MagicEvent event) {
disableActionButton(true);
if (CONFIG.getHideAiActionPrompt()) {
showMessage(MagicSource.NONE, "");
} else {
showMessage(event.getSource(),event.getChoiceDescription());
}
waitForUIUpdates();
//dynamically get the AI based on the player's index
final MagicPlayer player = event.getPlayer();
final MagicAI ai = player.getAiProfile().getAiType().getAI();
return ai.findNextEventChoiceResults(game, player);
}
private Object[] getPlayerNextEventChoiceResults(final MagicEvent event) throws UndoClickedException {
setSourceCardDefinition(event.getSource());
final Object[] choiceResults;
try {
choiceResults = event.getChoice().getPlayerChoiceResults(this,game,event);
} finally {
clearValidChoices();
setSourceCardDefinition(MagicSource.NONE);
}
return choiceResults;
}
private void executeNextEventWithChoices(final MagicEvent event) {
final Object[] choiceResults;
if (event.getPlayer().isArtificial()) {
choiceResults = getArtificialNextEventChoiceResults(event);
// clear skip till EOT if AI plays something
if (event.getChoice() == MagicPlayChoice.getInstance() && choiceResults[0] != MagicPlayChoiceResult.PASS && choiceResults[0] != MagicPlayChoiceResult.SKIP) {
game.clearSkipTurnTill();
}
} else {
try {
choiceResults = getPlayerNextEventChoiceResults(event);
} catch (UndoClickedException undo) {
if (gameConceded.get()) {
return;
} else {
performUndo();
return;
}
}
}
game.executeNextEvent(choiceResults);
}
public void resetGame() {
if (game.hasUndoPoints()) {
resetGame=true;
undoClicked();
}
}
public void concede() {
if (!gameConceded.get() && !game.isFinished()) {
game.setLosingPlayer(game.getPlayer(0));
game.setConceded(true);
game.clearUndoPoints();
gameConceded.set(true);
resume(true);
}
}
private void performUndo() {
if (resetGame) {
resetGame=false;
while (game.hasUndoPoints()) {
game.restore();
}
} else {
game.restore();
}
}
@Override
public void haltGame() {
running.set(false);
}
/**
* Main game loop runs on separate thread.
*/
@Override
public void runGame() {
assert !SwingUtilities.isEventDispatchThread();
running.set(true);
while (running.get()) {
if (isPaused.get()) {
pause(100);
} else if (game.isFinished()) {
doNextActionOnGameFinished();
} else {
executeNextEventOrPhase();
updateGameView();
}
}
}
/**
* Once a game has finished determine what happens next.
* <p>
* If running an automated game then automatically start next game/duel.
* If an interactive game then wait for input from user.
*/
private void doNextActionOnGameFinished() {
game.logMessages();
clearValidChoices();
showEndGameMessage();
playEndGameSoundEffect();
enableForwardButton();
if (MagicSystem.isAiVersusAi() == false && waitForInputOrUndo()) {
performUndo();
updateGameView();
} else {
game.advanceDuel();
SwingUtilities.invokeLater(() -> {
try {
gamePanel.close();
} catch (InvalidDeckException ex) {
ScreenController.showWarningMessage(ex.getMessage());
}
});
running.set(false);
}
}
private void executeNextEventOrPhase() {
if (game.hasNextEvent()) {
executeNextEvent();
} else {
game.executePhase();
}
}
private void executeNextEvent() {
final MagicEvent event=game.getNextEvent();
if (event instanceof MagicPriorityEvent) {
game.logMessages();
}
if (event.hasChoice()) {
executeNextEventWithChoices(event);
} else {
game.executeNextEvent();
}
}
private void showEndGameMessage() {
assert !SwingUtilities.isEventDispatchThread();
if (!MagicSystem.isAiVersusAi() && !MagicSystem.isDebugMode()) {
SwingUtilities.invokeLater(() -> {
duelPane.getDialogPanel().showEndGameMessage(SwingGameController.this);
});
}
showMessage(MagicSource.NONE,
String.format("{L} %s",
MText.get(_S3,
game.getLosingPlayer(),
gameConceded.get() ? MText.get(_S1) : MText.get(_S2)
)
)
);
}
private void playEndGameSoundEffect() {
if (game.getLosingPlayer().getIndex() == 0) {
game.playSound(MagicSound.LOSE);
} else {
game.playSound(MagicSound.WIN);
}
}
public void showChoiceCardPopup() {
final MagicCardDefinition cardDefinition = getSourceCardDefinition();
if (cardDefinition != MagicCardDefinition.UNKNOWN) {
final Point point = userActionPanel.getLocationOnScreen();
viewInfoRight(cardDefinition, 0, new Rectangle(point.x, point.y-20, userActionPanel.getWidth(), userActionPanel.getHeight()));
}
}
/**
* devMode only currently.
*/
public void doSaveGame() {
if (isValidSaveState()) {
final File saveGameFile = MagicFileChoosers.getSaveGameFile(gamePanel);
if (saveGameFile != null) {
final GameState gameState = GameStateSnapshot.getGameState(game);
GameStateFileWriter.createSaveGameFile(gameState, saveGameFile.getName());
ScreenController.showInfoMessage("Game saved!");
}
} else {
ScreenController.showInfoMessage("Can not save game state at this time.");
}
}
public void createGameplayReport() {
setGamePaused(true);
try {
GameplayReport.createNewReport(game);
} catch (Exception ex) {
Logger.getLogger(GameplayReport.class.getName()).log(Level.WARNING, null, ex);
ScreenController.showWarningMessage("There was a problem creating the report :-\n\n" + ex.getMessage());
}
try {
GameplayReport.openReportDirectory();
} catch (Exception ex) {
Logger.getLogger(GameplayReport.class.getName()).log(Level.WARNING, null, ex);
ScreenController.showWarningMessage(
"There was a problem opening the reports folder at " +
MagicFileSystem.getDataPath(MagicFileSystem.DataPath.REPORTS).toString() +
" :-\n\n" + ex.getMessage()
);
}
setGamePaused(false);
}
private boolean isValidSaveState() {
final boolean isHumanTurn = game.getTurnPlayer().isHuman();
final boolean isHumanPriority = game.getPriorityPlayer().isHuman();
final boolean isStackEmpty = game.getStack().isEmpty();
final boolean isFirstMain = game.isPhase(MagicPhaseType.FirstMain);
return isHumanTurn && isHumanPriority && isFirstMain && isStackEmpty;
}
public void setGamePaused(final boolean isPaused) {
this.isPaused.set(isPaused);
}
@Override
public MagicSubType getLandSubTypeChoice(final MagicSource source) throws UndoClickedException {
final ColorChoicePanel choicePanel = waitForInput(new Callable<ColorChoicePanel>() {
@Override
public ColorChoicePanel call() {
return new ColorChoicePanel(SwingGameController.this, source);
}
});
return choicePanel.getColor().getLandSubType();
}
@Override
public boolean getPayBuyBackCostChoice(final MagicSource source, final String costText) throws UndoClickedException {
final MayChoicePanel kickerPanel = waitForInput(new Callable<MayChoicePanel>() {
@Override
public MayChoicePanel call() {
return new MayChoicePanel(
SwingGameController.this,
source,
MText.get(_S4, costText)
);
}
});
return kickerPanel.isYesClicked();
}
@Override
public MagicColor getColorChoice(final MagicSource source) throws UndoClickedException {
final ColorChoicePanel choicePanel = waitForInput(new Callable<ColorChoicePanel>() {
@Override
public ColorChoicePanel call() {
return new ColorChoicePanel(SwingGameController.this, source);
}
});
return choicePanel.getColor();
}
@Override
public int getMultiKickerCountChoice(final MagicSource source, final MagicManaCost cost, final int maximumCount, final String name) throws UndoClickedException {
final MultiKickerChoicePanel kickerPanel = waitForInput(new Callable<MultiKickerChoicePanel>() {
@Override
public MultiKickerChoicePanel call() {
return new MultiKickerChoicePanel(SwingGameController.this, source, cost, maximumCount, name);
}
});
return kickerPanel.getKicker();
}
@Override
public int getSingleKickerCountChoice(final MagicSource source, final MagicManaCost cost, final String name) throws UndoClickedException {
final MayChoicePanel kickerPanel = waitForInput(new Callable<MayChoicePanel>() {
@Override
public MayChoicePanel call() {
return new MayChoicePanel(
SwingGameController.this,
source,
MText.get(_S5, name, cost.getText()));
}
});
return kickerPanel.isYesClicked() ? 1 : 0;
}
@Override
public boolean getMayChoice(final MagicSource source, final String description) throws UndoClickedException {
final MayChoicePanel choicePanel = waitForInput(new Callable<MayChoicePanel>() {
@Override
public MayChoicePanel call() {
return new MayChoicePanel(SwingGameController.this, source, description);
}
});
return choicePanel.isYesClicked();
}
@Override
public boolean getTakeMulliganChoice(final MagicSource source, final MagicPlayer player) throws UndoClickedException {
final MayChoicePanel choicePanel = waitForInput(new Callable<MayChoicePanel>() {
@Override
public MayChoicePanel call() {
final boolean showMulliganScreen =
MulliganScreen.isActive() ||
(player.getHandSize() == DuelConfig.getInstance().getHandSize() &&
GeneralConfig.getInstance().showMulliganScreen());
if (showMulliganScreen) {
return new MulliganChoicePanel(SwingGameController.this, source, MText.get(_S6), player.getPrivateHand());
} else {
return new MayChoicePanel(SwingGameController.this, source, MText.get(_S6));
}
}
});
return choicePanel.isYesClicked();
}
@Override
public int getModeChoice(final MagicSource source, final List<Integer> availableModes) throws UndoClickedException {
final ModeChoicePanel choicePanel = waitForInput(new Callable<ModeChoicePanel>() {
@Override
public ModeChoicePanel call() {
return new ModeChoicePanel(SwingGameController.this, source, availableModes);
}
});
return choicePanel.getMode();
}
@Override
public int getPayManaCostXChoice(final MagicSource source, final int maximumX) throws UndoClickedException {
final ManaCostXChoicePanel choicePanel = waitForInput(new Callable<ManaCostXChoicePanel>() {
@Override
public ManaCostXChoicePanel call() {
return new ManaCostXChoicePanel(SwingGameController.this, source, maximumX);
}
});
return choicePanel.getValueForX();
}
@Override
public MagicPlayChoiceResult getPlayChoice(final MagicSource source, final List<MagicPlayChoiceResult> results) throws UndoClickedException {
final PlayChoicePanel choicePanel = waitForInput(new Callable<PlayChoicePanel>() {
@Override
public PlayChoicePanel call() {
return new PlayChoicePanel(SwingGameController.this, source, results);
}
});
return choicePanel.getResult();
}
public PlayerZoneViewer getPlayerZoneViewer() {
if (playerZoneViewer == null) {
playerZoneViewer = new PlayerZoneViewer(this);
}
return playerZoneViewer;
}
public void addPlayerZoneListener(final IPlayerZoneListener listener) {
playerZoneListeners.add(listener);
}
public void notifyPlayerZoneChanged(final PlayerViewerInfo playerInfo, final MagicPlayerZone zone) {
for (IPlayerZoneListener listener : playerZoneListeners) {
listener.setActivePlayerZone(playerInfo, zone);
}
}
@Override
public void refreshSidebarLayout() {
gamePanel.refreshSidebarLayout();
}
public void highlightCard(long magicCardId, boolean b) {
if (magicCardId > 0) {
final CardViewerInfo cardInfo = gameViewerInfo.getCardViewerInfo(magicCardId);
if (cardInfo.isNotEmpty()) {
gamePanel.highlightCard(cardInfo, b);
} else {
System.err.printf("Highlight failed! MagicCard #%d not found!\n", magicCardId);
}
}
}
public void showMagicCardImage(long magicCardId) {
if (magicCardId > 0) {
final CardViewerInfo cardInfo = gameViewerInfo.getCardViewerInfo(magicCardId);
if (cardInfo.isNotEmpty()) {
viewCardPopupCentered(cardInfo, 0);
} else {
System.err.printf("Highlight failed! MagicCard #%d not found!\n", magicCardId);
}
}
}
private void showLogScreen() {
ScreenController.showGameLogScreen();
}
private void showKeywordsScreen() {
ScreenController.showKeywordsScreen();
}
private void switchLogStackLayout() {
logStackViewer.switchLogVisibility();
}
public void setLogStackViewer(LogStackViewer logStackViewer) {
this.logStackViewer = logStackViewer;
}
public void showGameOptionsOverlay() {
if (duelPane == null) {
//do nothing
} else if (duelPane.getDialogPanel().isVisible()) {
duelPane.getDialogPanel().setVisible(false);
} else {
new GameOptionsOverlay(this);
}
}
public void setStackCount(int count) {
logStackViewer.setStackCount(count);
}
private int getStackItemPause() {
return isStackFastForward.get() == true ? 0 : CONFIG.getMessageDelay();
}
@Override
public void setStackFastForward(boolean b) {
isPauseCancelled.set(b);
isStackFastForward.set(b);
}
@Override
public boolean isStackFastForward() {
return isStackFastForward.get();
}
@Override
public void doStackItemPause() {
if (game.getStack().hasItem()) {
if (getStackItemPause() > 0) {
pause(getStackItemPause());
}
}
}
boolean waitingForUser() {
return userActionPanel.isActionEnabled();
}
}