diff --git a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java index 512a6cfa8b..0d77f46ab1 100644 --- a/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java +++ b/Mage.Client/src/main/java/mage/client/remote/CallbackClientImpl.java @@ -399,7 +399,8 @@ public class CallbackClientImpl implements CallbackClient { } switch (usedPanel.getChatType()) { case GAME: - usedPanel.receiveMessage("", new StringBuilder("You may use hot keys to play faster:") + usedPanel.receiveMessage("", new StringBuilder() + .append("HOTKEYS:") .append("
Turn mousewheel up (ALT-e) - enlarge image of card the mousepointer hovers over") .append("
Turn mousewheel down (ALT-s) - enlarge original/alternate image of card the mousepointer hovers over") .append("
") @@ -429,12 +430,19 @@ public class CallbackClientImpl implements CallbackClient { .append("
") .append(KeyEvent.getKeyText(PreferencesDialog.getCurrentControlKey(PreferencesDialog.KEY_CONTROL_SWITCH_CHAT))) .append(" - Switth in/out to chat text field") + /* .append("
") .append(KeyEvent.getKeyText(PreferencesDialog.getCurrentControlKey(PreferencesDialog.KEY_CONTROL_TOGGLE_MACRO))) .append(" - Toggle recording a sequence of actions to repeat. Will not pause if interrupted and can fail if a selected card changes such as when scrying top card to bottom.") .append("
").append(System.getProperty("os.name").contains("Mac OS X") ? "Cmd" : "Ctrl").append(" + click - Hold priority while casting a spell or activating an ability") - .append("
").append("Type /fix message in chat to fix freezed game") - .append("
").append("Type /pings message in chat to show players and watchers ping") + */ + .append("
") + .append("
") + .append("CHAT COMMANDS:") + .append("
").append("/h username - show player's stats (history)") + .append("
").append("/w username message - send private message to player (whisper)") + .append("
").append("/pings - show players and watchers ping") + .append("
").append("/fix - fix freezed game") .toString(), null, MessageType.USER_INFO, ChatMessage.MessageColor.BLUE); break; diff --git a/Mage.Server/src/main/java/mage/server/ChatManager.java b/Mage.Server/src/main/java/mage/server/ChatManager.java index 82a8d2f7bd..041ceb5c8e 100644 --- a/Mage.Server/src/main/java/mage/server/ChatManager.java +++ b/Mage.Server/src/main/java/mage/server/ChatManager.java @@ -232,7 +232,7 @@ public enum ChatManager { if (entry.getKey().equals(id)) { GameController controller = entry.getValue(); if (controller != null) { - message += controller.attemptToFixGame(); + message += controller.attemptToFixGame(user); chatSessions.get(chatId).broadcastInfoToUser(user, message); } } diff --git a/Mage.Server/src/main/java/mage/server/User.java b/Mage.Server/src/main/java/mage/server/User.java index 447eb07df0..7cde19283f 100644 --- a/Mage.Server/src/main/java/mage/server/User.java +++ b/Mage.Server/src/main/java/mage/server/User.java @@ -37,7 +37,6 @@ public class User { private static final Logger logger = Logger.getLogger(User.class); public enum UserState { - Created, // Used if user is created an not connected to the session Connected, // Used if user is correctly connected Disconnected, // Used if the user lost connection diff --git a/Mage.Server/src/main/java/mage/server/game/GameController.java b/Mage.Server/src/main/java/mage/server/game/GameController.java index 5ec5bd688e..e80ca6a99e 100644 --- a/Mage.Server/src/main/java/mage/server/game/GameController.java +++ b/Mage.Server/src/main/java/mage/server/game/GameController.java @@ -1185,7 +1185,7 @@ public class GameController implements GameCallback { } private String getName(Player player) { - return player != null ? player.getName() : "-"; + return player != null ? player.getName() : "none"; } public String getPingsInfo() { @@ -1211,7 +1211,19 @@ public class GameController implements GameCallback { return String.join("
", usersInfo); } - public String attemptToFixGame() { + private String asGood(String text) { + return "" + text + ""; + } + + private String asBad(String text) { + return "" + text + ""; + } + + private String asWarning(String text) { + return "" + text + ""; + } + + public String attemptToFixGame(User user) { // try to fix disconnects if (game == null) { @@ -1222,7 +1234,7 @@ public class GameController implements GameCallback { return ""; } - logger.warn("FIX command was called for game " + game.getId() + " - players: " + + logger.warn("FIX command was called by " + user.getName() + " for game " + game.getId() + " - players: " + game.getPlayerList().stream() .map(game::getPlayer) .filter(Objects::nonNull) @@ -1230,100 +1242,137 @@ public class GameController implements GameCallback { .collect(Collectors.joining(", "))); StringBuilder sb = new StringBuilder(); - sb.append("
Game State:
"); - sb.append(state); + sb.append("FIX command called by " + user.getName() + ""); + sb.append(""); // font resize start for all next logs + sb.append("
Game ID: " + game.getId()); + + // pings info + sb.append("
"); + sb.append(getPingsInfo()); + boolean fixedAlready = false; - - Player activePlayer = game.getPlayer(state.getActivePlayerId()); - List fixActions = new ArrayList<>(); // for logs info // fix active - sb.append("
Checking active player: " + getName(activePlayer)); - if (activePlayer != null && activePlayer.hasLeft()) { - fixActions.add("active player"); - sb.append("
Found disconnected player! Concede..."); - activePlayer.concede(game); - activePlayer.leave(); // abort any wait response actions + Player playerActive = game.getPlayer(state.getActivePlayerId()); + sb.append("
Fixing active player: " + getName(playerActive)); + if (playerActive != null && !playerActive.canRespond()) { + fixActions.add("active player fix"); + sb.append("
WARNING, active player can't respond."); + sb.append("
Try to concede..."); + playerActive.concede(game); + playerActive.leave(); // abort any wait response actions + sb.append(" (" + asWarning("OK") + ", concede done)"); + + sb.append("
Try to skip step..."); Phase currentPhase = game.getPhase(); if (currentPhase != null) { currentPhase.getStep().skipStep(game, state.getActivePlayerId()); - sb.append("
Forcibly passing the phase!"); fixedAlready = true; + sb.append(" (" + asWarning("OK") + ", skip step done)"); } else { - sb.append("
Current phase null"); + sb.append(" (" + asBad("FAIL") + ", step is null)"); } - sb.append("
Active player has left"); + } else { + sb.append(playerActive != null ? " (" + asGood("OK") + ", can respond)" : " (" + asGood("OK") + ", no player)"); } // fix lost choosing dialog - sb.append("
Checking choosing player: " + getName(game.getPlayer(state.getChoosingPlayerId()))); - if (state.getChoosingPlayerId() != null) { - if (game.getPlayer(state.getChoosingPlayerId()).hasLeft()) { - fixActions.add("choosing player"); - sb.append("
Found disconnected player! Concede..."); - Player p = game.getPlayer(state.getChoosingPlayerId()); - if (p != null) { - p.concede(game); - p.leave(); // abort any wait response actions - } + Player choosingPlayer = game.getPlayer(state.getChoosingPlayerId()); + sb.append("
Fixing choosing player: " + getName(choosingPlayer)); + if (choosingPlayer != null && !choosingPlayer.canRespond()) { + fixActions.add("choosing player fix"); + + sb.append("
WARNING, choosing player can't respond."); + sb.append("
Try to concede..."); + choosingPlayer.concede(game); + choosingPlayer.leave(); // abort any wait response actions + sb.append(" (" + asWarning("OK") + ", concede done)"); + + sb.append("
Try to skip step..."); + if (fixedAlready) { + sb.append(" (OK, already skipped before)"); + } else { Phase currentPhase = game.getPhase(); - if (currentPhase != null && !fixedAlready) { - currentPhase.getStep().endStep(game, state.getActivePlayerId()); + if (currentPhase != null) { + currentPhase.getStep().skipStep(game, state.getActivePlayerId()); fixedAlready = true; - sb.append("
Forcibly passing the phase!"); - } else if (currentPhase == null) { - sb.append("
Current phase null"); + sb.append(" (" + asWarning("OK") + ", skip step done)"); + } else { + sb.append(" (" + asBad("FAIL") + ", step is null)"); } - sb.append("
Choosing player has left"); } + } else { + sb.append(choosingPlayer != null ? " (" + asGood("OK") + ", can respond)" : " (" + asGood("OK") + ", no player)"); } // fix lost priority - Player p = game.getPlayer(state.getPriorityPlayerId()); - sb.append("
Checking priority player: " + getName(game.getPlayer(state.getPriorityPlayerId()))); - if (p != null) { - if (p.hasLeft()) { - fixActions.add("priority player"); - sb.append("
Found disconnected player! Concede..."); - p.concede(game); - p.leave(); // abort any wait response actions + Player priorityPlayer = game.getPlayer(state.getPriorityPlayerId()); + sb.append("
Fixing priority player: " + getName(priorityPlayer)); + if (priorityPlayer != null && !priorityPlayer.canRespond()) { + fixActions.add("priority player fix"); + sb.append("
WARNING, priority player can't respond."); + sb.append("
Try to concede..."); + priorityPlayer.concede(game); + priorityPlayer.leave(); // abort any wait response actions + sb.append(" (" + asWarning("OK") + ", concede done)"); + + sb.append("
Try to skip step..."); + if (fixedAlready) { + sb.append(" (" + asWarning("OK") + ", already skipped before)"); + } else { Phase currentPhase = game.getPhase(); - if (currentPhase != null && !fixedAlready) { + if (currentPhase != null) { currentPhase.getStep().skipStep(game, state.getActivePlayerId()); fixedAlready = true; - sb.append("
Forcibly passing the phase!"); + sb.append(" (" + asWarning("OK") + ", skip step done)"); + } else { + sb.append(" (" + asBad("FAIL") + ", step is null)"); } } - sb.append("
"); + } else { + sb.append(priorityPlayer != null ? " (" + asGood("OK") + ", can respond)" : " (" + asGood("OK") + ", no player)"); } // fix timeout - sb.append("
Checking Future Timeout: "); + sb.append("
Fixing future timeout: "); if (futureTimeout != null) { - sb.append("Cancelled?="); - sb.append(futureTimeout.isCancelled()); - sb.append(",,,Done?="); - sb.append(futureTimeout.isDone()); - sb.append(",,,GetDelay?="); - sb.append((int) futureTimeout.getDelay(TimeUnit.SECONDS)); - if ((int) futureTimeout.getDelay(TimeUnit.SECONDS) < 25) { - fixActions.add("future timeout"); + sb.append("cancelled?=" + futureTimeout.isCancelled()); + sb.append("...done?=" + futureTimeout.isDone()); + int delay = (int) futureTimeout.getDelay(TimeUnit.SECONDS); + sb.append("...getDelay?=" + delay); + if (delay < 25) { + fixActions.add("future timeout fix"); + + sb.append("
WARNING, future timeout delay < 25"); + sb.append("
Try to pass..."); PassAbility pass = new PassAbility(); game.endTurn(pass); - sb.append("
Forcibly passing the turn!"); + sb.append(" (" + asWarning("OK") + ", pass done)"); + } else { + sb.append(" (" + asGood("OK") + ", delay > 25)"); } } else { - sb.append("Not using future Timeout!"); + sb.append(" (" + asGood("OK") + ", timeout is not using)"); } - sb.append("
"); + // TODO: fix non started game (send game started event to user?) + + + // ALL DONE if (fixActions.isEmpty()) { - fixActions.add("none actions"); + fixActions.add("none"); } - logger.warn("FIX command result for game " + game.getId() + ": " + fixActions.stream().collect(Collectors.joining(", "))); + String appliedFixes = fixActions.stream().collect(Collectors.joining(", ")); + sb.append("
Applied fixes: " + appliedFixes); + sb.append(""); // font resize end + sb.append("
"); + + logger.warn("FIX command result for game " + game.getId() + ": " + appliedFixes); + + System.out.println(sb.toString()); return sb.toString(); }