Communication with HallServer is now less bandwith intensive, as only updates are sent on the line.
This commit is contained in:
@@ -40,6 +40,11 @@
|
||||
<artifactId>commons-collections</artifactId>
|
||||
<version>3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-lang</groupId>
|
||||
<artifactId>commons-lang</artifactId>
|
||||
<version>2.6</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.jersey</groupId>
|
||||
<artifactId>jersey-server</artifactId>
|
||||
|
||||
@@ -25,7 +25,7 @@ public class CachedPlayerDAO implements PlayerDAO, Cached {
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return _playerById.size()+_playerByName.size();
|
||||
return _playerById.size() + _playerByName.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -33,8 +33,10 @@ public class CachedPlayerDAO implements PlayerDAO, Cached {
|
||||
Player player = (Player) _playerById.get(id);
|
||||
if (player == null) {
|
||||
player = _delegate.getPlayer(id);
|
||||
_playerById.put(id, player);
|
||||
_playerByName.put(player.getName(), player);
|
||||
if (player != null) {
|
||||
_playerById.put(id, player);
|
||||
_playerByName.put(player.getName(), player);
|
||||
}
|
||||
}
|
||||
return player;
|
||||
}
|
||||
@@ -44,8 +46,10 @@ public class CachedPlayerDAO implements PlayerDAO, Cached {
|
||||
Player player = (Player) _playerByName.get(playerName);
|
||||
if (player == null) {
|
||||
player = _delegate.getPlayer(playerName);
|
||||
_playerById.put(player.getId(), player);
|
||||
_playerByName.put(player.getName(), player);
|
||||
if (player != null) {
|
||||
_playerById.put(player.getId(), player);
|
||||
_playerByName.put(player.getName(), player);
|
||||
}
|
||||
}
|
||||
return player;
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ public class LotroGameMediator {
|
||||
private boolean _allowSpectators;
|
||||
private boolean _cancellable;
|
||||
// private final int _maxSecondsForGamePerPlayer = 60 * 40; // 40 minutes
|
||||
private final int _channelInactivityTimeoutPeriod = 1000 * 60 * 5; // 5 minutes
|
||||
private final int _playerDecisionTimeoutPeriod = 1000 * 60 * 10; // 10 minutes
|
||||
|
||||
private ReentrantReadWriteLock _lock = new ReentrantReadWriteLock(true);
|
||||
@@ -97,8 +96,8 @@ public class LotroGameMediator {
|
||||
return _lotroGame.getWinnerPlayerId();
|
||||
}
|
||||
|
||||
public Set<String> getPlayersPlaying() {
|
||||
return Collections.unmodifiableSet(_playersPlaying);
|
||||
public List<String> getPlayersPlaying() {
|
||||
return new LinkedList<String>(_playersPlaying);
|
||||
}
|
||||
|
||||
public String getGameStatus() {
|
||||
@@ -236,7 +235,7 @@ public class LotroGameMediator {
|
||||
// Channel is stale (user no longer connected to game, to save memory, we remove the channel
|
||||
// User can always reconnect and establish a new channel
|
||||
GatheringParticipantCommunicationChannel channel = playerChannels.getValue();
|
||||
if (currentTime > channel.getLastConsumed().getTime() + _channelInactivityTimeoutPeriod) {
|
||||
if (currentTime > channel.getLastConsumed().getTime() + _playerDecisionTimeoutPeriod) {
|
||||
_lotroGame.removeGameStateListener(channel);
|
||||
_communicationChannels.remove(playerId);
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ public class AwaitingTable {
|
||||
return _players.containsKey(playerId);
|
||||
}
|
||||
|
||||
public Set<String> getPlayerNames() {
|
||||
return Collections.unmodifiableSet(_players.keySet());
|
||||
public List<String> getPlayerNames() {
|
||||
return new LinkedList<String>(_players.keySet());
|
||||
}
|
||||
|
||||
public Set<LotroGameParticipant> getPlayers() {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.gempukku.lotro.hall;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface HallChannelVisitor {
|
||||
public void channelNumber(int channelNumber);
|
||||
|
||||
public void serverTime(String serverTime);
|
||||
public void runningPlayerGame(String gameId);
|
||||
public void playerBusy(boolean busy);
|
||||
|
||||
public void addTournamentQueue(String queueId, Map<String, String> props);
|
||||
public void updateTournamentQueue(String queueId, Map<String, String> props);
|
||||
public void removeTournamentQueue(String queueId);
|
||||
|
||||
public void addTable(String tableId, Map<String, String> props);
|
||||
public void updateTable(String tableId, Map<String, String> props);
|
||||
public void removeTable(String tableId);
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.gempukku.lotro.hall;
|
||||
|
||||
import com.gempukku.lotro.game.Player;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class HallCommunicationChannel {
|
||||
private int _channelNumber;
|
||||
private long _lastConsumed;
|
||||
private Map<String, Map<String, String>> _tournamentQueuePropsOnClient = new HashMap<String, Map<String, String>>();
|
||||
private Map<String, Map<String, String>> _tablePropsOnClient = new HashMap<String, Map<String, String>>();
|
||||
|
||||
public HallCommunicationChannel(int channelNumber) {
|
||||
_channelNumber = channelNumber;
|
||||
}
|
||||
|
||||
public int getChannelNumber() {
|
||||
return _channelNumber;
|
||||
}
|
||||
|
||||
public long getLastConsumed() {
|
||||
return _lastConsumed;
|
||||
}
|
||||
|
||||
public synchronized void processCommunicationChannel(HallServer hallServer, Player player, final HallChannelVisitor hallChannelVisitor) {
|
||||
hallChannelVisitor.channelNumber(_channelNumber);
|
||||
|
||||
final Map<String, Map<String, String>> tournamentsOnServer = new HashMap<String, Map<String, String>>();
|
||||
final Map<String, Map<String, String>> tablesOnServer = new HashMap<String, Map<String, String>>();
|
||||
|
||||
hallServer.processHall(player,
|
||||
new HallInfoVisitor() {
|
||||
@Override
|
||||
public void serverTime(String time) {
|
||||
hallChannelVisitor.serverTime(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerBusy(boolean busy) {
|
||||
hallChannelVisitor.playerBusy(busy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTable(String tableId, String gameId, boolean watchable, TableStatus status, String statusDescription, String formatName, String tournamentName, List<String> playerIds, String winner) {
|
||||
Map<String, String> props = new HashMap<String, String>();
|
||||
props.put("gameId", gameId);
|
||||
props.put("watchable", String.valueOf(watchable));
|
||||
props.put("status", String.valueOf(status));
|
||||
props.put("statusDescription", statusDescription);
|
||||
props.put("format", formatName);
|
||||
props.put("tournament", tournamentName);
|
||||
props.put("players", StringUtils.join(playerIds, ","));
|
||||
if (winner != null)
|
||||
props.put("winner", winner);
|
||||
|
||||
tablesOnServer.put(tableId, props);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTournamentQueue(String tournamentQueueKey, int cost, String collectionName, String formatName, String tournamentQueueName, int playerCount, boolean playerSignedUp) {
|
||||
Map<String, String> props = new HashMap<String, String>();
|
||||
props.put("cost", String.valueOf(cost));
|
||||
props.put("collection", collectionName);
|
||||
props.put("format", formatName);
|
||||
props.put("queue", tournamentQueueName);
|
||||
props.put("playerCount", String.valueOf(playerCount));
|
||||
props.put("signedUp", String.valueOf(playerSignedUp));
|
||||
|
||||
tournamentsOnServer.put(tournamentQueueKey, props);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runningPlayerGame(String gameId) {
|
||||
hallChannelVisitor.runningPlayerGame(gameId);
|
||||
}
|
||||
});
|
||||
|
||||
for (Map.Entry<String, Map<String, String>> tournamentQueueOnClient : _tournamentQueuePropsOnClient.entrySet()) {
|
||||
String tournamentQueueId = tournamentQueueOnClient.getKey();
|
||||
Map<String, String> tournamentProps = tournamentQueueOnClient.getValue();
|
||||
Map<String, String> tournamentLatestProps = tournamentsOnServer.get(tournamentQueueId);
|
||||
if (tournamentLatestProps != null) {
|
||||
if (!tournamentProps.equals(tournamentLatestProps))
|
||||
hallChannelVisitor.updateTournamentQueue(tournamentQueueId, tournamentLatestProps);
|
||||
} else {
|
||||
hallChannelVisitor.removeTournamentQueue(tournamentQueueId);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Map<String, String>> tournamentQueueOnServer : tournamentsOnServer.entrySet())
|
||||
if (!_tournamentQueuePropsOnClient.containsKey(tournamentQueueOnServer.getKey()))
|
||||
hallChannelVisitor.addTournamentQueue(tournamentQueueOnServer.getKey(), tournamentQueueOnServer.getValue());
|
||||
|
||||
_tournamentQueuePropsOnClient = tournamentsOnServer;
|
||||
|
||||
for (Map.Entry<String, Map<String, String>> tableOnClient : _tablePropsOnClient.entrySet()) {
|
||||
String tableId = tableOnClient.getKey();
|
||||
Map<String, String> tableProps = tableOnClient.getValue();
|
||||
Map<String, String> tableLatestProps = tablesOnServer.get(tableId);
|
||||
if (tableLatestProps != null) {
|
||||
if (!tableProps.equals(tableLatestProps))
|
||||
hallChannelVisitor.updateTable(tableId, tableLatestProps);
|
||||
} else {
|
||||
hallChannelVisitor.removeTable(tableId);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Map<String, String>> tableOnServer : tablesOnServer.entrySet())
|
||||
if (!_tablePropsOnClient.containsKey(tableOnServer.getKey()))
|
||||
hallChannelVisitor.addTable(tableOnServer.getKey(), tableOnServer.getValue());
|
||||
|
||||
_tablePropsOnClient = tablesOnServer;
|
||||
|
||||
_lastConsumed = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
package com.gempukku.lotro.hall;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
|
||||
public interface HallInfoVisitor {
|
||||
public enum TableStatus {
|
||||
WAITING, PLAYING, FINISHED
|
||||
}
|
||||
|
||||
public void serverTime(String time);
|
||||
|
||||
public void playerIsWaiting(boolean waiting);
|
||||
public void playerBusy(boolean busy);
|
||||
|
||||
public void visitTable(String tableId, String gameId, boolean watchable, String tableStatus, String formatName, String tournamentName, Set<String> playerIds, String winner);
|
||||
|
||||
public void runningPlayerDraft(String draftId);
|
||||
|
||||
public void runningPlayerGame(String gameId);
|
||||
public void visitTable(String tableId, String gameId, boolean watchable, TableStatus status, String statusDescription, String formatName, String tournamentName, List<String> playerIds, String winner);
|
||||
|
||||
public void visitTournamentQueue(String tournamentQueueKey, int cost, String collectionName, String formatName, String tournamentQueueName, int playerCount, boolean playerSignedUp);
|
||||
|
||||
public void runningPlayerGame(String gameId);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ import com.gempukku.lotro.logic.timing.GameResultListener;
|
||||
import com.gempukku.lotro.logic.vo.LotroDeck;
|
||||
import com.gempukku.lotro.tournament.*;
|
||||
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
@@ -44,7 +46,8 @@ public class HallServer extends AbstractServer {
|
||||
private Map<String, AwaitingTable> _awaitingTables = new LinkedHashMap<String, AwaitingTable>();
|
||||
private Map<String, RunningTable> _runningTables = new LinkedHashMap<String, RunningTable>();
|
||||
|
||||
private Map<Player, Long> _lastVisitedPlayers = new HashMap<Player, Long>();
|
||||
private Map<Player, HallCommunicationChannel> _playerChannelCommunication = new HashMap<Player, HallCommunicationChannel>();
|
||||
private int _nextChannelNumber = 0;
|
||||
|
||||
private Map<String, Tournament> _runningTournaments = new LinkedHashMap<String, Tournament>();
|
||||
private Map<String, TournamentQueue> _tournamentQueues = new HashMap<String, TournamentQueue>();
|
||||
@@ -259,23 +262,51 @@ public class HallServer extends AbstractServer {
|
||||
}
|
||||
}
|
||||
|
||||
public void processHall(Player player, HallInfoVisitor visitor) {
|
||||
public void signupUserForHall(Player player, HallChannelVisitor hallChannelVisitor) {
|
||||
_hallDataAccessLock.readLock().lock();
|
||||
try {
|
||||
HallCommunicationChannel channel = new HallCommunicationChannel(_nextChannelNumber++);
|
||||
channel.processCommunicationChannel(this, player, hallChannelVisitor);
|
||||
_playerChannelCommunication.put(player, channel);
|
||||
} finally {
|
||||
_hallDataAccessLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void processHall(Player player, int channelNumber, HallChannelVisitor hallChannelVisitor) {
|
||||
_hallDataAccessLock.readLock().lock();
|
||||
try {
|
||||
HallCommunicationChannel communicationChannel = _playerChannelCommunication.get(player);
|
||||
if (communicationChannel != null) {
|
||||
if (communicationChannel.getChannelNumber() == channelNumber) {
|
||||
communicationChannel.processCommunicationChannel(this, player, hallChannelVisitor);
|
||||
} else {
|
||||
throw new WebApplicationException(Response.Status.CONFLICT);
|
||||
}
|
||||
} else {
|
||||
throw new WebApplicationException(Response.Status.GONE);
|
||||
}
|
||||
} finally {
|
||||
_hallDataAccessLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
protected void processHall(Player player, HallInfoVisitor visitor) {
|
||||
_hallDataAccessLock.readLock().lock();
|
||||
try {
|
||||
_lastVisitedPlayers.put(player, System.currentTimeMillis());
|
||||
visitor.playerIsWaiting(isPlayerBusy(player.getName()));
|
||||
visitor.serverTime(DateUtils.getStringDateWithHour());
|
||||
visitor.playerBusy(isPlayerBusy(player.getName()));
|
||||
|
||||
// First waiting
|
||||
for (Map.Entry<String, AwaitingTable> tableInformation : _awaitingTables.entrySet()) {
|
||||
final AwaitingTable table = tableInformation.getValue();
|
||||
|
||||
Set<String> players;
|
||||
List<String> players;
|
||||
if (table.getLeague() != null)
|
||||
players = Collections.<String>emptySet();
|
||||
players = Collections.<String>emptyList();
|
||||
else
|
||||
players = table.getPlayerNames();
|
||||
visitor.visitTable(tableInformation.getKey(), null, false, "Waiting", table.getLotroFormat().getName(), getTournamentName(table), players, null);
|
||||
visitor.visitTable(tableInformation.getKey(), null, false, HallInfoVisitor.TableStatus.WAITING, "Waiting", table.getLotroFormat().getName(), getTournamentName(table), players, null);
|
||||
}
|
||||
|
||||
// Then non-finished
|
||||
@@ -286,7 +317,7 @@ public class HallServer extends AbstractServer {
|
||||
LotroGameMediator lotroGameMediator = _lotroServer.getGameById(runningTable.getGameId());
|
||||
if (lotroGameMediator != null) {
|
||||
if (!lotroGameMediator.isFinished())
|
||||
visitor.visitTable(runningGame.getKey(), runningTable.getGameId(), player.getType().contains("a") || lotroGameMediator.isAllowSpectators(), lotroGameMediator.getGameStatus(), runningTable.getFormatName(), runningTable.getTournamentName(), lotroGameMediator.getPlayersPlaying(), lotroGameMediator.getWinner());
|
||||
visitor.visitTable(runningGame.getKey(), runningTable.getGameId(), player.getType().contains("a") || lotroGameMediator.isAllowSpectators(), HallInfoVisitor.TableStatus.PLAYING, lotroGameMediator.getGameStatus(), runningTable.getFormatName(), runningTable.getTournamentName(), lotroGameMediator.getPlayersPlaying(), lotroGameMediator.getWinner());
|
||||
else
|
||||
finishedTables.put(runningGame.getKey(), runningTable);
|
||||
}
|
||||
@@ -297,7 +328,7 @@ public class HallServer extends AbstractServer {
|
||||
final RunningTable runningTable = nonPlayingGame.getValue();
|
||||
LotroGameMediator lotroGameMediator = _lotroServer.getGameById(runningTable.getGameId());
|
||||
if (lotroGameMediator != null)
|
||||
visitor.visitTable(nonPlayingGame.getKey(), runningTable.getGameId(), false, lotroGameMediator.getGameStatus(), runningTable.getFormatName(), runningTable.getTournamentName(), lotroGameMediator.getPlayersPlaying(), lotroGameMediator.getWinner());
|
||||
visitor.visitTable(nonPlayingGame.getKey(), runningTable.getGameId(), false, HallInfoVisitor.TableStatus.FINISHED, lotroGameMediator.getGameStatus(), runningTable.getFormatName(), runningTable.getTournamentName(), lotroGameMediator.getPlayersPlaying(), lotroGameMediator.getWinner());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, TournamentQueue> tournamentQueueEntry : _tournamentQueues.entrySet()) {
|
||||
@@ -486,11 +517,11 @@ public class HallServer extends AbstractServer {
|
||||
}
|
||||
|
||||
long currentTime = System.currentTimeMillis();
|
||||
Map<Player, Long> visitCopy = new LinkedHashMap<Player, Long>(_lastVisitedPlayers);
|
||||
for (Map.Entry<Player, Long> lastVisitedPlayer : visitCopy.entrySet()) {
|
||||
if (currentTime > lastVisitedPlayer.getValue() + _playerInactivityPeriod) {
|
||||
Map<Player, HallCommunicationChannel> visitCopy = new LinkedHashMap<Player, HallCommunicationChannel>(_playerChannelCommunication);
|
||||
for (Map.Entry<Player, HallCommunicationChannel> lastVisitedPlayer : visitCopy.entrySet()) {
|
||||
if (currentTime > lastVisitedPlayer.getValue().getLastConsumed() + _playerInactivityPeriod) {
|
||||
Player player = lastVisitedPlayer.getKey();
|
||||
_lastVisitedPlayers.remove(player);
|
||||
_playerChannelCommunication.remove(player);
|
||||
leaveAwaitingTables(player);
|
||||
leaveQueues(player);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import com.gempukku.lotro.game.LotroCardBlueprintLibrary;
|
||||
import com.gempukku.lotro.game.LotroFormat;
|
||||
import com.gempukku.lotro.game.Player;
|
||||
import com.gempukku.lotro.game.formats.LotroFormatLibrary;
|
||||
import com.gempukku.lotro.hall.HallChannelVisitor;
|
||||
import com.gempukku.lotro.hall.HallException;
|
||||
import com.gempukku.lotro.hall.HallInfoVisitor;
|
||||
import com.gempukku.lotro.hall.HallServer;
|
||||
import com.gempukku.lotro.league.LeagueSerieData;
|
||||
import com.gempukku.lotro.league.LeagueService;
|
||||
@@ -122,6 +122,36 @@ public class HallResource extends AbstractResource {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Produces(MediaType.APPLICATION_XML)
|
||||
@Path("/update")
|
||||
public Document updateHall(
|
||||
@FormParam("participantId") String participantId,
|
||||
@FormParam("channelNumber") int channelNumber,
|
||||
@Context HttpServletRequest request,
|
||||
@Context HttpServletResponse response) throws ParserConfigurationException, Exception {
|
||||
Player resourceOwner = getResourceOwnerSafely(request, participantId);
|
||||
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
|
||||
Document doc = documentBuilder.newDocument();
|
||||
|
||||
Element hall = doc.createElement("hall");
|
||||
hall.setAttribute("currency", String.valueOf(_collectionManager.getPlayerCollection(resourceOwner, "permanent").getCurrency()));
|
||||
String motd = _hallServer.getMOTD();
|
||||
if (motd != null)
|
||||
hall.setAttribute("motd", motd);
|
||||
|
||||
_hallServer.processHall(resourceOwner, channelNumber, new SerializeHallInfoVisitor(doc, hall));
|
||||
|
||||
doc.appendChild(hall);
|
||||
|
||||
processDeliveryServiceNotification(request, response);
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_XML)
|
||||
public Document getHall(
|
||||
@@ -141,7 +171,7 @@ public class HallResource extends AbstractResource {
|
||||
if (motd != null)
|
||||
hall.setAttribute("motd", motd);
|
||||
|
||||
_hallServer.processHall(resourceOwner, new SerializeHallInfoVisitor(doc, hall));
|
||||
_hallServer.signupUserForHall(resourceOwner, new SerializeHallInfoVisitor(doc, hall));
|
||||
for (Map.Entry<String, LotroFormat> format : _formatLibrary.getHallFormats().entrySet()) {
|
||||
Element formatElem = doc.createElement("format");
|
||||
formatElem.setAttribute("type", format.getKey());
|
||||
@@ -248,7 +278,7 @@ public class HallResource extends AbstractResource {
|
||||
return doc;
|
||||
}
|
||||
|
||||
private class SerializeHallInfoVisitor implements HallInfoVisitor {
|
||||
private class SerializeHallInfoVisitor implements HallChannelVisitor {
|
||||
private Document _doc;
|
||||
private Element _hall;
|
||||
|
||||
@@ -258,47 +288,69 @@ public class HallResource extends AbstractResource {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serverTime(String time) {
|
||||
_hall.setAttribute("serverTime", time);
|
||||
public void channelNumber(int channelNumber) {
|
||||
_hall.setAttribute("channelNumber", String.valueOf(channelNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerIsWaiting(boolean waiting) {
|
||||
_hall.setAttribute("waiting", String.valueOf(waiting));
|
||||
public void serverTime(String serverTime) {
|
||||
_hall.setAttribute("serverTime", serverTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTable(String tableId, String gameId, boolean watchable, String tableStatus, String formatName, String tournamentName, Set<String> playerIds, String winner) {
|
||||
public void addTournamentQueue(String queueId, Map<String, String> props) {
|
||||
Element queue = _doc.createElement("queue");
|
||||
queue.setAttribute("action", "add");
|
||||
queue.setAttribute("id", queueId);
|
||||
for (Map.Entry<String, String> attribute : props.entrySet())
|
||||
queue.setAttribute(attribute.getKey(), attribute.getValue());
|
||||
_hall.appendChild(queue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTournamentQueue(String queueId, Map<String, String> props) {
|
||||
Element queue = _doc.createElement("queue");
|
||||
queue.setAttribute("action", "update");
|
||||
queue.setAttribute("id", queueId);
|
||||
for (Map.Entry<String, String> attribute : props.entrySet())
|
||||
queue.setAttribute(attribute.getKey(), attribute.getValue());
|
||||
_hall.appendChild(queue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeTournamentQueue(String queueId) {
|
||||
Element queue = _doc.createElement("queue");
|
||||
queue.setAttribute("action", "remove");
|
||||
queue.setAttribute("id", queueId);
|
||||
_hall.appendChild(queue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTable(String tableId, Map<String, String> props) {
|
||||
Element table = _doc.createElement("table");
|
||||
table.setAttribute("action", "add");
|
||||
table.setAttribute("id", tableId);
|
||||
if (gameId != null)
|
||||
table.setAttribute("gameId", gameId);
|
||||
table.setAttribute("status", tableStatus);
|
||||
table.setAttribute("watchable", String.valueOf(watchable));
|
||||
table.setAttribute("format", formatName);
|
||||
table.setAttribute("tournament", tournamentName);
|
||||
table.setAttribute("players", mergeStrings(playerIds));
|
||||
if (winner != null)
|
||||
table.setAttribute("winner", winner);
|
||||
for (Map.Entry<String, String> attribute : props.entrySet())
|
||||
table.setAttribute(attribute.getKey(), attribute.getValue());
|
||||
_hall.appendChild(table);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runningPlayerDraft(String draftId) {
|
||||
//To change body of implemented methods use File | Settings | File Templates.
|
||||
public void updateTable(String tableId, Map<String, String> props) {
|
||||
Element table = _doc.createElement("table");
|
||||
table.setAttribute("action", "update");
|
||||
table.setAttribute("id", tableId);
|
||||
for (Map.Entry<String, String> attribute : props.entrySet())
|
||||
table.setAttribute(attribute.getKey(), attribute.getValue());
|
||||
_hall.appendChild(table);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTournamentQueue(String tournamentQueueKey, int cost, String collectionName, String formatName,
|
||||
String tournamentQueueName, int playerCount, boolean playerSignedUp) {
|
||||
Element tournamentQueue = _doc.createElement("tournamentQueue");
|
||||
tournamentQueue.setAttribute("id", tournamentQueueKey);
|
||||
tournamentQueue.setAttribute("cost", String.valueOf(cost));
|
||||
tournamentQueue.setAttribute("tournament", tournamentQueueName);
|
||||
tournamentQueue.setAttribute("players", String.valueOf(playerCount));
|
||||
tournamentQueue.setAttribute("format", formatName);
|
||||
tournamentQueue.setAttribute("joined", String.valueOf(playerSignedUp));
|
||||
_hall.appendChild(tournamentQueue);
|
||||
public void removeTable(String tableId) {
|
||||
Element table = _doc.createElement("table");
|
||||
table.setAttribute("action", "remove");
|
||||
table.setAttribute("id", tableId);
|
||||
_hall.appendChild(table);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -307,6 +359,11 @@ public class HallResource extends AbstractResource {
|
||||
runningGame.setAttribute("id", gameId);
|
||||
_hall.appendChild(runningGame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playerBusy(boolean busy) {
|
||||
_hall.setAttribute("busy", String.valueOf(busy));
|
||||
}
|
||||
}
|
||||
|
||||
private String mergeStrings(Set<String> strings) {
|
||||
|
||||
@@ -14,6 +14,12 @@
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.eventHeader {
|
||||
font-size: 120%;
|
||||
font-weight: bolder;
|
||||
background-color: #666666;
|
||||
}
|
||||
|
||||
.leagueName, #stats .period, .tournamentName, .playerStatHeader {
|
||||
font-size: 200%;
|
||||
font-weight: bolder;
|
||||
|
||||
@@ -499,6 +499,19 @@ var GempLotrCommunication = Class.extend({
|
||||
dataType:"xml"
|
||||
});
|
||||
},
|
||||
updateHall:function (callback, channelNumber, errorMap) {
|
||||
$.ajax({
|
||||
type:"POST",
|
||||
url:this.url + "/hall/update",
|
||||
cache:false,
|
||||
data:{
|
||||
channelNumber:channelNumber,
|
||||
participantId:getUrlParam("participantId") },
|
||||
success:this.deliveryCheck(callback),
|
||||
error:this.errorCheck(errorMap),
|
||||
dataType:"xml"
|
||||
});
|
||||
},
|
||||
joinQueue:function (queueId, deckName, callback, errorMap) {
|
||||
$.ajax({
|
||||
type:"POST",
|
||||
|
||||
@@ -11,9 +11,8 @@ var GempLotrHallUI = Class.extend({
|
||||
tablesDiv:null,
|
||||
buttonsDiv:null,
|
||||
|
||||
showEmptyTableQueues:null,
|
||||
|
||||
pocketDiv:null,
|
||||
hallChannelId: null,
|
||||
|
||||
init:function (div, url, chat) {
|
||||
this.div = div;
|
||||
@@ -40,6 +39,12 @@ var GempLotrHallUI = Class.extend({
|
||||
|
||||
this.tablesDiv = $("<div></div>");
|
||||
this.tablesDiv.css({overflow:"auto", left:"0px", top:"0px", width:width + "px", height:(height - 30) + "px"});
|
||||
|
||||
this.addQueuesTable();
|
||||
this.addWaitingTablesTable();
|
||||
this.addPlayingTablesTable();
|
||||
this.addFinishedTablesTable();
|
||||
|
||||
this.div.append(this.tablesDiv);
|
||||
|
||||
this.buttonsDiv = $("<div></div>");
|
||||
@@ -90,8 +95,6 @@ var GempLotrHallUI = Class.extend({
|
||||
this.decksSelect = $("<select style='width: 220px'></select>");
|
||||
this.decksSelect.hide();
|
||||
|
||||
var showQueuesCheck = $("<input type='checkbox' id='showEmptyTableQueuesCheck' value='showQueues'/><label for='showEmptyTableQueuesCheck'>Show empty queues</label>");
|
||||
|
||||
this.buttonsDiv.append(this.supportedFormatsSelect);
|
||||
this.buttonsDiv.append(this.decksSelect);
|
||||
this.buttonsDiv.append(this.createTableButton);
|
||||
@@ -106,25 +109,163 @@ var GempLotrHallUI = Class.extend({
|
||||
|
||||
this.buttonsDiv.append(this.leaveTableButton);
|
||||
|
||||
this.buttonsDiv.append(" | ");
|
||||
this.buttonsDiv.append(showQueuesCheck);
|
||||
|
||||
this.div.append(this.buttonsDiv);
|
||||
|
||||
this.updateHall();
|
||||
this.getHall();
|
||||
this.updateDecks();
|
||||
},
|
||||
|
||||
addQueuesTable: function() {
|
||||
var header = $("<div class='eventHeader'></div>");
|
||||
|
||||
var content = $("<div></div>");
|
||||
|
||||
var toggleContent = $("<div>Toggle tournament queues</div>").button({
|
||||
icons: {
|
||||
primary: "ui-icon-circlesmall-minus"
|
||||
},
|
||||
text: false
|
||||
});
|
||||
toggleContent.css({width: "13px", height: "15px"});
|
||||
toggleContent.click(
|
||||
function() {
|
||||
if (toggleContent.button("option", "icons")["primary"] == "ui-icon-circlesmall-minus")
|
||||
toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-plus"});
|
||||
else
|
||||
toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-minus"});
|
||||
content.toggle("blind", {}, 200);
|
||||
});
|
||||
header.append(toggleContent);
|
||||
header.append(" Tournament queues");
|
||||
|
||||
var table = $("<table class='tables queues'></table>");
|
||||
table.append("<tr><th width='20%'>Format</th><th width='20%'>Collection</th><th width='20%'>Queue name</th><th width='10%'>Players</th><th width='10%'>Cost</th><th width='20%'>Actions</th></tr>");
|
||||
content.append(table);
|
||||
|
||||
this.tablesDiv.append(header);
|
||||
this.tablesDiv.append(content);
|
||||
},
|
||||
|
||||
addWaitingTablesTable: function() {
|
||||
var header = $("<div class='eventHeader'></div>");
|
||||
|
||||
var content = $("<div></div>");
|
||||
|
||||
var toggleContent = $("<div>Toggle waiting tables</div>").button({
|
||||
icons: {
|
||||
primary: "ui-icon-circlesmall-minus"
|
||||
},
|
||||
text: false
|
||||
});
|
||||
toggleContent.css({width: "13px", height: "15px"});
|
||||
toggleContent.click(
|
||||
function() {
|
||||
if (toggleContent.button("option", "icons")["primary"] == "ui-icon-circlesmall-minus")
|
||||
toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-plus"});
|
||||
else
|
||||
toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-minus"});
|
||||
content.toggle("blind", {}, 200);
|
||||
});
|
||||
header.append(toggleContent);
|
||||
header.append(" Waiting tables");
|
||||
|
||||
var table = $("<table class='tables waitingTables'></table>");
|
||||
table.append("<tr><th width='20%'>Format</th><th width='30%'>Tournament</th><th width='10%'>Status</th><th width='20%'>Players</th><th width='20%'>Actions</th></tr>");
|
||||
content.append(table);
|
||||
|
||||
this.tablesDiv.append(header);
|
||||
this.tablesDiv.append(content);
|
||||
},
|
||||
|
||||
addPlayingTablesTable: function() {
|
||||
var header = $("<div class='eventHeader'></div>");
|
||||
|
||||
var content = $("<div></div>");
|
||||
|
||||
var toggleContent = $("<div>Toggle playing tables</div>").button({
|
||||
icons: {
|
||||
primary: "ui-icon-circlesmall-minus"
|
||||
},
|
||||
text: false
|
||||
});
|
||||
toggleContent.css({width: "13px", height: "15px"});
|
||||
toggleContent.click(
|
||||
function() {
|
||||
if (toggleContent.button("option", "icons")["primary"] == "ui-icon-circlesmall-minus")
|
||||
toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-plus"});
|
||||
else
|
||||
toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-minus"});
|
||||
content.toggle("blind", {}, 200);
|
||||
});
|
||||
header.append(toggleContent);
|
||||
header.append(" Playing tables");
|
||||
|
||||
var table = $("<table class='tables playingTables'></table>");
|
||||
table.append("<tr><th width='20%'>Format</th><th width='30%'>Tournament</th><th width='10%'>Status</th><th width='20%'>Players</th><th width='20%'>Actions</th></tr>");
|
||||
content.append(table);
|
||||
|
||||
this.tablesDiv.append(header);
|
||||
this.tablesDiv.append(content);
|
||||
},
|
||||
|
||||
addFinishedTablesTable: function() {
|
||||
var header = $("<div class='eventHeader'></div>");
|
||||
|
||||
var content = $("<div></div>");
|
||||
|
||||
var toggleContent = $("<div>Toggle finished tables</div>").button({
|
||||
icons: {
|
||||
primary: "ui-icon-circlesmall-minus"
|
||||
},
|
||||
text: false
|
||||
});
|
||||
toggleContent.css({width: "13px", height: "15px"});
|
||||
toggleContent.click(
|
||||
function() {
|
||||
if (toggleContent.button("option", "icons")["primary"] == "ui-icon-circlesmall-minus")
|
||||
toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-plus"});
|
||||
else
|
||||
toggleContent.button("option", "icons", {primary: "ui-icon-circlesmall-minus"});
|
||||
content.toggle("blind", {}, 200);
|
||||
});
|
||||
header.append(toggleContent);
|
||||
header.append(" Finished tables");
|
||||
|
||||
var table = $("<table class='tables finishedTables'></table>");
|
||||
table.append("<tr><th width='20%'>Format</th><th width='30%'>Tournament</th><th width='10%'>Status</th><th width='20%'>Players</th><th width='20%'>Winner</th></tr>");
|
||||
content.append(table);
|
||||
|
||||
this.tablesDiv.append(header);
|
||||
this.tablesDiv.append(content);
|
||||
},
|
||||
|
||||
hallResized:function (width, height) {
|
||||
this.tablesDiv.css({overflow:"auto", left:"0px", top:"0px", width:width + "px", height:(height - 30) + "px"});
|
||||
this.buttonsDiv.css({left:"0px", top:(height - 30) + "px", width:width + "px", height:29 + "px", align:"right", backgroundColor:"#000000", "border-top-width":"1px", "border-top-color":"#ffffff", "border-top-style":"solid"});
|
||||
},
|
||||
|
||||
getHall: function() {
|
||||
var that = this;
|
||||
|
||||
this.comm.getHall(function(xml) {
|
||||
that.processHall(xml);
|
||||
});
|
||||
},
|
||||
|
||||
updateHall:function () {
|
||||
var that = this;
|
||||
|
||||
this.comm.getHall(function (xml) {
|
||||
that.processHall(xml);
|
||||
this.comm.updateHall(
|
||||
function (xml) {
|
||||
that.processHall(xml);
|
||||
}, this.hallChannelId,
|
||||
{
|
||||
"409":function() {
|
||||
that.chat.appendMessage("You have accessed Game Hall in another browser, press F5 (refresh) to regain access in this window");
|
||||
},
|
||||
"410":function() {
|
||||
that.chat.appendMessage("You have been inactive for too long, press F5 (refresh) to enter Game Hall again");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@@ -163,9 +304,11 @@ var GempLotrHallUI = Class.extend({
|
||||
},
|
||||
|
||||
processHall:function (xml) {
|
||||
var that = this;
|
||||
|
||||
var root = xml.documentElement;
|
||||
if (root.tagName == "hall") {
|
||||
this.tablesDiv.html("");
|
||||
this.hallChannelId = root.getAttribute("channelNumber");
|
||||
|
||||
var currency = root.getAttribute("currency");
|
||||
this.pocketDiv.html(formatPrice(currency));
|
||||
@@ -174,41 +317,171 @@ var GempLotrHallUI = Class.extend({
|
||||
if (motd != null)
|
||||
$("#motd").html("<b>MOTD:</b> " + motd);
|
||||
|
||||
var waiting = root.getAttribute("waiting") == "true";
|
||||
var busy = root.getAttribute("busy") == "true";
|
||||
|
||||
var tablesTable = $("<table class='tables'></table>");
|
||||
tablesTable.append("<tr><th width='20%'>Format</th><th width='30%'>Tournament</th><th width='10%'>Status</th><th width='20%'>Players</th><th width='20%'>Actions</th></tr>");
|
||||
var queues = root.getElementsByTagName("queue");
|
||||
for (var i = 0; i < queues.length; i++) {
|
||||
var queue = queues[i];
|
||||
var id = queue.getAttribute("id");
|
||||
var action = queue.getAttribute("action");
|
||||
if (action == "add" || action == "update") {
|
||||
var actionsField = $("<td></td>");
|
||||
|
||||
var tournamentQueues = root.getElementsByTagName("tournamentQueue");
|
||||
for (var i = 0; i < tournamentQueues.length; i++) {
|
||||
var tournamentQueue = tournamentQueues[i];
|
||||
var id = tournamentQueue.getAttribute("id");
|
||||
var tournamentName = tournamentQueue.getAttribute("tournament");
|
||||
var players = parseInt(tournamentQueue.getAttribute("players"));
|
||||
var formatName = tournamentQueue.getAttribute("format");
|
||||
var joined = tournamentQueue.getAttribute("joined");
|
||||
if (players > 0 || $("#showEmptyTableQueuesCheck").prop("checked"))
|
||||
this.appendTournamentQueue(tablesTable, id, formatName, tournamentName, players, joined);
|
||||
var joined = queue.getAttribute("signedUp");
|
||||
if (joined != "true") {
|
||||
var but = $("<button>Join queue</button>");
|
||||
$(but).button().click(
|
||||
function (event) {
|
||||
var deck = that.decksSelect.val();
|
||||
if (deck != null)
|
||||
that.comm.joinQueue(id, deck, function (xml) {
|
||||
that.processResponse(xml);
|
||||
});
|
||||
});
|
||||
actionsField.append(but);
|
||||
} else {
|
||||
var but = $("<button>Leave queue</button>");
|
||||
$(but).button().click(
|
||||
function (event) {
|
||||
var deck = that.decksSelect.val();
|
||||
if (deck != null)
|
||||
that.comm.leaveQueue(id, deck, function (xml) {
|
||||
that.processResponse(xml);
|
||||
});
|
||||
});
|
||||
actionsField.append(but);
|
||||
}
|
||||
|
||||
var row = $("<tr class='queue" + id + "'><td>" + queue.getAttribute("format") + "</td>" +
|
||||
"<td>" + queue.getAttribute("collection") + "</td>" +
|
||||
"<td>" + queue.getAttribute("queue") + "</td>" +
|
||||
"<td>" + queue.getAttribute("playerCount") + "</td>" +
|
||||
"<td>" + formatPrice(queue.getAttribute("cost")) + "</td>" +
|
||||
"</tr>");
|
||||
|
||||
row.append(actionsField);
|
||||
|
||||
if (action == "add") {
|
||||
$("table.queues", this.tablesDiv)
|
||||
.append(row);
|
||||
} else if (action == "update") {
|
||||
$(".queue" + id, this.tablesDiv).replaceWith(row);
|
||||
}
|
||||
} else if (action == "remove") {
|
||||
$(".queue" + id, this.tablesDiv).remove();
|
||||
}
|
||||
}
|
||||
|
||||
var tables = root.getElementsByTagName("table");
|
||||
for (var i = 0; i < tables.length; i++) {
|
||||
var table = tables[i];
|
||||
var id = table.getAttribute("id");
|
||||
var gameId = table.getAttribute("gameId");
|
||||
var status = table.getAttribute("status");
|
||||
var watchable = table.getAttribute("watchable");
|
||||
var playersAttr = table.getAttribute("players");
|
||||
var formatName = table.getAttribute("format");
|
||||
var tournamentName = table.getAttribute("tournament");
|
||||
var players = new Array();
|
||||
if (playersAttr.length > 0)
|
||||
players = playersAttr.split(",");
|
||||
var winner = table.getAttribute("winner");
|
||||
var action = table.getAttribute("action");
|
||||
if (action == "add" || action == "update") {
|
||||
var status = table.getAttribute("status");
|
||||
|
||||
var tableDiv = this.appendTable(tablesTable, id, gameId, watchable, status, formatName, tournamentName, players, waiting, winner);
|
||||
var gameId = table.getAttribute("gameId");
|
||||
var statusDescription = table.getAttribute("statusDescription");
|
||||
var watchable = table.getAttribute("watchable");
|
||||
var playersAttr = table.getAttribute("players");
|
||||
var formatName = table.getAttribute("format");
|
||||
var tournamentName = table.getAttribute("tournament");
|
||||
var players = new Array();
|
||||
if (playersAttr.length > 0)
|
||||
players = playersAttr.split(",");
|
||||
var winner = table.getAttribute("winner");
|
||||
|
||||
var row = $("<tr class='table" + id + "'></tr>");
|
||||
|
||||
row.append("<td>" + formatName + "</td>");
|
||||
row.append("<td>" + tournamentName + "</td>");
|
||||
row.append("<td>" + statusDescription + "</td>");
|
||||
|
||||
var playersStr = "";
|
||||
for (var i = 0; i < players.length; i++) {
|
||||
if (i > 0)
|
||||
playersStr += ", ";
|
||||
playersStr += players[i];
|
||||
}
|
||||
row.append("<td>" + playersStr + "</td>");
|
||||
|
||||
var lastField = $("<td></td>");
|
||||
if (status == "WAITING" && !busy) {
|
||||
var that = this;
|
||||
|
||||
var but = $("<button>Join table</button>");
|
||||
$(but).button().click(
|
||||
function (event) {
|
||||
var deck = that.decksSelect.val();
|
||||
if (deck != null)
|
||||
that.comm.joinTable(id, deck, function (xml) {
|
||||
that.processResponse(xml);
|
||||
});
|
||||
});
|
||||
lastField.append(but);
|
||||
}
|
||||
|
||||
if (status == "PLAYING" && watchable == "true") {
|
||||
var but = $("<button>Watch game</button>");
|
||||
$(but).button().click(
|
||||
function (event) {
|
||||
var participantId = getUrlParam("participantId");
|
||||
var participantIdAppend = "";
|
||||
if (participantId != null)
|
||||
participantIdAppend = "&participantId=" + participantId;
|
||||
location.href = "/gemp-lotr/game.html?gameId=" + gameId + participantIdAppend;
|
||||
});
|
||||
lastField.append(but);
|
||||
}
|
||||
|
||||
if (status == "FINISHED" && winner != null) {
|
||||
lastField.append(winner);
|
||||
}
|
||||
|
||||
row.append(lastField);
|
||||
|
||||
if (action == "add") {
|
||||
if (status == "WAITING") {
|
||||
$("table.waitingTables", this.tablesDiv)
|
||||
.append(row);
|
||||
} else if (status == "PLAYING") {
|
||||
$("table.playingTables", this.tablesDiv)
|
||||
.append(row);
|
||||
} else if (status == "FINISHED") {
|
||||
$("table.finishedTables", this.tablesDiv)
|
||||
.append(row);
|
||||
}
|
||||
} else if (action == "update") {
|
||||
if (status == "WAITING") {
|
||||
if ($(".table"+id, $("table.waitingTables")).length > 0) {
|
||||
$(".table" + id, this.tablesDiv).replaceWith(row);
|
||||
} else {
|
||||
$(".table" + id, this.tablesDiv).remove();
|
||||
$("table.waitingTables", this.tablesDiv)
|
||||
.append(row);
|
||||
}
|
||||
} else if (status == "PLAYING") {
|
||||
if ($(".table"+id, $("table.playingTables")).length > 0) {
|
||||
$(".table" + id, this.tablesDiv).replaceWith(row);
|
||||
} else {
|
||||
$(".table" + id, this.tablesDiv).remove();
|
||||
$("table.playingTables", this.tablesDiv)
|
||||
.append(row);
|
||||
}
|
||||
} else if (status == "FINISHED") {
|
||||
if ($(".table"+id, $("table.finishedTables")).length > 0) {
|
||||
$(".table" + id, this.tablesDiv).replaceWith(row);
|
||||
} else {
|
||||
$(".table" + id, this.tablesDiv).remove();
|
||||
$("table.finishedTables", this.tablesDiv)
|
||||
.append(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (action == "remove") {
|
||||
$(".table" + id, this.tablesDiv).remove();
|
||||
}
|
||||
}
|
||||
this.tablesDiv.append(tablesTable);
|
||||
|
||||
var skipReload = false;
|
||||
var games = root.getElementsByTagName("game");
|
||||
@@ -233,7 +506,7 @@ var GempLotrHallUI = Class.extend({
|
||||
this.supportedFormatsInitialized = true;
|
||||
}
|
||||
|
||||
if (waiting) {
|
||||
if (busy) {
|
||||
this.supportedFormatsSelect.hide();
|
||||
this.decksSelect.hide();
|
||||
this.createTableButton.hide();
|
||||
@@ -257,7 +530,8 @@ var GempLotrHallUI = Class.extend({
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
,
|
||||
|
||||
appendTournamentQueue:function (container, id, formatName, tournamentName, players, joined) {
|
||||
var row = $("<tr></tr>");
|
||||
@@ -298,7 +572,8 @@ var GempLotrHallUI = Class.extend({
|
||||
|
||||
container.append(row);
|
||||
|
||||
},
|
||||
}
|
||||
,
|
||||
|
||||
appendTable:function (container, id, gameId, watchable, status, formatName, tournamentName, players, waiting, winner) {
|
||||
var row = $("<tr></tr>");
|
||||
|
||||
Reference in New Issue
Block a user