diff --git a/gemp-lotr/db/update-game-history-date.sql b/gemp-lotr/db/update-game-history-date.sql new file mode 100644 index 000000000..369f926af --- /dev/null +++ b/gemp-lotr/db/update-game-history-date.sql @@ -0,0 +1,63 @@ + + + +SELECT * +FROM game_history gh +ORDER BY ID DESC + +ALTER TABLE gemp_db.game_history +ADD COLUMN start_time DATETIME NOT NULL DEFAULT now() AFTER start_date; + +ALTER TABLE gemp_db.game_history +ADD COLUMN end_time DATETIME NOT NULL DEFAULT now() AFTER end_date; + +UPDATE game_history +SET start_time = from_unixtime(floor(start_date/1000)), end_time = from_unixtime(floor(end_date/1000)); + +ALTER TABLE gemp_db.game_history +DROP COLUMN start_date; + +ALTER TABLE gemp_db.game_history +DROP COLUMN end_date; + +ALTER TABLE gemp_db.game_history +RENAME COLUMN start_time TO start_date; + +ALTER TABLE gemp_db.game_history +RENAME COLUMN end_time TO end_date; + +ALTER TABLE gemp_db.game_history ADD INDEX game_history_win_id_index (win_recording_id); +ALTER TABLE gemp_db.game_history ADD INDEX game_history_lose_id_index (lose_recording_id); + + + +ALTER TABLE gemp_db.game_history +ADD COLUMN winnerId INT(11) NOT NULL DEFAULT 0 AFTER winner; + +ALTER TABLE gemp_db.game_history +ADD COLUMN loserId INT(11) NOT NULL DEFAULT 0 AFTER loser; + +UPDATE game_history +INNER JOIN player P1 + ON P1.name = game_history.winner +INNER JOIN player P2 + ON P2.name = game_history.loser +SET winnerId = P1.id, loserId = P2.id; + +ALTER TABLE gemp_db.game_history +ADD CONSTRAINT fk_winnerId FOREIGN KEY (winnerId) REFERENCES player(id); + +ALTER TABLE gemp_db.game_history +ADD CONSTRAINT fk_loserId FOREIGN KEY (loserId) REFERENCES player(id); + + + + + + + + + + + + diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/GameHistoryRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/GameHistoryRequestHandler.java index 2409b5bfc..93e58baec 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/GameHistoryRequestHandler.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/GameHistoryRequestHandler.java @@ -15,6 +15,7 @@ import org.w3c.dom.Element; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.lang.reflect.Type; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; @@ -60,6 +61,8 @@ public class GameHistoryRequestHandler extends LotroServerRequestHandler impleme gameHistory.setAttribute("count", String.valueOf(recordCount)); gameHistory.setAttribute("playerId", resourceOwner.getName()); + var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + for (DBDefs.GameHistory game : playerGameHistory) { Element historyEntry = doc.createElement("historyEntry"); historyEntry.setAttribute("winner", game.winner); @@ -81,8 +84,8 @@ public class GameHistoryRequestHandler extends LotroServerRequestHandler impleme historyEntry.setAttribute("deckName", game.loser_deck_name); } - historyEntry.setAttribute("startTime", game.start_date.toLocalDate().toString()); - historyEntry.setAttribute("endTime", game.end_date.toLocalDate().toString()); + historyEntry.setAttribute("startTime", game.start_date.format(formatter)); + historyEntry.setAttribute("endTime", game.end_date.format(formatter)); gameHistory.appendChild(historyEntry); } diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/PlaytestRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/PlaytestRequestHandler.java index 77f8c53fc..8ba871619 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/PlaytestRequestHandler.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/PlaytestRequestHandler.java @@ -1,5 +1,6 @@ package com.gempukku.lotro.async.handler; +import com.alibaba.fastjson.JSON; import com.gempukku.lotro.async.HttpProcessingException; import com.gempukku.lotro.async.ResponseWriter; import com.gempukku.lotro.common.DBDefs; @@ -105,7 +106,7 @@ public class PlaytestRequestHandler extends LotroServerRequestHandler implements final List gameHistory = _gameHistoryService.getGameHistoryForFormat(format, count); - responseWriter.writeJsonResponse(JsonConvert.toJson(gameHistory)); + responseWriter.writeJsonResponse(JSON.toJSONString(gameHistory)); } finally { postDecoder.destroy(); diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/ServerStatsRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/ServerStatsRequestHandler.java index 782aa3d4e..7762a7ed3 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/ServerStatsRequestHandler.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/ServerStatsRequestHandler.java @@ -12,9 +12,10 @@ import io.netty.handler.codec.http.QueryStringDecoder; import org.apache.log4j.Logger; import java.lang.reflect.Type; -import java.text.ParseException; import java.text.SimpleDateFormat; -import java.util.Date; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.Map; import java.util.TimeZone; @@ -42,25 +43,29 @@ public class ServerStatsRequestHandler extends LotroServerRequestHandler impleme try { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); format.setTimeZone(TimeZone.getTimeZone("GMT")); - long from = format.parse(startDay).getTime(); - Date to = format.parse(startDay); + + //This convoluted conversion is actually necessary, for it to be flexible enough to take + //human-level dates such as 2023-2-13 (note the lack of zero padding) + var from = ZonedDateTime.ofInstant(format.parse(startDay).toInstant(), ZoneOffset.UTC); + + ZonedDateTime to = from; + switch (length) { - case "month" -> to.setMonth(to.getMonth() + 1); - case "week" -> to.setDate(to.getDate() + 7); - case "day" -> to.setDate(to.getDate() + 1); + case "month" -> to = from.plusMonths(1); + case "week" -> to = from.plusDays(7); + case "day" -> to = from.plusDays(1); default -> throw new HttpProcessingException(400); } - long duration = to.getTime() - from; var stats = new JSONDefs.PlayHistoryStats(); - stats.ActivePlayers = _gameHistoryService.getActivePlayersCount(from, duration); - stats.GamesCount = _gameHistoryService.getGamesPlayedCount(from, duration); - stats.StartDate = format.format(new Date(from)); - stats.EndDate = format.format(new Date(from + duration - 1)); - stats.Stats = _gameHistoryService.getGameHistoryStatistics(from, duration); + stats.ActivePlayers = _gameHistoryService.getActivePlayersCount(from, to); + stats.GamesCount = _gameHistoryService.getGamesPlayedCount(from, to); + stats.StartDate = from.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + stats.EndDate = to.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + stats.Stats = _gameHistoryService.getGameHistoryStatistics(from, to); responseWriter.writeJsonResponse(JSON.toJSONString(stats)); - } catch (ParseException exp) { + } catch (Exception exp) { logHttpError(_log, 400, request.uri(), exp); throw new HttpProcessingException(400); } diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/StatusRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/StatusRequestHandler.java index 20076f523..2ad2dfd0e 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/StatusRequestHandler.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/StatusRequestHandler.java @@ -10,6 +10,8 @@ import io.netty.handler.codec.http.HttpRequest; import org.apache.log4j.Logger; import java.lang.reflect.Type; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.Map; public class StatusRequestHandler extends LotroServerRequestHandler implements UriRequestHandler { @@ -30,11 +32,13 @@ public class StatusRequestHandler extends LotroServerRequestHandler implements U public void handleRequest(String uri, HttpRequest request, Map context, ResponseWriter responseWriter, String remoteIp) throws Exception { if (uri.equals("") && request.method() == HttpMethod.GET) { - int day = 1000 * 60 * 60 * 24; - int week = 1000 * 60 * 60 * 24 * 7; + var today = ZonedDateTime.now(ZoneOffset.UTC); + var yesterday = today.minusDays(1); + var lastWeek = today.minusDays(7); + String sb = "Tables count: " + _hallServer.getTablesCount() + ", players in hall: " + _chatServer.getChatRoom("Game Hall").getUsersInRoom(false).size() + - ", games played in last 24 hours: " + _gameHistoryService.getGamesPlayedCount(System.currentTimeMillis() - day, day) + - ", active players in last week: " + _gameHistoryService.getActivePlayersCount(System.currentTimeMillis() - week, week); + ", games played in last 24 hours: " + _gameHistoryService.getGamesPlayedCount(yesterday, today) + + ", active players in last week: " + _gameHistoryService.getActivePlayersCount(lastWeek, today); responseWriter.writeHtmlResponse(sb); } else { diff --git a/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/DBDefs.java b/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/DBDefs.java index b358cc56a..976430f72 100644 --- a/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/DBDefs.java +++ b/gemp-lotr/gemp-lotr-common/src/main/java/com/gempukku/lotro/common/DBDefs.java @@ -1,6 +1,7 @@ package com.gempukku.lotro.common; import java.time.LocalDateTime; +import java.time.ZonedDateTime; import java.util.Date; public class DBDefs { @@ -10,7 +11,9 @@ public class DBDefs { public int id; public String winner; + public int winnerId; public String loser; + public int loserId; public String win_reason; public String lose_reason; @@ -18,8 +21,8 @@ public class DBDefs { public String win_recording_id; public String lose_recording_id; - public long start_date; - public long end_date; + public ZonedDateTime start_date; + public ZonedDateTime end_date; public String format_name; @@ -28,18 +31,7 @@ public class DBDefs { public String tournament; - - public Date GetStartDate() - { - return new Date(start_date); - } - - public Date GetEndDate() - { - return new Date(end_date); - } - - + public int replay_version = -1; } public static class Collection { diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/EventSerializer.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/EventSerializer.java index affafde0f..27450b9e9 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/EventSerializer.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/EventSerializer.java @@ -8,6 +8,7 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Map; @@ -15,7 +16,10 @@ public class EventSerializer { public Node serializeEvent(Document doc, GameEvent gameEvent) { Element eventElem = doc.createElement("ge"); eventElem.setAttribute("type", gameEvent.getType().getCode()); - eventElem.setAttribute("timestamp", gameEvent.getTimestamp().toString()); + //TODO: + // - figure out a better date/time format for the log + // - import the json replay info into gemp + eventElem.setAttribute("timestamp", gameEvent.getTimestamp().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); if (gameEvent.getBlueprintId() != null) eventElem.setAttribute("blueprintId", gameEvent.getBlueprintId()); @@ -43,6 +47,8 @@ public class EventSerializer { eventElem.setAttribute("otherCardIds", arrayToCommaSeparated(gameEvent.getOtherCardIds())); if (gameEvent.getMessage() != null) eventElem.setAttribute("message", gameEvent.getMessage()); + if (gameEvent.getVersion() != null) + eventElem.setAttribute("version", gameEvent.getVersion().toString()); if (gameEvent.getGameStats() != null) serializeGameStats(doc, eventElem, gameEvent.getGameStats()); if (gameEvent.getAwaitingDecision() != null) diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameCommunicationChannel.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameCommunicationChannel.java index 9c9e92c63..4d7a4ae52 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameCommunicationChannel.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameCommunicationChannel.java @@ -5,7 +5,6 @@ import com.gempukku.lotro.communication.GameStateListener; import com.gempukku.lotro.game.PhysicalCard; import com.gempukku.lotro.logic.decisions.AwaitingDecision; import com.gempukku.lotro.logic.timing.GameStats; -import com.gempukku.lotro.logic.vo.LotroDeck; import com.gempukku.polling.LongPollableResource; import com.gempukku.polling.WaitingRequest; @@ -188,10 +187,6 @@ public class GameCommunicationChannel implements GameStateListener, LongPollable appendEvent(new GameEvent(DECISION).awaitingDecision(decision).participantId(playerId)); } - public void deckReadout(Map decks) { - appendEvent(new GameEvent(DECK_READOUT).decks(decks)); - } - @Override public void sendWarning(String playerId, String warning) { if (playerId.equals(_self)) diff --git a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameEvent.java b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameEvent.java index 8e3147aa3..d3db064f5 100644 --- a/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameEvent.java +++ b/gemp-lotr/gemp-lotr-logic/src/main/java/com/gempukku/lotro/game/state/GameEvent.java @@ -8,8 +8,8 @@ import com.gempukku.lotro.logic.decisions.AwaitingDecision; import com.gempukku.lotro.logic.timing.GameStats; import com.gempukku.lotro.logic.vo.LotroDeck; -import java.time.LocalDateTime; import java.time.ZoneOffset; +import java.time.ZonedDateTime; import java.util.List; import java.util.Map; @@ -23,7 +23,7 @@ public class GameEvent { ADD_TOKENS("AT"), REMOVE_TOKENS("RT"), SEND_MESSAGE("M"), SEND_WARNING("W"), GAME_STATS("GS"), - CHAT_MESSAGE("CM"), DECK_READOUT("DR"), + CHAT_MESSAGE("CM"), CARD_AFFECTED_BY_CARD("CAC"), SHOW_CARD_ON_SCREEN("EP"), FLASH_CARD_IN_PLAY("CA"), DECISION("D"); private final String code; @@ -55,14 +55,20 @@ public class GameEvent { private Map _decks; private GameStats _gameStats; private AwaitingDecision _awaitingDecision; - private LocalDateTime _timestamp; + private ZonedDateTime _timestamp; + private Integer _version; public GameEvent(Type type) { _type = type; - _timestamp = LocalDateTime.now(ZoneOffset.UTC); + _timestamp = ZonedDateTime.now(ZoneOffset.UTC); } - public LocalDateTime getTimestamp() { return _timestamp; } + public ZonedDateTime getTimestamp() { return _timestamp; } + public Integer getVersion() { return _version; } + public GameEvent version(int version) { + _version = version; + return this; + } public Integer getIndex() { return _index; diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/builder/ServerBuilder.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/builder/ServerBuilder.java index 7151bac98..fd2a5b710 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/builder/ServerBuilder.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/builder/ServerBuilder.java @@ -48,7 +48,8 @@ public class ServerBuilder { extract(objectMap, GameHistoryDAO.class))); objectMap.put(GameRecorder.class, new GameRecorder( - extract(objectMap, GameHistoryService.class))); + extract(objectMap, GameHistoryService.class), + extract(objectMap, PlayerDAO.class))); objectMap.put(CollectionsManager.class, new CollectionsManager( diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/DbGameHistoryDAO.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/DbGameHistoryDAO.java index c020b68a6..22efe7b13 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/DbGameHistoryDAO.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/DbGameHistoryDAO.java @@ -2,43 +2,62 @@ package com.gempukku.lotro.db; import com.gempukku.lotro.common.DBDefs; import com.gempukku.lotro.game.Player; +import org.sql2o.Query; import org.sql2o.Sql2o; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.*; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; public class DbGameHistoryDAO implements GameHistoryDAO { private final DbAccess _dbAccess; + private final DateTimeFormatter _dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd"); public DbGameHistoryDAO(DbAccess dbAccess) { _dbAccess = dbAccess; } - public void addGameHistory(String winner, String loser, String winReason, String loseReason, String winRecordingId, String loseRecordingId, String formatName, String tournament, String winnerDeckName, String loserDeckName, Date startDate, Date endDate) { + public void addGameHistory(String winner, int winnerId, String loser, int loserId, String winReason, String loseReason, String winRecordingId, String loseRecordingId, + String formatName, String tournament, String winnerDeckName, String loserDeckName, ZonedDateTime startDate, ZonedDateTime endDate, int replayVersion) { try { - try (Connection connection = _dbAccess.getDataSource().getConnection()) { - try (PreparedStatement statement = connection.prepareStatement("insert into game_history (winner, loser, win_reason, lose_reason, win_recording_id, lose_recording_id, format_name, tournament, winner_deck_name, loser_deck_name, start_date, end_date) values (?,?,?,?,?,?,?,?,?,?,?,?)")) { - statement.setString(1, winner); - statement.setString(2, loser); - statement.setString(3, winReason); - statement.setString(4, loseReason); - statement.setString(5, winRecordingId); - statement.setString(6, loseRecordingId); - statement.setString(7, formatName); - statement.setString(8, tournament); - statement.setString(9, winnerDeckName); - statement.setString(10, loserDeckName); - statement.setLong(11, startDate.getTime()); - statement.setLong(12, endDate.getTime()); + Sql2o db = new Sql2o(_dbAccess.getDataSource()); - statement.execute(); - } + String sql = """ + INSERT INTO game_history (winner, winnerId, loser, loserId, win_reason, lose_reason, win_recording_id, lose_recording_id, + format_name, tournament, winner_deck_name, loser_deck_name, start_date, end_date, replay_version) + VALUES (:winner, :winnerId, :loser, :loserId, :win_reason, :lose_reason, :win_recording_id, :lose_recording_id, + :format_name, :tournament, :winner_deck_name, :loser_deck_name, :start_date, :end_date, :version) + """; + + try (org.sql2o.Connection conn = db.beginTransaction()) { + Query query = conn.createQuery(sql, true); + query.addParameter("winner", winner) + .addParameter("winnerId", winnerId) + .addParameter("loser", loser) + .addParameter("loserId", loserId) + .addParameter("win_reason", winReason) + .addParameter("lose_reason", loseReason) + .addParameter("win_recording_id", winRecordingId) + .addParameter("lose_recording_id", loseRecordingId) + .addParameter("format_name", formatName) + .addParameter("tournament", tournament) + .addParameter("winner_deck_name", winnerDeckName) + .addParameter("loser_deck_name", loserDeckName) + .addParameter("start_date", startDate.format(DateTimeFormatter.ISO_DATE_TIME)) + .addParameter("end_date", endDate.format(DateTimeFormatter.ISO_DATE_TIME)) + .addParameter("version", replayVersion); + + query.executeUpdate(); + conn.commit(); } - } catch (SQLException exp) { - throw new RuntimeException("Unable to get count of player games", exp); + } catch (Exception ex) { + throw new RuntimeException("Unable to insert game history", ex); } } @@ -49,7 +68,16 @@ public class DbGameHistoryDAO implements GameHistoryDAO { Sql2o db = new Sql2o(_dbAccess.getDataSource()); try (org.sql2o.Connection conn = db.open()) { - String sql = "select winner, loser, win_reason, lose_reason, win_recording_id, lose_recording_id, format_name, tournament, winner_deck_name, loser_deck_name, start_date, end_date from game_history where winner=:playerName or loser=:playerName order by end_date desc limit :start, :count"; + String sql = """ + SELECT winner, winnerId, loser, loserId, win_reason, lose_reason, + win_recording_id, lose_recording_id, format_name, tournament, + winner_deck_name, loser_deck_name, start_date, end_date + FROM game_history + WHERE winner = :playerName + OR loser = :playerName + ORDER BY end_date DESC + LIMIT :start, :count; + """; List result = conn.createQuery(sql) .addParameter("playerName", player.getName()) .addParameter("start", start) @@ -64,6 +92,55 @@ public class DbGameHistoryDAO implements GameHistoryDAO { } + public DBDefs.GameHistory getGameHistory(String recordID) { + try { + + Sql2o db = new Sql2o(_dbAccess.getDataSource()); + + try (org.sql2o.Connection conn = db.open()) { + String sql = """ + SELECT winner, winnerId, loser, loserId, win_reason, lose_reason, + win_recording_id, lose_recording_id, format_name, tournament, + winner_deck_name, loser_deck_name, start_date, end_date + FROM game_history + WHERE win_recording_id = :recordID + OR lose_recording_id = :recordID; + """; + List result = conn.createQuery(sql) + .addParameter("recordID", recordID) + .executeAndFetch(DBDefs.GameHistory.class); + + return result.stream().findFirst().orElse(null); + } + } catch (Exception ex) { + throw new RuntimeException("Unable to retrieve game history for player", ex); + } + + } + + public boolean doesReplayIDExist(String id) { + try { + + Sql2o db = new Sql2o(_dbAccess.getDataSource()); + + try (org.sql2o.Connection conn = db.open()) { + String sql = """ + SELECT COUNT(*) + FROM game_history + WHERE win_recording_id = :id + OR lose_recording_id = :id + """; + Integer result = conn.createQuery(sql) + .addParameter("id", id) + .executeScalar(Integer.class); + + return result >= 1; + } + } catch (Exception ex) { + throw new RuntimeException("Unable to retrieve existence of replay ID", ex); + } + } + @Override public List getGameHistoryForFormat(String format, int count) { try { @@ -73,7 +150,7 @@ public class DbGameHistoryDAO implements GameHistoryDAO { try (org.sql2o.Connection conn = db.open()) { String sql = """ SELECT - winner, loser, win_reason, lose_reason, + winner, winnerId, loser, loserId, win_reason, lose_reason, win_recording_id, lose_recording_id, format_name, tournament, winner_deck_name, loser_deck_name, start_date, end_date @@ -82,12 +159,11 @@ public class DbGameHistoryDAO implements GameHistoryDAO { ORDER BY end_date DESC LIMIT :count """; - List result = conn.createQuery(sql) + + return conn.createQuery(sql) .addParameter("format", "%" + format + "%") .addParameter("count", count) .executeAndFetch(DBDefs.GameHistory.class); - - return result; } } catch (Exception ex) { throw new RuntimeException("Unable to retrieve game history by format", ex); @@ -96,85 +172,82 @@ public class DbGameHistoryDAO implements GameHistoryDAO { public int getGameHistoryForPlayerCount(Player player) { try { - try (Connection connection = _dbAccess.getDataSource().getConnection()) { - try (PreparedStatement statement = connection.prepareStatement("select count(*) from game_history where winner=? or loser=?")) { - statement.setString(1, player.getName()); - statement.setString(2, player.getName()); - try (ResultSet rs = statement.executeQuery()) { - if (rs.next()) - return rs.getInt(1); - else - return -1; - } - } + + Sql2o db = new Sql2o(_dbAccess.getDataSource()); + + try (org.sql2o.Connection conn = db.open()) { + String sql = """ + SELECT COUNT(*) + FROM game_history + WHERE winner = :player + OR loser = :player + """; + Integer result = conn.createQuery(sql) + .addParameter("player", player.getName()) + .executeScalar(Integer.class); + + return Objects.requireNonNullElse(result, -1); } - } catch (SQLException exp) { - throw new RuntimeException("Unable to get count of player games", exp); + } catch (Exception ex) { + throw new RuntimeException("Unable to retrieve count of player games", ex); } } - public int getActivePlayersCount(long from, long duration) { + public int getActivePlayersCount(ZonedDateTime from, ZonedDateTime to) { try { - try (Connection connection = _dbAccess.getDataSource().getConnection()) { - try (PreparedStatement statement = connection.prepareStatement( - "select count(*) from (SELECT winner FROM game_history where end_date>=? and end_date=? and end_date=? and end_date getCasualGamesPlayedPerFormat(long from, long duration) { - try { - try (Connection connection = _dbAccess.getDataSource().getConnection()) { - try (PreparedStatement statement = connection.prepareStatement("select count(*), format_name from game_history where (tournament is null or tournament like 'Casual %') and end_date>=? and end_date result = new HashMap<>(); - try (ResultSet rs = statement.executeQuery()) { - while (rs.next()) { - result.put(rs.getString(2), rs.getInt(1)); - } - } - return result; - } - } - } catch (SQLException exp) { - throw new RuntimeException("Unable to get count of games played", exp); - } - } - - public List GetAllGameFormatData(long from, long duration) { + public List GetAllGameFormatData(ZonedDateTime from, ZonedDateTime to) { try { Sql2o db = new Sql2o(_dbAccess.getDataSource()); @@ -185,13 +258,12 @@ public class DbGameHistoryDAO implements GameHistoryDAO { ,format_name AS Format ,CASE WHEN tournament IS NULL OR tournament LIKE 'Casual %' THEN 1 ELSE 0 END AS Casual FROM game_history - WHERE end_date >= :from - AND end_date < :to + WHERE end_date BETWEEN :from AND :to GROUP BY format_name, CASE WHEN tournament IS NULL OR tournament LIKE 'Casual %' THEN 1 ELSE 0 END """; List result = conn.createQuery(sql) - .addParameter("from", from) - .addParameter("to", from + duration) + .addParameter("from", from.format(_dateFormat)) + .addParameter("to", to.format(_dateFormat)) .executeAndFetch(DBDefs.FormatStats.class); return result; @@ -233,9 +305,14 @@ public class DbGameHistoryDAO implements GameHistoryDAO { Sql2o db = new Sql2o(_dbAccess.getDataSource()); try (org.sql2o.Connection conn = db.open()) { - String sql = "select winner, loser, win_reason, lose_reason, win_recording_id, lose_recording_id, format_name, " + - "tournament, winner_deck_name, loser_deck_name, start_date, end_date from game_history " + - "where format_name=:formatName order by end_date desc limit :count"; + String sql = """ + SELECT winner, winnerId, loser, loserId, win_reason, lose_reason, win_recording_id, lose_recording_id, format_name, + tournament, winner_deck_name, loser_deck_name, start_date, end_date + FROM game_history + WHERE format_name = :formatName + ORDER BY end_date DESC + LIMIT :count + """; List result = conn.createQuery(sql) .addParameter("formatName", requestedFormatName) .addParameter("count", count) diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/GameHistoryDAO.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/GameHistoryDAO.java index e7626e51a..da077c2bb 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/GameHistoryDAO.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/GameHistoryDAO.java @@ -3,26 +3,23 @@ package com.gempukku.lotro.db; import com.gempukku.lotro.common.DBDefs; import com.gempukku.lotro.game.Player; -import java.util.Date; +import java.time.ZonedDateTime; import java.util.List; -import java.util.Map; public interface GameHistoryDAO { - public void addGameHistory(String winner, String loser, String winReason, String loseReason, String winRecordingId, String loseRecordingId, String formatName, String tournament, String winnerDeckName, String loserDeckName, Date startDate, Date endDate); - + public void addGameHistory(String winner, int winnerId, String loser, int loserId, String winReason, String loseReason, String winRecordingId, String loseRecordingId, String formatName, String tournament, String winnerDeckName, String loserDeckName, ZonedDateTime startDate, ZonedDateTime endDate, int version); + public DBDefs.GameHistory getGameHistory(String recordID); + public boolean doesReplayIDExist(String id); public List getGameHistoryForPlayer(Player player, int start, int count); - public int getGameHistoryForPlayerCount(Player player); public List getGameHistoryForFormat(String format, int count); - public int getActivePlayersCount(long from, long duration); + public int getActivePlayersCount(ZonedDateTime from, ZonedDateTime to); - public int getGamesPlayedCount(long from, long duration); + public int getGamesPlayedCount(ZonedDateTime from, ZonedDateTime to); - public Map getCasualGamesPlayedPerFormat(long from, long duration); - - public List GetAllGameFormatData(long from, long duration); + public List GetAllGameFormatData(ZonedDateTime from, ZonedDateTime to); public List getCasualPlayerStatistics(Player player); diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/GameHistoryService.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/GameHistoryService.java index 7868366af..283146350 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/GameHistoryService.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/GameHistoryService.java @@ -4,7 +4,7 @@ import com.gempukku.lotro.common.DBDefs; import com.gempukku.lotro.db.GameHistoryDAO; import com.gempukku.lotro.db.PlayerStatistic; -import java.util.Date; +import java.time.ZonedDateTime; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -17,8 +17,13 @@ public class GameHistoryService { _gameHistoryDAO = gameHistoryDAO; } - public void addGameHistory(String winner, String loser, String winReason, String loseReason, String winRecordingId, String loseRecordingId, String formatName, String tournament, String winnerDeckName, String loserDeckName, Date startDate, Date endDate) { - _gameHistoryDAO.addGameHistory(winner, loser, winReason, loseReason, winRecordingId, loseRecordingId, formatName, tournament, winnerDeckName, loserDeckName, startDate, endDate); + public void addGameHistory(DBDefs.GameHistory gh) { + addGameHistory(gh.winner, gh.winnerId, gh.loser, gh.loserId, gh.win_reason, gh.lose_reason, gh.win_recording_id, gh.lose_recording_id, + gh.format_name, gh.tournament, gh.winner_deck_name, gh.loser_deck_name, gh.start_date, gh.end_date, gh.replay_version); + } + + public void addGameHistory(String winner, int winnerId, String loser, int loserId, String winReason, String loseReason, String winRecordingId, String loseRecordingId, String formatName, String tournament, String winnerDeckName, String loserDeckName, ZonedDateTime startDate, ZonedDateTime endDate, int version) { + _gameHistoryDAO.addGameHistory(winner, winnerId, loser, loserId, winReason, loseReason, winRecordingId, loseRecordingId, formatName, tournament, winnerDeckName, loserDeckName, startDate, endDate, version); Integer winnerCount = _playerGameCount.get(winner); Integer loserCount = _playerGameCount.get(loser); if (winnerCount != null) @@ -27,6 +32,14 @@ public class GameHistoryService { _playerGameCount.put(loser, loserCount + 1); } + public boolean doesReplayIDExist(String id) { + return _gameHistoryDAO.doesReplayIDExist(id); + } + + public DBDefs.GameHistory getGameHistory(String recordID) { + return _gameHistoryDAO.getGameHistory(recordID); + } + public int getGameHistoryForPlayerCount(Player player) { Integer result = _playerGameCount.get(player.getName()); if (result != null) @@ -44,20 +57,16 @@ public class GameHistoryService { return _gameHistoryDAO.getGameHistoryForFormat(format, count); } - public List getTrackableGames(int count) { - return _gameHistoryDAO.getLastGames("Second Edition", count); - } - - public int getActivePlayersCount(long from, long duration) { + public int getActivePlayersCount(ZonedDateTime from, ZonedDateTime duration) { return _gameHistoryDAO.getActivePlayersCount(from, duration); } - public int getGamesPlayedCount(long from, long duration) { + public int getGamesPlayedCount(ZonedDateTime from, ZonedDateTime duration) { return _gameHistoryDAO.getGamesPlayedCount(from, duration); } - public List getGameHistoryStatistics(long from, long duration) { - return _gameHistoryDAO.GetAllGameFormatData(from, duration); + public List getGameHistoryStatistics(ZonedDateTime from, ZonedDateTime to) { + return _gameHistoryDAO.GetAllGameFormatData(from, to); } public List getCasualPlayerStatistics(Player player) { diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/GameRecorder.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/GameRecorder.java index da35fb62c..b70b6df4e 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/GameRecorder.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/GameRecorder.java @@ -1,6 +1,8 @@ package com.gempukku.lotro.game; import com.gempukku.lotro.common.AppConfig; +import com.gempukku.lotro.common.DBDefs; +import com.gempukku.lotro.db.PlayerDAO; import com.gempukku.lotro.game.state.EventSerializer; import com.gempukku.lotro.game.state.GameCommunicationChannel; import com.gempukku.lotro.game.state.GameEvent; @@ -17,7 +19,12 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.*; -import java.util.*; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; import java.util.concurrent.ThreadLocalRandom; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; @@ -28,9 +35,164 @@ public class GameRecorder { private static final int _charsCount = _possibleChars.length(); private final GameHistoryService _gameHistoryService; + private final PlayerDAO _playerDAO; - public GameRecorder(GameHistoryService gameHistoryService) { + public GameRecorder(GameHistoryService gameHistoryService, PlayerDAO playerDAO) { _gameHistoryService = gameHistoryService; + _playerDAO = playerDAO; + } + + + public GameRecordingInProgress recordGame(LotroGameMediator lotroGame, LotroFormat format, final String tournamentName, final Map decks) { + final ZonedDateTime startDate = ZonedDateTime.now(ZoneOffset.UTC); + final Map recordingChannels = new HashMap<>(); + for (String playerId : lotroGame.getPlayersPlaying()) { + var recordChannel = new GameCommunicationChannel(playerId, 0); + lotroGame.addGameStateListener(playerId, recordChannel); + recordingChannels.put(playerId, recordChannel); + } + + return (winnerName, winReason, loserName, loseReason) -> { + final ZonedDateTime endDate = ZonedDateTime.now(ZoneOffset.UTC); + + var gameInfo = new DBDefs.GameHistory() {{ + winner = winnerName; + winnerId = _playerDAO.getPlayer(winnerName).getId(); + loser = loserName; + loserId = _playerDAO.getPlayer(loserName).getId(); + + win_reason = winReason; + lose_reason = loseReason; + + win_recording_id = getNewRecordingID(); + lose_recording_id = getNewRecordingID(); + + start_date = startDate; + end_date = endDate; + + format_name = format.getName(); + + winner_deck_name = decks.get(winner).getDeckName(); + loser_deck_name = decks.get(loser).getDeckName(); + + tournament = tournamentName; + + //Update this version as needed + replay_version = 1; + }}; + + var playerRecordingId = saveRecordedChannels(recordingChannels, gameInfo, decks); + _gameHistoryService.addGameHistory(gameInfo); + + if(format.isPlaytest()) + { + String url = "https://docs.google.com/forms/d/e/1FAIpQLSdKJrCmjoyUqDTusDcpNoWAmvkGdzQqTxWGpdNIFX9biCee-A/viewform?usp=pp_url&entry.1592109986="; + String winnerURL = "https://play.lotrtcgpc.net/gemp-lotr/game.html%3FreplayId%3D" + winnerName + "$" + playerRecordingId.get(winnerName); + String loserURL = "https://play.lotrtcgpc.net/gemp-lotr/game.html%3FreplayId%3D" + loserName + "$" + playerRecordingId.get(loserName); + url += winnerURL + "%20" + loserURL; + lotroGame.sendMessageToPlayers("Thank you for playtesting! If you have any feedback, bugs, or other issues to report about this match, please do so using this form."); + } + + }; + } + + public interface GameRecordingInProgress { + void finishRecording(String winner, String winReason, String loser, String loseReason); + } + + private File getRecordingFileVersion0(String playerId, String gameId) { + File gameReplayFolder = new File(AppConfig.getProperty("application.root"), "replay"); + File playerReplayFolder = new File(gameReplayFolder, playerId); + return new File(playerReplayFolder, gameId + ".xml.gz"); + } + + private File getRecordingFileVersion1(String playerId, String gameId, ZonedDateTime startDate) { + var gameReplayFolder = new File(AppConfig.getProperty("application.root"), "replay"); + var yearFolder = new File(gameReplayFolder, String.format("%04d", startDate.getYear())); + var monthFolder = new File(yearFolder, String.format("%02d", startDate.getMonthValue())); + var playerReplayFolder = new File(monthFolder, playerId); + return new File(playerReplayFolder, gameId + ".xml.gz"); + } + + private OutputStream getRecordingWriteStream(String playerId, String gameId, ZonedDateTime startDate) throws IOException { + File recordingFile = getRecordingFileVersion1(playerId, gameId, startDate); + recordingFile.getParentFile().mkdirs(); + + Deflater deflater = new Deflater(9); + return new DeflaterOutputStream(new FileOutputStream(recordingFile), deflater); + } + + private Map saveRecordedChannels(Map gameProgress, DBDefs.GameHistory gameInfo, Map decks) { + Map result = new HashMap<>(); + for (Map.Entry playerRecordings : gameProgress.entrySet()) { + String playerId = playerRecordings.getKey(); + String recordingId = ""; + if(playerId.equals(gameInfo.winner)) { + recordingId = gameInfo.win_recording_id; + } + else { + recordingId = gameInfo.lose_recording_id; + } + + final List gameEvents = playerRecordings.getValue().consumeGameEvents(); + + try { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document doc = documentBuilder.newDocument(); + Element gameReplay = doc.createElement("gameReplay"); + EventSerializer serializer = new EventSerializer(); + + var info = doc.createElement("info"); + + info.setAttribute("replay_version", String.valueOf(gameInfo.replay_version)); + info.setAttribute("winner", gameInfo.winner); + info.setAttribute("loser", gameInfo.loser); + info.setAttribute("win_reason", gameInfo.win_reason); + info.setAttribute("lose_reason", gameInfo.lose_reason); + info.setAttribute("start_date", gameInfo.start_date.toString()); + info.setAttribute("end_date", gameInfo.end_date.toString()); + info.setAttribute("format", gameInfo.format_name); + info.setAttribute("tournament", gameInfo.tournament); + + for(var pair : decks.entrySet()) { + String player = pair.getKey(); + var deck = pair.getValue(); + + var deckElement = doc.createElement("deckReadout"); + deckElement.setAttribute("playerId", player); + deckElement.setAttribute("name", deck.getDeckName()); + deckElement.setAttribute("rb", deck.getRingBearer()); + deckElement.setAttribute("ring", deck.getRing()); + deckElement.setAttribute("sites", String.join(",", deck.getSites())); + deckElement.setAttribute("deck", String.join(",", deck.getAdventureCards())); + + info.appendChild(deckElement); + } + + gameReplay.appendChild(info); + + for (GameEvent gameEvent : gameEvents) { + gameReplay.appendChild(serializer.serializeEvent(doc, gameEvent)); + } + + doc.appendChild(gameReplay); + + try (OutputStream replayStream = getRecordingWriteStream(playerId, recordingId, gameInfo.start_date)) { + // Prepare the DOM document for writing + Source source = new DOMSource(doc); + // Prepare the output file + Result streamResult = new StreamResult(replayStream); + // Write the DOM document to the file + Transformer xformer = TransformerFactory.newInstance().newTransformer(); + xformer.transform(source, streamResult); + } + } catch (Exception exp) { + + } + result.put(playerId, recordingId); + } + return result; } private String randomUid() { @@ -43,108 +205,32 @@ public class GameRecorder { return new String(chars); } - public InputStream getRecordedGame(String playerId, String gameId) throws IOException { - final File file = getRecordingFile(playerId, gameId); - if (!file.exists() || !file.isFile()) - return null; - return new InflaterInputStream(new FileInputStream(file)); - } - - public GameRecordingInProgress recordGame(LotroGameMediator lotroGame, LotroFormat format, final String tournament, final Map decks) { - final Date startData = new Date(); - final Map recordingChannels = new HashMap<>(); - for (String playerId : lotroGame.getPlayersPlaying()) { - var recordChannel = new GameCommunicationChannel(playerId, 0); - lotroGame.addGameStateListener(playerId, recordChannel); - recordingChannels.put(playerId, recordChannel); - } - - return (winner, winReason, loser, loseReason) -> { - for(var comm : recordingChannels.values()) { - comm.deckReadout(decks); - } - - var playerRecordingId = saveRecordedChannels(recordingChannels); - _gameHistoryService.addGameHistory(winner, loser, winReason, loseReason, - playerRecordingId.get(winner), playerRecordingId.get(loser), format.getName(), - tournament, decks.get(winner).getDeckName(), decks.get(loser).getDeckName(), startData, new Date()); - - if(format.isPlaytest()) - { - String url = "https://docs.google.com/forms/d/e/1FAIpQLSdKJrCmjoyUqDTusDcpNoWAmvkGdzQqTxWGpdNIFX9biCee-A/viewform?usp=pp_url&entry.1592109986="; - String winnerURL = "https://play.lotrtcgpc.net/gemp-lotr/game.html%3FreplayId%3D" + winner + "$" + playerRecordingId.get(winner); - String loserURL = "https://play.lotrtcgpc.net/gemp-lotr/game.html%3FreplayId%3D" + loser + "$" + playerRecordingId.get(loser); - url += winnerURL + "%20" + loserURL; - lotroGame.sendMessageToPlayers("Thank you for playtesting! If you have any feedback, bugs, or other issues to report about this match, please do so using this form."); - } - - }; - } - - public interface GameRecordingInProgress { - void finishRecording(String winner, String winReason, String loser, String loseReason); - } - - private File getRecordingFile(String playerId, String gameId) { - File gameReplayFolder = new File(AppConfig.getProperty("application.root"), "replay"); - File playerReplayFolder = new File(gameReplayFolder, playerId); - return new File(playerReplayFolder, gameId + ".xml.gz"); - } - - private OutputStream getRecordingWriteStream(String playerId, String gameId) throws IOException { - File recordingFile = getRecordingFile(playerId, gameId); - recordingFile.getParentFile().mkdirs(); - - Deflater deflater = new Deflater(9); - return new DeflaterOutputStream(new FileOutputStream(recordingFile), deflater); - } - - private Map saveRecordedChannels(Map gameProgress) { - Map result = new HashMap<>(); - for (Map.Entry playerRecordings : gameProgress.entrySet()) { - String playerId = playerRecordings.getKey(); - - String gameRecordingId = getRecordingId(playerId); - final List gameEvents = playerRecordings.getValue().consumeGameEvents(); - - try { - try (OutputStream replayStream = getRecordingWriteStream(playerId, gameRecordingId)) { - DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); - Document doc = documentBuilder.newDocument(); - Element gameReplay = doc.createElement("gameReplay"); - EventSerializer serializer = new EventSerializer(); - for (GameEvent gameEvent : gameEvents) { - gameReplay.appendChild(serializer.serializeEvent(doc, gameEvent)); - } - - doc.appendChild(gameReplay); - - // Prepare the DOM document for writing - Source source = new DOMSource(doc); - - // Prepare the output file - Result streamResult = new StreamResult(replayStream); - - // Write the DOM document to the file - Transformer xformer = TransformerFactory.newInstance().newTransformer(); - xformer.transform(source, streamResult); - } - } catch (Exception exp) { - - } - result.put(playerId, gameRecordingId); - } - return result; - } - - private String getRecordingId(String playerId) { - String result; - File recordingFile; + private String getNewRecordingID() { + String id; do { - result = randomUid(); - recordingFile = getRecordingFile(playerId, result); - } while (recordingFile.exists()); - return result; + id = randomUid(); + } while (_gameHistoryService.doesReplayIDExist(id)); + return id; + } + + public InputStream getRecordedGame(String playerId, String recordId) throws IOException { + var history = _gameHistoryService.getGameHistory(recordId); + + if(history == null) + return null; + + File recordingFile = null; + + if(history.replay_version == 0) { + recordingFile = getRecordingFileVersion0(playerId, recordId); + } + else if(history.replay_version == 1) { + recordingFile = getRecordingFileVersion1(playerId, recordId, history.start_date); + } + + if (recordingFile == null || !recordingFile.exists() || !recordingFile.isFile()) + return null; + + return new InflaterInputStream(new FileInputStream(recordingFile)); } } diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroGameMediator.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroGameMediator.java index 10a348e6d..79090be82 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroGameMediator.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroGameMediator.java @@ -353,12 +353,6 @@ public class LotroGameMediator { } } - public void readoutParticipantDecks() { - for(var comms : _communicationChannels.values()) { - comms.deckReadout(_playerDecks); - } - } - public GameCommunicationChannel getCommunicationChannel(Player player, int channelNumber) throws PrivateInformationException, SubscriptionConflictException, SubscriptionExpiredException { String playerName = player.getName(); if (!player.hasType(Player.Type.ADMIN) && !_allowSpectators && !_playersPlaying.contains(playerName)) diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroServer.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroServer.java index 0dd807022..3008548a6 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroServer.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/LotroServer.java @@ -126,7 +126,7 @@ public class LotroServer extends AbstractServer { decks.put(participant.getPlayerId(), participant.getDeck()); } - lotroGameMediator.sendMessageToPlayers("Players in the game are: " + players.toString()); + lotroGameMediator.sendMessageToPlayers("Players in the game are: " + players); final var gameRecordingInProgress = _gameRecorder.recordGame(lotroGameMediator, gameSettings.getLotroFormat(), tournamentName, decks); lotroGameMediator.addGameResultListener(