forked from External/mage
Network upgrade and new reconnection mode (#11527)
Network upgrade and new reconnection mode: * users can disconnect or close app without game progress loose now; * disconnect dialog will show active tables stats and additional options; * all active tables will be restored on reconnect (tables, tourneys, games, drafts, sideboarding, constructing); * user must use same server and username on next connection; * there are few minutes for reconnect until server kick off a disconnected player from all player's tables (concede/loose); * now you can safety reconnect after IP change (after proxy/vpn/wifi/router restart); Other improvements and fixes: * gui: main menu - improved switch panel button, added stats about current tables/panels; * gui: improved data sync and updates (fixes many use cases with empty battlefield, not started games/drafts/tourneys, not updatable drafts, etc); * gui: improved stability on game updates (fixes some random errors related to wrong threads); * server: fixed miss messages about player's disconnection problems for other players in the chat; * refactor: simplified and improved connection and network related code, deleted outdated code, added docs; * tests: improved load test to support lands only set for more stable performance/network testing (set TEST_AI_RANDOM_DECK_SETS = PELP and run test_TwoAIPlayGame_Multiple);
This commit is contained in:
parent
7f0558ff3c
commit
960e896903
71 changed files with 1274 additions and 802 deletions
|
|
@ -5,15 +5,13 @@ import mage.abilities.Ability;
|
|||
import mage.abilities.common.PassAbility;
|
||||
import mage.cards.Card;
|
||||
import mage.cards.Cards;
|
||||
import mage.cards.decks.Deck;
|
||||
import mage.cards.decks.DeckCardLists;
|
||||
import mage.cards.repository.CardInfo;
|
||||
import mage.cards.repository.CardRepository;
|
||||
import mage.choices.Choice;
|
||||
import mage.constants.ManaType;
|
||||
import mage.constants.PlayerAction;
|
||||
import mage.constants.Zone;
|
||||
import mage.game.*;
|
||||
import mage.game.Game;
|
||||
import mage.game.GameOptions;
|
||||
import mage.game.GameState;
|
||||
import mage.game.Table;
|
||||
import mage.game.command.Plane;
|
||||
import mage.game.events.Listener;
|
||||
import mage.game.events.PlayerQueryEvent;
|
||||
|
|
@ -46,7 +44,7 @@ import java.util.stream.Collectors;
|
|||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
/**
|
||||
* @author BetaSteward_at_googlemail.com
|
||||
* @author BetaSteward_at_googlemail.com, JayDi85
|
||||
*/
|
||||
public class GameController implements GameCallback {
|
||||
|
||||
|
|
@ -317,7 +315,6 @@ public class GameController implements GameCallback {
|
|||
}
|
||||
user.get().addGame(playerId, gameSession);
|
||||
logger.debug("Player " + player.getName() + ' ' + playerId + " has " + joinType + " gameId: " + game.getId());
|
||||
// TODO: force user update?!
|
||||
managerFactory.chatManager().broadcast(chatId, "", game.getPlayer(playerId).getLogName() + " has " + joinType + " the game", MessageColor.ORANGE, true, game, MessageType.GAME, null);
|
||||
checkStart();
|
||||
}
|
||||
|
|
@ -347,7 +344,7 @@ public class GameController implements GameCallback {
|
|||
}
|
||||
|
||||
private void sendInfoAboutPlayersNotJoinedYetAndTryToFixIt() {
|
||||
// runs every 5 secs untill all players join
|
||||
// runs every 10 secs untill all players join
|
||||
for (Player player : game.getPlayers().values()) {
|
||||
if (player.canRespond() && player.isHuman()) {
|
||||
Optional<User> requestedUser = getUserByPlayerId(player.getId());
|
||||
|
|
@ -358,7 +355,7 @@ public class GameController implements GameCallback {
|
|||
// join the game because player has not joined or was removed because of disconnect
|
||||
String problemPlayerFixes;
|
||||
user.removeConstructing(player.getId());
|
||||
managerFactory.gameManager().joinGame(game.getId(), user.getId());
|
||||
managerFactory.gameManager().joinGame(game.getId(), user.getId()); // will generate new session on empty
|
||||
logger.warn("Forced join of player " + player.getName() + " (" + user.getUserState() + ") to gameId: " + game.getId());
|
||||
if (user.isConnected()) {
|
||||
// init game session, see reconnect()
|
||||
|
|
@ -370,9 +367,8 @@ public class GameController implements GameCallback {
|
|||
session.init();
|
||||
managerFactory.gameManager().sendPlayerString(session.getGameId(), user.getId(), "");
|
||||
} else {
|
||||
problemPlayerFixes = "leave on broken game session";
|
||||
logger.error("Can't find game session for forced join, leave it: player " + player.getName() + " in gameId: " + game.getId());
|
||||
player.leave();
|
||||
throw new IllegalStateException("Wrong code usage: session can't be null cause it created in forced joinGame already");
|
||||
//player.leave();
|
||||
}
|
||||
} else {
|
||||
problemPlayerFixes = "leave on disconnected";
|
||||
|
|
@ -868,12 +864,12 @@ public class GameController implements GameCallback {
|
|||
}
|
||||
|
||||
private synchronized void multiAmount(UUID playerId, final List<MultiAmountMessage> messages,
|
||||
final int min, final int max, final Map<String, Serializable> options)
|
||||
final int min, final int max, final Map<String, Serializable> options)
|
||||
throws MageException {
|
||||
perform(playerId, playerId1 -> getGameSession(playerId1).getMultiAmount(messages, min, max, options));
|
||||
}
|
||||
|
||||
private void informOthers(UUID playerId) throws MageException {
|
||||
private void informOthers(UUID playerId) {
|
||||
StringBuilder message = new StringBuilder();
|
||||
if (game.getStep() != null) {
|
||||
message.append(game.getTurnStepType().toString()).append(" - ");
|
||||
|
|
@ -889,7 +885,7 @@ public class GameController implements GameCallback {
|
|||
}
|
||||
}
|
||||
|
||||
private void informOthers(List<UUID> players) throws MageException {
|
||||
private void informOthers(List<UUID> players) {
|
||||
// first player is always original controller
|
||||
Player controller = null;
|
||||
if (players != null && !players.isEmpty()) {
|
||||
|
|
@ -974,18 +970,19 @@ public class GameController implements GameCallback {
|
|||
*
|
||||
* @param playerId
|
||||
* @param command
|
||||
* @throws MageException
|
||||
*/
|
||||
private void perform(UUID playerId, Command command) throws MageException {
|
||||
private void perform(UUID playerId, Command command) {
|
||||
perform(playerId, command, true);
|
||||
}
|
||||
|
||||
private void perform(UUID playerId, Command command, boolean informOthers) throws MageException {
|
||||
private void perform(UUID playerId, Command command, boolean informOthers) {
|
||||
if (game.getPlayer(playerId).isGameUnderControl()) { // is the player controlling it's own turn
|
||||
if (gameSessions.containsKey(playerId)) {
|
||||
setupTimeout(playerId);
|
||||
command.execute(playerId);
|
||||
}
|
||||
// TODO: if watcher disconnects then game freezes with active timer, must be fix for such use case
|
||||
// same for another player (can be fixed by super-duper connection)
|
||||
if (informOthers) {
|
||||
informOthers(playerId);
|
||||
}
|
||||
|
|
@ -997,6 +994,8 @@ public class GameController implements GameCallback {
|
|||
command.execute(uuid);
|
||||
}
|
||||
}
|
||||
// TODO: if watcher disconnects then game freeze with active timer, must be fix for such use case
|
||||
// same for another player (can be fixed by super-duper connection)
|
||||
if (informOthers) {
|
||||
informOthers(players);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable {
|
|||
|
||||
private static final Logger LOGGER = Logger.getLogger(GamesRoomImpl.class);
|
||||
|
||||
private static final int MAX_FINISHED_TABLES = 50;
|
||||
|
||||
private static final ScheduledExecutorService UPDATE_EXECUTOR = Executors.newSingleThreadScheduledExecutor();
|
||||
private static List<TableView> tableView = new ArrayList<>();
|
||||
private static List<MatchView> matchView = new ArrayList<>();
|
||||
|
|
@ -65,7 +67,7 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable {
|
|||
for (Table table : allTables) {
|
||||
if (table.getState() != TableState.FINISHED) {
|
||||
tableList.add(new TableView(table));
|
||||
} else if (matchList.size() < 50) {
|
||||
} else if (matchList.size() < MAX_FINISHED_TABLES) {
|
||||
matchList.add(new MatchView(table));
|
||||
} else {
|
||||
// more since 50 matches finished since this match so removeUserFromAllTablesAndChat it
|
||||
|
|
@ -79,7 +81,8 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable {
|
|||
matchView = matchList;
|
||||
List<UsersView> users = new ArrayList<>();
|
||||
for (User user : managerFactory.userManager().getUsers()) {
|
||||
if (user.getUserState() != User.UserState.Offline && !user.getName().equals("Admin")) {
|
||||
if (user.getUserState() != User.UserState.Offline
|
||||
&& !user.getName().equals(User.ADMIN_NAME)) {
|
||||
try {
|
||||
users.add(new UsersView(user.getUserData().getFlagName(), user.getName(),
|
||||
user.getMatchHistory(), user.getMatchQuitRatio(), user.getTourneyHistory(),
|
||||
|
|
@ -244,12 +247,4 @@ class TableListSorter implements Comparator<Table> {
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class UserNameSorter implements Comparator<UsersView> {
|
||||
|
||||
@Override
|
||||
public int compare(UsersView one, UsersView two) {
|
||||
return one.getUserName().compareToIgnoreCase(two.getUserName());
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue