Improved reconnect and tournament handling. Reconnect time is now shown for disconneted players on player list and tournament panel. You can now reconnect (during 3 minutes) to a tournament also if meanwhile new game (after sideboarding ended) or round was started. Conceding the complete match in a tournament can no longer result in a draw, if you won games before. Quitting a tournament does now always end all active games of that quitting player.

This commit is contained in:
LevelX2 2014-03-31 02:24:59 +02:00
parent c76529bf91
commit 9ff5bcbd92
29 changed files with 282 additions and 109 deletions

View file

@ -109,6 +109,7 @@ public class MageServerImpl implements MageServer {
LogServiceImpl.instance.log(LogKeys.KEY_WRONG_VERSION, userName, version.toString(), Main.getVersion().toString(), sessionId);
throw new MageVersionException(version, Main.getVersion());
}
logger.info(new StringBuilder("RegisterClient - userName: ").append(userName).append(" sessionId = ").append(sessionId));
return SessionManager.getInstance().registerUser(sessionId, userName);
} catch (Exception ex) {
if (ex instanceof MageVersionException) {
@ -333,16 +334,16 @@ public class MageServerImpl implements MageServer {
return SessionManager.getInstance().extendUserSession(sessionId);
}
@Override
public void deregisterClient(final String sessionId) throws MageException {
execute("deregisterClient", sessionId, new Action() {
@Override
public void execute() {
SessionManager.getInstance().disconnect(sessionId, true);
logger.debug("Client deregistered ...");
}
});
}
// @Override
// public void deregisterClient(final String sessionId) throws MageException {
// execute("deregisterClient", sessionId, new Action() {
// @Override
// public void execute() {
// SessionManager.getInstance().disconnect(sessionId, true);
// logger.debug("Client deregistered ...");
// }
// });
// }
@Override
public void startMatch(final String sessionId, final UUID roomId, final UUID tableId) throws MageException {

View file

@ -37,6 +37,7 @@ import mage.interfaces.callback.ClientCallback;
import mage.players.net.UserData;
import mage.players.net.UserGroup;
import mage.server.util.ConfigSettings;
import mage.view.ChatMessage;
import mage.view.UserDataView;
import org.apache.log4j.Logger;
import org.jboss.remoting.callback.AsynchInvokerCallbackHandler;
@ -88,6 +89,8 @@ public class Session {
user = UserManager.getInstance().findUser(userName);
if (user.getHost().equals(host)) {
if (user.getSessionId().isEmpty()) {
// TODO Send Chat message to tables (user is not registered yet)
// ChatManager.getInstance().broadcast([CHAT ID TABLES], "has reconnected", ChatMessage.MessageColor.GREEN);
logger.info("Reconnecting session for " + userName);
} else {
//throw new MageException("This machine is already connected");
@ -124,7 +127,10 @@ public class Session {
userData = new UserData(UserGroup.PLAYER, userDataView.getAvatarId(), userDataView.isShowAbilityPickerForced());
user.setUserData(userData);
} else {
userData.setAvatarId(userDataView.getAvatarId());
if (userDataView.getAvatarId() == 51) { // Update special avatar if first avatar is selected
updateAvatar(userName, userData);
}
userData.setAvatarId(userDataView.getAvatarId());
userData.setShowAbilityPickerForced(userDataView.isShowAbilityPickerForced());
}
return true;

View file

@ -111,7 +111,7 @@ public class SessionManager {
LogServiceImpl.instance.log(LogKeys.KEY_SESSION_DISCONNECTED, sessionId);
}
} else {
logger.info("could not find session with id " + sessionId);
logger.info("disconnect: could not find session with id " + sessionId);
}
}

View file

@ -42,7 +42,6 @@ import mage.cards.decks.InvalidDeckException;
import mage.constants.RangeOfInfluence;
import mage.constants.TableState;
import mage.game.GameException;
import mage.game.GameOptions;
import mage.game.Seat;
import mage.game.Table;
import mage.game.draft.Draft;
@ -56,7 +55,6 @@ import mage.game.tournament.Tournament;
import mage.game.tournament.TournamentOptions;
import mage.game.tournament.TournamentPlayer;
import mage.players.Player;
import mage.server.challenge.ChallengeManager;
import mage.server.draft.DraftManager;
import mage.server.game.DeckValidatorFactory;
import mage.server.game.GameFactory;
@ -306,7 +304,7 @@ public class TableController {
}
else {
TournamentManager.getInstance().submitDeck(tournament.getId(), playerId, deck);
UserManager.getInstance().getUser(userId).removeConstructing(table.getId());
UserManager.getInstance().getUser(userId).removeConstructing(playerId);
}
}
@ -474,6 +472,14 @@ public class TableController {
if (!match.getPlayer(entry.getValue()).hasQuit()) {
User user = UserManager.getInstance().getUser(entry.getKey());
if (user != null) {
if (!user.isConnected()) {
// if the user is not connected but exits, the user is currently disconnected. So it's neccessary
// to join the user to the game here, so he can join the game, if he reconnects in time.
// remove a existing constructing for the player if it exists
user.removeConstructing(match.getPlayer(entry.getValue()).getPlayer().getId());
GameManager.getInstance().joinGame(match.getGame().getId(), user.getId());
}
logger.info(new StringBuilder("User ").append(user.getName()).append(" game started - matchId ").append(match.getId()).append(" userId: ").append(user.getId()));
user.gameStarted(match.getGame().getId(), entry.getValue());
if (creator == null) {
@ -486,10 +492,8 @@ public class TableController {
}
}
else {
TableManager.getInstance().removeTable(table.getId());
GameManager.getInstance().removeGame(match.getGame().getId());
logger.warn("Unable to find player " + entry.getKey());
break;
match.getPlayer(entry.getValue()).setQuit(true);
}
}
}
@ -658,7 +662,9 @@ public class TableController {
user.showUserMessage("Match info", sb.toString());
}
// remove table from user - table manager holds table for display of finished matches
user.removeTable(entry.getValue());
if (!table.isTournamentSubTable()) {
user.removeTable(entry.getValue());
}
}
}
}
@ -735,4 +741,29 @@ public class TableController {
return match;
}
public boolean isMatchTableStillValid() {
// check only normal match table
if (!table.isTournament() && !table.isTournamentSubTable()) {
int humanPlayers = 0;
int validHumanPlayers = 0;
if (match == null) {
return false;
}
for(Map.Entry<UUID, UUID> userPlayerEntry: userPlayerMap.entrySet()) {
MatchPlayer matchPlayer = match.getPlayer(userPlayerEntry.getValue());
if (matchPlayer.getPlayer().isHuman()) {
humanPlayers++;
if (!matchPlayer.hasQuit()) {
User user = UserManager.getInstance().getUser(userPlayerEntry.getKey());
if (user != null) {
validHumanPlayers++;
}
}
}
}
// if at least 2 human players are valid (multiplayer) or all human players are valid the table is valid
return validHumanPlayers >= 2 || validHumanPlayers == humanPlayers;
}
return true;
}
}

