Added timestamps to all recorded replay events. Added a readout of the exact decks used to the end of replays.

This commit is contained in:
Christian 'ketura' McCarty
2023-03-12 05:17:03 -05:00
parent 752c286443
commit 5a1da08991
6 changed files with 98 additions and 39 deletions

View File

@@ -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<String, LotroDeck> 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());

View File

@@ -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<GameEvent> _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<String, LotroDeck> decks) {
appendEvent(new GameEvent(DECK_READOUT).decks(decks));
}
@Override
public void sendWarning(String playerId, String warning) {
if (playerId.equals(_self))

View File

@@ -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<String, LotroDeck> _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<String, LotroDeck> getDecks() {
return _decks;
}
public GameEvent decks(Map<String, LotroDeck> decks) {
_decks = decks;
return this;
}
public String getParticipantId() {
return _participantId;
}

View File

@@ -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<String, String> deckNames) {
public GameRecordingInProgress recordGame(LotroGameMediator lotroGame, LotroFormat format, final String tournament, final Map<String, LotroDeck> decks) {
final Date startData = new Date();
final Map<String, GameCommunicationChannel> 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<String, String> 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())
{

View File

@@ -29,6 +29,7 @@ public class LotroGameMediator {
private final Map<String, Integer> _playerClocks = new HashMap<>();
private final Map<String, Long> _decisionQuerySentTimes = new HashMap<>();
private final Set<String> _playersPlaying = new HashSet<>();
private final Map<String, LotroDeck> _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<String, LotroDeck> 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("<br><b>Twilight cost:</b> " + twilightCost);
} catch (UnsupportedOperationException exp) {
} catch (UnsupportedOperationException ignored) {
}
try {
int strength = _lotroGame.getModifiersQuerying().getStrength(_lotroGame, card);
sb.append("<br><b>Strength:</b> " + strength);
} catch (UnsupportedOperationException exp) {
} catch (UnsupportedOperationException ignored) {
}
try {
int vitality = _lotroGame.getModifiersQuerying().getVitality(_lotroGame, card);
sb.append("<br><b>Vitality:</b> " + vitality);
} catch (UnsupportedOperationException exp) {
} catch (UnsupportedOperationException ignored) {
}
try {
int resistance = _lotroGame.getModifiersQuerying().getResistance(_lotroGame, card);
sb.append("<br><b>Resistance:</b> " + resistance);
} catch (UnsupportedOperationException exp) {
} catch (UnsupportedOperationException ignored) {
}
try {
int siteNumber = _lotroGame.getModifiersQuerying().getMinionSiteNumber(_lotroGame, card);
sb.append("<br><b>Site number:</b> " + 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))

View File

@@ -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<String, String> loserPlayerIdsWithReasons) {
_finishedGamesTime.put(gameId, new Date());
}
new GameResultListener() {
@Override
public void gameFinished(String winnerPlayerId, String winReason, Map<String, String> 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<String, String> deckNames = new HashMap<>();
Map<String, LotroDeck> 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<String, String> loserPlayerIdsWithReasons) {
final Map.Entry<String, String> loserEntry = loserPlayerIdsWithReasons.entrySet().iterator().next();
new GameResultListener() {
@Override
public void gameFinished(String winnerPlayerId, String winReason, Map<String, String> 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);