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();
}