From 9478b9aefdc51e75eeb7803135a7d149c27e4edb Mon Sep 17 00:00:00 2001 From: Christian 'ketura' McCarty Date: Fri, 17 Mar 2023 01:33:25 -0500 Subject: [PATCH] Added json summary generation after replay is compiled. --- .../handler/PlayerInfoRequestHandler.java | 2 - .../lotro/game/state/EventSerializer.java | 5 +- .../gempukku/lotro/db/DbGameHistoryDAO.java | 5 +- .../com/gempukku/lotro/game/GameRecorder.java | 21 ++- .../gempukku/lotro/game/ReplayMetadata.java | 151 ++++++++++++++++++ 5 files changed, 175 insertions(+), 9 deletions(-) create mode 100644 gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/ReplayMetadata.java diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/PlayerInfoRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/PlayerInfoRequestHandler.java index 72254eb74..f394bcf41 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/PlayerInfoRequestHandler.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/PlayerInfoRequestHandler.java @@ -4,7 +4,6 @@ import com.alibaba.fastjson.JSON; import com.gempukku.lotro.async.HttpProcessingException; import com.gempukku.lotro.async.ResponseWriter; import com.gempukku.lotro.game.Player; -import com.google.gson.Gson; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.QueryStringDecoder; @@ -14,7 +13,6 @@ import java.lang.reflect.Type; import java.util.Map; public class PlayerInfoRequestHandler extends LotroServerRequestHandler implements UriRequestHandler { - private final Gson JsonConvert = new Gson(); private static final Logger _log = Logger.getLogger(PlayerInfoRequestHandler.class); 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 27450b9e9..ef4fde98e 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 @@ -16,10 +16,7 @@ public class EventSerializer { public Node serializeEvent(Document doc, GameEvent gameEvent) { Element eventElem = doc.createElement("ge"); eventElem.setAttribute("type", gameEvent.getType().getCode()); - //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)); + eventElem.setAttribute("timestamp", gameEvent.getTimestamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss.SSSS"))); if (gameEvent.getBlueprintId() != null) eventElem.setAttribute("blueprintId", gameEvent.getBlueprintId()); 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 22efe7b13..dce764388 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 @@ -18,6 +18,7 @@ import java.util.Objects; public class DbGameHistoryDAO implements GameHistoryDAO { private final DbAccess _dbAccess; private final DateTimeFormatter _dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private final DateTimeFormatter _dateTimeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss"); public DbGameHistoryDAO(DbAccess dbAccess) { _dbAccess = dbAccess; @@ -49,8 +50,8 @@ public class DbGameHistoryDAO implements GameHistoryDAO { .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("start_date", startDate.format(_dateTimeFormat)) + .addParameter("end_date", endDate.format(_dateTimeFormat)) .addParameter("version", replayVersion); query.executeUpdate(); 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 b70b6df4e..71b89cf53 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,5 +1,6 @@ package com.gempukku.lotro.game; +import com.alibaba.fastjson.JSON; import com.gempukku.lotro.common.AppConfig; import com.gempukku.lotro.common.DBDefs; import com.gempukku.lotro.db.PlayerDAO; @@ -114,6 +115,16 @@ public class GameRecorder { return new File(playerReplayFolder, gameId + ".xml.gz"); } + private File getSummaryFile(DBDefs.GameHistory history) { + var gameReplayFolder = new File(AppConfig.getProperty("application.root"), "replay"); + var summaryFolder = new File(gameReplayFolder, "summaries"); + var yearFolder = new File(summaryFolder, String.format("%04d", history.start_date.getYear())); + var monthFolder = new File(yearFolder, String.format("%02d", history.start_date.getMonthValue())); + monthFolder.mkdirs(); + return new File(monthFolder, history.winner + "_vs_" + history.loser + "_" + + history.win_recording_id + "_" + history.lose_recording_id+ ".json"); + } + private OutputStream getRecordingWriteStream(String playerId, String gameId, ZonedDateTime startDate) throws IOException { File recordingFile = getRecordingFileVersion1(playerId, gameId, startDate); recordingFile.getParentFile().mkdirs(); @@ -124,6 +135,8 @@ public class GameRecorder { private Map saveRecordedChannels(Map gameProgress, DBDefs.GameHistory gameInfo, Map decks) { Map result = new HashMap<>(); + var metadata = new ReplayMetadata(gameInfo, decks); + for (Map.Entry playerRecordings : gameProgress.entrySet()) { String playerId = playerRecordings.getKey(); String recordingId = ""; @@ -135,6 +148,7 @@ public class GameRecorder { } final List gameEvents = playerRecordings.getValue().consumeGameEvents(); + metadata.ParseReplay(playerId, gameEvents); try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); @@ -187,10 +201,15 @@ public class GameRecorder { Transformer xformer = TransformerFactory.newInstance().newTransformer(); xformer.transform(source, streamResult); } + result.put(playerId, recordingId); + + try(var out = new PrintWriter(getSummaryFile(gameInfo).getAbsolutePath())) { + out.println(JSON.toJSONString(metadata)); + } } catch (Exception exp) { } - result.put(playerId, recordingId); + } return result; } diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/ReplayMetadata.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/ReplayMetadata.java new file mode 100644 index 000000000..3b06b40db --- /dev/null +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/game/ReplayMetadata.java @@ -0,0 +1,151 @@ +package com.gempukku.lotro.game; + +import com.gempukku.lotro.common.DBDefs; +import com.gempukku.lotro.common.Zone; +import com.gempukku.lotro.game.state.GameEvent; +import com.gempukku.lotro.logic.vo.LotroDeck; +import com.mysql.cj.util.StringUtils; + +import java.util.*; +import java.util.regex.Pattern; + +public class ReplayMetadata { + + public class DeckMetadata { + public String Owner; + public String TargetFormat; + public String DeckName; + public List AdventureDeck; + public List DrawDeck; + public String RingBearer; + public String Ring; + public List StartingFellowship = new ArrayList<>(); + } + + public Integer MetadataVersion = 1; + + public DBDefs.GameHistory GameReplayInfo; + + public Map Decks = new HashMap<>(); + public Map PlayerIDs = new HashMap<>(); + public Map Bids = new HashMap<>(); + public String WentFirst; + public boolean GameStarted = false; + public boolean Conceded = false; + public boolean Canceled = false; + + public Map AllCards = new HashMap<>(); + + public Set SeenCards = new HashSet<>(); + + public HashSet PlayedCards = new HashSet<>(); + + public ReplayMetadata(DBDefs.GameHistory game, Map decks) { + GameReplayInfo = game; + + for(var pair : decks.entrySet()) { + String player = pair.getKey(); + var deck = pair.getValue(); + var metadata = new DeckMetadata() {{ + Owner = player; + TargetFormat = deck.getTargetFormat(); + DeckName = deck.getDeckName(); + AdventureDeck = deck.getSites(); + DrawDeck = deck.getAdventureCards(); + RingBearer = deck.getRingBearer(); + Ring = deck.getRing(); + }}; + + Decks.put(player, metadata); + } + + if(GameReplayInfo.lose_reason.contains("cancelled") || GameReplayInfo.win_reason.contains("cancelled")) { + Canceled = true; + } + + if(GameReplayInfo.lose_reason.contains("Concession") || GameReplayInfo.win_reason.contains("Concession")) { + Conceded = true; + } + } + + public String GetOpponent(String player) { + return PlayerIDs.keySet().stream().filter(x -> !x.equals(player)).findFirst().get(); + } + + private final Pattern gameStartPattern = Pattern.compile("Players in the game are: ([\\w-]+), ([\\w-]+)"); + private final Pattern orderPattern = Pattern.compile("([\\w-]+) has chosen to go (.*)"); + private final Pattern bidPattern = Pattern.compile("([\\w-]+) bid (\\d+)"); + public void ParseReplay(String player, List events) { + GameStarted = false; + + for(var event : events) { + if(event.getType() == GameEvent.Type.SEND_MESSAGE) { + var message = event.getMessage(); + if(StringUtils.isNullOrEmpty(message)) + continue; + + var regex = gameStartPattern.matcher(message); + if(regex.matches()) { + PlayerIDs.put(regex.group(1), 1); + PlayerIDs.put(regex.group(2), 2); + continue; + } + + regex = orderPattern.matcher(message); + if(regex.matches()) { + String bidder = regex.group(1); + String order = regex.group(2); + if(order.equals("first")) { + WentFirst = bidder; + } + else if(order.equals("second")) { + WentFirst = GetOpponent(bidder); + } + continue; + } + + regex = bidPattern.matcher(message); + if(regex.matches()) { + String bidder = regex.group(1); + String bid = regex.group(2); + Bids.put(bidder, Integer.valueOf(bid)); + continue; + } + } + else if(!GameStarted && event.getType() == GameEvent.Type.GAME_PHASE_CHANGE) { + var phase = event.getPhase(); + if (phase != null && phase.equals("Between turns")) + { + GameStarted = true; + } + } + + else if(event.getType() == GameEvent.Type.PUT_CARD_INTO_PLAY) { + + var bpID = event.getBlueprintId(); + var cardID = event.getCardId(); + var participantID = event.getParticipantId(); + var zone = event.getZone(); + var targetCardID = event.getTargetCardId(); + + if (bpID != null && cardID != null && participantID != null && participantID.equals(player)) { + if (!GameStarted && (zone.equals(Zone.FREE_CHARACTERS)) || zone.equals(Zone.ATTACHED)) { + Decks.get(player).StartingFellowship.add(bpID); + } + + switch (zone) { + case FREE_CHARACTERS, SUPPORT, SHADOW_CHARACTERS, ADVENTURE_PATH, VOID_FROM_HAND, VOID -> { + AllCards.put(cardID.toString(), bpID); + SeenCards.add(cardID); + PlayedCards.add(cardID); + } + case ATTACHED, HAND, STACKED, DEAD, DISCARD, ADVENTURE_DECK, DECK, REMOVED -> { + AllCards.put(cardID.toString(), bpID); + SeenCards.add(cardID); + } + } + } + } + } + } +}