From 5a1da089910358a61b2acf205f8b6b2507064392 Mon Sep 17 00:00:00 2001 From: Christian 'ketura' McCarty Date: Sun, 12 Mar 2023 05:17:03 -0500 Subject: [PATCH] Added timestamps to all recorded replay events. Added a readout of the exact decks used to the end of replays. --- .../lotro/game/state/EventSerializer.java | 21 ++++++++ .../game/state/GameCommunicationChannel.java | 8 ++- .../gempukku/lotro/game/state/GameEvent.java | 19 +++++++ .../com/gempukku/lotro/game/GameRecorder.java | 15 ++++-- .../lotro/game/LotroGameMediator.java | 23 +++++---- .../com/gempukku/lotro/game/LotroServer.java | 51 ++++++++++--------- 6 files changed, 98 insertions(+), 39 deletions(-) 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 96fa5fe77..affafde0f 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 @@ -3,6 +3,7 @@ package com.gempukku.lotro.game.state; import com.gempukku.lotro.common.Zone; import com.gempukku.lotro.logic.decisions.AwaitingDecision; import com.gempukku.lotro.logic.timing.GameStats; +import com.gempukku.lotro.logic.vo.LotroDeck; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -14,6 +15,7 @@ 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()); if (gameEvent.getBlueprintId() != null) eventElem.setAttribute("blueprintId", gameEvent.getBlueprintId()); @@ -45,6 +47,8 @@ public class EventSerializer { serializeGameStats(doc, eventElem, gameEvent.getGameStats()); if (gameEvent.getAwaitingDecision() != null) serializeDecision(doc, eventElem, gameEvent.getAwaitingDecision()); + if (gameEvent.getDecks() != null) + serializeDecks(doc, eventElem, gameEvent.getDecks()); return eventElem; } @@ -74,6 +78,23 @@ public class EventSerializer { return sb.toString(); } + private void serializeDecks(Document document, Element eventElem, Map decks) { + for(var pair : decks.entrySet()) { + String player = pair.getKey(); + var deck = pair.getValue(); + + var deckElement = document.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())); + + eventElem.appendChild(deckElement); + } + } + private void serializeDecision(Document doc, Element eventElem, AwaitingDecision decision) { eventElem.setAttribute("id", String.valueOf(decision.getAwaitingDecisionId())); eventElem.setAttribute("decisionType", decision.getDecisionType().name()); 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 5bb5c81c5..9c9e92c63 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 @@ -3,14 +3,16 @@ package com.gempukku.lotro.game.state; import com.gempukku.lotro.common.Token; import com.gempukku.lotro.communication.GameStateListener; import com.gempukku.lotro.game.PhysicalCard; -import static com.gempukku.lotro.game.state.GameEvent.Type.*; 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; import java.util.*; +import static com.gempukku.lotro.game.state.GameEvent.Type.*; + public class GameCommunicationChannel implements GameStateListener, LongPollableResource { private List _events = Collections.synchronizedList(new LinkedList<>()); private final String _self; @@ -186,6 +188,10 @@ 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 d938d4432..8e3147aa3 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 @@ -6,8 +6,12 @@ import com.gempukku.lotro.common.Zone; 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 java.time.LocalDateTime; +import java.time.ZoneOffset; import java.util.List; +import java.util.Map; public class GameEvent { public enum Type { @@ -19,6 +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"), CARD_AFFECTED_BY_CARD("CAC"), SHOW_CARD_ON_SCREEN("EP"), FLASH_CARD_IN_PLAY("CA"), DECISION("D"); private final String code; @@ -47,13 +52,18 @@ public class GameEvent { private Integer _count; private Token _token; private int[] _otherCardIds; + private Map _decks; private GameStats _gameStats; private AwaitingDecision _awaitingDecision; + private LocalDateTime _timestamp; public GameEvent(Type type) { _type = type; + _timestamp = LocalDateTime.now(ZoneOffset.UTC); } + public LocalDateTime getTimestamp() { return _timestamp; } + public Integer getIndex() { return _index; } @@ -130,6 +140,15 @@ public class GameEvent { return this; } + public Map getDecks() { + return _decks; + } + + public GameEvent decks(Map decks) { + _decks = decks; + return this; + } + public String getParticipantId() { return _participantId; } 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 24c0ac297..da35fb62c 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 @@ -4,6 +4,7 @@ import com.gempukku.lotro.common.AppConfig; import com.gempukku.lotro.game.state.EventSerializer; import com.gempukku.lotro.game.state.GameCommunicationChannel; import com.gempukku.lotro.game.state.GameEvent; +import com.gempukku.lotro.logic.vo.LotroDeck; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -49,18 +50,24 @@ public class GameRecorder { return new InflaterInputStream(new FileInputStream(file)); } - public GameRecordingInProgress recordGame(LotroGameMediator lotroGame, LotroFormat format, final String tournament, final Map deckNames) { + 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()) { - GameCommunicationChannel recordChannel = new GameCommunicationChannel(playerId, 0); + var recordChannel = new GameCommunicationChannel(playerId, 0); lotroGame.addGameStateListener(playerId, recordChannel); recordingChannels.put(playerId, recordChannel); } return (winner, winReason, loser, loseReason) -> { - Map playerRecordingId = saveRecordedChannels(recordingChannels); - _gameHistoryService.addGameHistory(winner, loser, winReason, loseReason, playerRecordingId.get(winner), playerRecordingId.get(loser), format.getName(), tournament, deckNames.get(winner), deckNames.get(loser), startData, new Date()); + 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()) { 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 3ebf757ab..10a348e6d 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 @@ -29,6 +29,7 @@ public class LotroGameMediator { private final Map _playerClocks = new HashMap<>(); private final Map _decisionQuerySentTimes = new HashMap<>(); private final Set _playersPlaying = new HashSet<>(); + private final Map _playerDecks = new HashMap<>(); private final String _gameId; private final int _maxSecondsForGamePerPlayer; @@ -54,17 +55,15 @@ public class LotroGameMediator { if (participants.length < 1) throw new IllegalArgumentException("Game can't have less than one participant"); - Map decks = new HashMap<>(); - for (LotroGameParticipant participant : participants) { String participantId = participant.getPlayerId(); - decks.put(participantId, participant.getDeck()); + _playerDecks.put(participantId, participant.getDeck()); _playerClocks.put(participantId, 0); _playersPlaying.add(participantId); } _userFeedback = new DefaultUserFeedback(); - _lotroGame = new DefaultLotroGame(lotroFormat, decks, _userFeedback, library); + _lotroGame = new DefaultLotroGame(lotroFormat, _playerDecks, _userFeedback, library); _userFeedback.setGame(_lotroGame); } @@ -191,27 +190,27 @@ public class LotroGameMediator { PhysicalCard target = card.getAttachedTo(); int twilightCost = _lotroGame.getModifiersQuerying().getTwilightCost(_lotroGame, card, target, 0, false); sb.append("
Twilight cost: " + twilightCost); - } catch (UnsupportedOperationException exp) { + } catch (UnsupportedOperationException ignored) { } try { int strength = _lotroGame.getModifiersQuerying().getStrength(_lotroGame, card); sb.append("
Strength: " + strength); - } catch (UnsupportedOperationException exp) { + } catch (UnsupportedOperationException ignored) { } try { int vitality = _lotroGame.getModifiersQuerying().getVitality(_lotroGame, card); sb.append("
Vitality: " + vitality); - } catch (UnsupportedOperationException exp) { + } catch (UnsupportedOperationException ignored) { } try { int resistance = _lotroGame.getModifiersQuerying().getResistance(_lotroGame, card); sb.append("
Resistance: " + resistance); - } catch (UnsupportedOperationException exp) { + } catch (UnsupportedOperationException ignored) { } try { int siteNumber = _lotroGame.getModifiersQuerying().getMinionSiteNumber(_lotroGame, card); sb.append("
Site number: " + siteNumber); - } catch (UnsupportedOperationException exp) { + } catch (UnsupportedOperationException ignored) { } StringBuilder keywords = new StringBuilder(); @@ -354,6 +353,12 @@ 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 b2052e00d..0dd807022 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 @@ -104,46 +104,47 @@ public class LotroServer extends AbstractServer { gameSettings.getMaxSecondsPerPlayer(), gameSettings.getMaxSecondsPerDecision(), gameSettings.getLeague() != null || !gameSettings.isCompetitive(), !gameSettings.isCompetitive(), gameSettings.isPrivateGame()); lotroGameMediator.addGameResultListener( - new GameResultListener() { - @Override - public void gameFinished(String winnerPlayerId, String winReason, Map loserPlayerIdsWithReasons) { - _finishedGamesTime.put(gameId, new Date()); - } + new GameResultListener() { + @Override + public void gameFinished(String winnerPlayerId, String winReason, Map loserPlayerIdsWithReasons) { + _finishedGamesTime.put(gameId, new Date()); + } - @Override - public void gameCancelled() { - _finishedGamesTime.put(gameId, new Date()); - } - }); + @Override + public void gameCancelled() { + _finishedGamesTime.put(gameId, new Date()); + } + }); lotroGameMediator.sendMessageToPlayers("You're starting a game of " + gameSettings.getLotroFormat().getName()); StringBuilder players = new StringBuilder(); - Map deckNames = new HashMap<>(); + Map decks = new HashMap<>(); for (LotroGameParticipant participant : participants) { - deckNames.put(participant.getPlayerId(), participant.getDeck().getDeckName()); if (players.length() > 0) players.append(", "); players.append(participant.getPlayerId()); + decks.put(participant.getPlayerId(), participant.getDeck()); } lotroGameMediator.sendMessageToPlayers("Players in the game are: " + players.toString()); - final GameRecorder.GameRecordingInProgress gameRecordingInProgress = _gameRecorder.recordGame(lotroGameMediator, gameSettings.getLotroFormat(), tournamentName, deckNames); + final var gameRecordingInProgress = _gameRecorder.recordGame(lotroGameMediator, gameSettings.getLotroFormat(), tournamentName, decks); lotroGameMediator.addGameResultListener( - new GameResultListener() { - @Override - public void gameFinished(String winnerPlayerId, String winReason, Map loserPlayerIdsWithReasons) { - final Map.Entry loserEntry = loserPlayerIdsWithReasons.entrySet().iterator().next(); + new GameResultListener() { + @Override + public void gameFinished(String winnerPlayerId, String winReason, Map loserPlayerIdsWithReasons) { + final var loserEntry = loserPlayerIdsWithReasons.entrySet().iterator().next(); - - gameRecordingInProgress.finishRecording(winnerPlayerId, winReason, loserEntry.getKey(), loserEntry.getValue()); - } - - @Override - public void gameCancelled() { - gameRecordingInProgress.finishRecording(participants[0].getPlayerId(), "Game cancelled due to error", participants[1].getPlayerId(), "Game cancelled due to error"); - } + //potentially this is where to kick off any "reveal deck" events + //lotroGameMediator.readoutParticipantDecks(); + gameRecordingInProgress.finishRecording(winnerPlayerId, winReason, loserEntry.getKey(), loserEntry.getValue()); } + + @Override + public void gameCancelled() { + gameRecordingInProgress.finishRecording(participants[0].getPlayerId(), "Game cancelled due to error", participants[1].getPlayerId(), "Game cancelled due to error"); + } + } ); _runningGames.put(gameId, lotroGameMediator);