View file

@ -47,6 +47,7 @@ import mage.game.match.MatchPlayer;
import mage.game.tournament.Tournament;
import mage.game.tournament.TournamentOptions;
import mage.players.Player;
import mage.server.game.GameManager;
import mage.server.game.GamesRoomManager;
import org.apache.log4j.Logger;
@ -175,6 +176,20 @@ public class TableManager {
}
}
// remove user from all sub tables of a tournament
public void userQuitTournamentSubTables(UUID tournamentId, UUID userId) {
for (TableController controller: controllers.values()) {
if (controller.getTable().isTournamentSubTable() && controller.getTable().getTournament().getId().equals(tournamentId)) {
Match match = controller.getTable().getMatch();
if (match != null) {
if (match.getGame() != null) {
GameManager.getInstance().quitMatch(match.getGame().getId(), userId);
}
}
}
}
}
public boolean isTableOwner(UUID tableId, UUID userId) {
if (controllers.containsKey(tableId)) {
return controllers.get(tableId).isOwner(userId);
@ -341,20 +356,10 @@ public class TableManager {
logger.warn("Table expired: id = " + table.getId() + ", created_by=" + table.getControllerName() + ". Removing...");
toRemove.add(table.getId());
}
// remove immediately non tournament tables with no human players
// remove tables not valid anymore
else if (!table.isTournament()) {
boolean canBeRemoved = true;
for (MatchPlayer matchPlayer :table.getMatch().getPlayers()) {
Player player = matchPlayer.getPlayer();
if (player != null && player.isHuman() && !player.hasLeft()) {
canBeRemoved = false;
}
// tournament sub tables may not be removed as long the tournament is not finished
if(table.isTournamentSubTable() && table.getTournament().getEndTime() == null) {
canBeRemoved = false;
}
}
if (canBeRemoved) {
TableController tableController = getController(table.getId());
if (!tableController.isMatchTableStillValid()) {
logger.warn("Table with no active human player: id = " + table.getId() + ", created_by=" + table.getControllerName() + ". Removing...");
toRemove.add(table.getId());
}

View file

@ -34,6 +34,7 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import mage.cards.decks.Deck;
import mage.game.Table;
import mage.interfaces.callback.ClientCallback;
@ -42,6 +43,7 @@ import mage.server.draft.DraftSession;
import mage.server.game.GameManager;
import mage.server.game.GameSession;
import mage.server.tournament.TournamentSession;
import mage.server.util.SystemUtil;
import mage.view.TableClientMessage;
import org.apache.log4j.Logger;
@ -133,6 +135,13 @@ public class User {
return userState == UserState.Connected || userState == UserState.Reconnected;
}
public String getDisconnectDuration() {
long secondsDisconnected = SystemUtil.getDateDiff(lastActivity, new Date(), TimeUnit.SECONDS);
int minutes = (int) secondsDisconnected / 60;
int seconds = (int) secondsDisconnected % 60;
return new StringBuilder(Integer.toString(minutes)).append(":").append(seconds > 9 ? seconds: "0" + Integer.toString(seconds)).toString();
}
public Date getConnectionTime() {
return connectionTime;
}
@ -246,7 +255,7 @@ public class User {
}
for (Entry<UUID, TournamentSession> entry: constructing.entrySet()) {
entry.getValue().construct(0);
entry.getValue().construct(0); // TODO: Check if this is correct
}
for (Entry<UUID, Deck> entry: sideboarding.entrySet()) {
TableController controller = TableManager.getInstance().getController(entry.getKey());
@ -323,10 +332,15 @@ public class User {
}
public String getGameInfo() {
StringBuilder sb = new StringBuilder();
String disconnectInfo = "";
if (!isConnected()) {
disconnectInfo = new StringBuilder(" (discon. ").append(getDisconnectDuration()).append(")").toString();
}
int draft = 0, match = 0, sideboard = 0, tournament = 0, construct = 0;
for (Table table : tables.values()) {
for (Map.Entry<UUID, Table> tableEntry : tables.entrySet()) {
Table table = tableEntry.getValue();
if (table.isTournament()) {
switch (table.getState()) {
case CONSTRUCTING:
@ -339,6 +353,11 @@ public class User {
tournament++;
break;
}
if (!isConnected()) {
table.getTournament().getPlayer(tableEntry.getKey()).setDisconnectInfo(disconnectInfo);
} else {
table.getTournament().getPlayer(tableEntry.getKey()).setDisconnectInfo("");
}
} else {
switch (table.getState()) {
case SIDEBOARDING:
@ -365,6 +384,7 @@ public class User {
if (tournament > 0) {
sb.append("TP: ").append(tournament).append(" ");
}
sb.append(disconnectInfo);
return sb.toString();
}

View file

@ -102,13 +102,13 @@ public class UserManager {
public void disconnect(UUID userId, User.DisconnectReason reason) {
if (userId != null) {
ChatManager.getInstance().removeUser(userId, reason);
if (users.containsKey(userId)) {
User user = users.get(userId);
logger.debug(new StringBuilder("User ").append(user.getName()).append(" has lost connection userId:").append(userId));
users.get(userId).setSessionId("");
user.setSessionId(""); // Session will be set again with new id if user reconnects
ChatManager.getInstance().broadcast(userId, "has lost connection", MessageColor.BLACK);
logger.info(new StringBuilder("User ").append(user.getName()).append(" has lost connection userId:").append(userId));
}
ChatManager.getInstance().removeUser(userId, reason);
}
}
@ -143,6 +143,9 @@ public class UserManager {
return false;
}
/**
* Is the connection lost for more than 3 minutes, the user will be removed (within 3 minutes he can reconnect)
*/
private void checkExpired() {
Calendar expired = Calendar.getInstance();
expired.add(Calendar.MINUTE, -3) ;

View file

@ -112,9 +112,6 @@ public class GamesRoomImpl extends RoomImpl implements GamesRoom, Serializable {
List<UsersView> users = new ArrayList<>();
for (User user : UserManager.getInstance().getUsers()) {
StringBuilder sb = new StringBuilder(user.getGameInfo());
if (!user.isConnected()) {
sb.append(" (discon.)");
}
users.add(new UsersView(user.getName(), user.getInfo(), sb.toString()));
}
Collections.sort(users, new UserNameSorter());

View file

@ -53,6 +53,7 @@ import mage.server.UserManager;
import mage.server.draft.DraftController;
import mage.server.draft.DraftManager;
import mage.server.draft.DraftSession;
import mage.server.game.GameManager;
import mage.server.game.GamesRoomManager;
import mage.server.util.ThreadExecutor;
import mage.view.ChatMessage.MessageColor;
@ -91,6 +92,9 @@ public class TournamentController {
@Override
public void event(TableEvent event) {
switch (event.getEventType()) {
case CHECK_STATE_PLAYERS:
checkPlayersState();
break;
case INFO:
ChatManager.getInstance().broadcast(chatId, "", event.getMessage(), MessageColor.BLACK, true, MessageType.STATUS);
logger.debug(tournament.getId() + " " + event.getMessage());
@ -214,8 +218,8 @@ public class TournamentController {
table.setTournament(tournament);
TournamentPlayer player1 = pair.getPlayer1();
TournamentPlayer player2 = pair.getPlayer2();
tableManager.addPlayer(getPlayerSessionId(player1.getPlayer().getId()), table.getId(), player1.getPlayer(), player1.getPlayerType(), player1.getDeck());
tableManager.addPlayer(getPlayerSessionId(player2.getPlayer().getId()), table.getId(), player2.getPlayer(), player2.getPlayerType(), player2.getDeck());
tableManager.addPlayer(getPlayerUserId(player1.getPlayer().getId()), table.getId(), player1.getPlayer(), player1.getPlayerType(), player1.getDeck());
tableManager.addPlayer(getPlayerUserId(player2.getPlayer().getId()), table.getId(), player2.getPlayer(), player2.getPlayerType(), player2.getDeck());
tableManager.startTournamentSubMatch(null, table.getId());
pair.setMatch(tableManager.getMatch(table.getId()));
pair.setTableId(table.getId());
@ -242,7 +246,7 @@ public class TournamentController {
if (tournamentSessions.containsKey(playerId)) {
TournamentSession tournamentSession = tournamentSessions.get(playerId);
tournamentSession.construct(timeout);
UserManager.getInstance().getUser(getPlayerSessionId(playerId)).addConstructing(playerId, tournamentSession);
UserManager.getInstance().getUser(getPlayerUserId(playerId)).addConstructing(playerId, tournamentSession);
TournamentPlayer player = tournament.getPlayer(playerId);
player.setState(TournamentPlayerState.CONSTRUCTING);
}
@ -285,6 +289,8 @@ public class TournamentController {
String info;
if (tournament.isDoneConstructing()) {
info = new StringBuilder("during round ").append(tournament.getRounds().size()).toString();
// quit active matches of that tournament
TableManager.getInstance().userQuitTournamentSubTables(tournament.getId(), userId);
} else {
if (tPlayer.getState().equals(TournamentPlayerState.DRAFTING)) {
info = "during Draft phase";
@ -344,7 +350,7 @@ public class TournamentController {
return false;
}
private UUID getPlayerSessionId(UUID playerId) {
private UUID getPlayerUserId(UUID playerId) {
for (Entry<UUID, UUID> entry: userPlayerMap.entrySet()) {
if (entry.getValue().equals(playerId)) {
return entry.getKey();
@ -365,4 +371,28 @@ public class TournamentController {
public boolean isAbort() {
return tournament.isAbort();
}
public boolean isPlayerAlive(UUID playerId) {
if (tournamentSessions.containsKey(playerId)) {
return tournamentSessions.get(playerId).isKilled();
}
return false;
}
private void checkPlayersState() {
for (TournamentPlayer tournamentPlayer: tournament.getPlayers()) {
if (!tournamentPlayer.getEliminated()) {
if (tournamentSessions.containsKey(tournamentPlayer.getPlayer().getId())) {
if (tournamentSessions.get(tournamentPlayer.getPlayer().getId()).isKilled()) {
tournamentPlayer.setEliminated();
tournamentPlayer.setStateInfo("disconnected");
}
} else {
tournamentPlayer.setEliminated();
tournamentPlayer.setStateInfo("no tournament session");
}
}
}
}
}

View file

@ -39,7 +39,7 @@ import mage.interfaces.callback.ClientCallback;
import mage.server.User;
import mage.server.UserManager;
import mage.server.util.ThreadExecutor;
import mage.view.*;
import mage.view.TournamentView;
import org.apache.log4j.Logger;
/**
@ -123,6 +123,10 @@ public class TournamentSession {
killed = true;
}
public boolean isKilled() {
return killed;
}
private synchronized void setupTimeout(int seconds) {
if (futureTimeout != null && !futureTimeout.isDone()) {
return;

View file

@ -8,11 +8,13 @@ import mage.game.Game;
import mage.players.Player;
import java.io.File;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -164,4 +166,17 @@ public class SystemUtil {
System.out.println(sanitize("anPlsdf123 ") + "|");
System.out.println(sanitize("anPlsdf123\r\n") + "|");
}
/**
* Get a diff between two dates
*
* @param date1 the oldest date
* @param date2 the newest date
* @param timeUnit the unit in which you want the diff
* @return the diff value, in the provided unit
*/
public static long getDateDiff(Date date1, Date date2, TimeUnit timeUnit) {
long diffInMillies = date2.getTime() - date1.getTime();
return timeUnit.convert(diffInMillies, TimeUnit.MILLISECONDS);
}
}