Replay summary deck extraction, and deck output overhaul
- Adding /replay/summarydecks endpoint, which takes a replay ID (win or loss) and produces a readout of both decks involved as extracted directly from the json replay summary - Created new DeckRenderer class, which centralizes the HTML rendering in one place instead of being spread out and duplicated in various places - Added forum rendering, which alters links and formatting to use the phpbb markup style for pasting into TLHH - Converted the existing deck sharing + tournament deck display functions to use the DeckRenderer
This commit is contained in:
@@ -2,23 +2,36 @@ package com.gempukku.lotro.async.handler;
|
||||
|
||||
import com.gempukku.lotro.async.HttpProcessingException;
|
||||
import com.gempukku.lotro.async.ResponseWriter;
|
||||
import com.gempukku.lotro.game.GameRecorder;
|
||||
import com.gempukku.lotro.collection.DeckRenderer;
|
||||
import com.gempukku.lotro.common.JSONDefs;
|
||||
import com.gempukku.lotro.game.*;
|
||||
import com.gempukku.lotro.game.formats.LotroFormatLibrary;
|
||||
import com.gempukku.lotro.hall.GameTimer;
|
||||
import com.gempukku.lotro.logic.vo.LotroDeck;
|
||||
import com.gempukku.lotro.tournament.TournamentService;
|
||||
import com.gempukku.util.JsonUtils;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
|
||||
import io.netty.util.AsciiString;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.module.FindException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
|
||||
|
||||
public class ReplayRequestHandler extends LotroServerRequestHandler implements UriRequestHandler {
|
||||
private final GameRecorder _gameRecorder;
|
||||
private final DeckRenderer _deckRenderer;
|
||||
|
||||
private static final Logger _log = Logger.getLogger(ReplayRequestHandler.class);
|
||||
|
||||
@@ -26,43 +39,84 @@ public class ReplayRequestHandler extends LotroServerRequestHandler implements U
|
||||
super(context);
|
||||
|
||||
_gameRecorder = extractObject(context, GameRecorder.class);
|
||||
_deckRenderer = new DeckRenderer(extractObject(context, LotroCardBlueprintLibrary.class),
|
||||
extractObject(context, LotroFormatLibrary.class), new SortAndFilterCards());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleRequest(String uri, HttpRequest request, Map<Type, Object> context, ResponseWriter responseWriter, String remoteIp) throws Exception {
|
||||
if (uri.startsWith("/") && request.method() == HttpMethod.GET) {
|
||||
String replayId = uri.substring(1);
|
||||
|
||||
if (!replayId.contains("$"))
|
||||
throw new HttpProcessingException(404);
|
||||
if (replayId.contains("."))
|
||||
throw new HttpProcessingException(404);
|
||||
|
||||
final String[] split = replayId.split("\\$");
|
||||
if (split.length != 2)
|
||||
throw new HttpProcessingException(404);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
final InputStream recordedGame = _gameRecorder.getRecordedGame(split[0], split[1]);
|
||||
try (recordedGame) {
|
||||
if (recordedGame == null)
|
||||
throw new HttpProcessingException(404);
|
||||
byte[] bytes = new byte[1024];
|
||||
int count;
|
||||
while ((count = recordedGame.read(bytes)) != -1)
|
||||
baos.write(bytes, 0, count);
|
||||
} catch (IOException exp) {
|
||||
_log.error("Error 404 response for " + request.uri(), exp);
|
||||
throw new HttpProcessingException(404);
|
||||
}
|
||||
|
||||
Map<AsciiString, String> headers = new HashMap<>();
|
||||
headers.put(CONTENT_TYPE, "application/html; charset=UTF-8");
|
||||
|
||||
responseWriter.writeByteResponse(baos.toByteArray(), headers);
|
||||
if (uri.startsWith("/summarydecks")) {
|
||||
int length = "/summarydecks/".length();
|
||||
ServeSummaryDecks(uri.substring(length, uri.length()), request, responseWriter);
|
||||
}
|
||||
else if (uri.startsWith("/") && request.method() == HttpMethod.GET) {
|
||||
ServeReplayXML(uri.substring(1), request, responseWriter);
|
||||
} else {
|
||||
throw new HttpProcessingException(404);
|
||||
}
|
||||
}
|
||||
|
||||
private void ServeSummaryDecks(String replayId, HttpRequest request, ResponseWriter responseWriter) throws HttpProcessingException {
|
||||
validateAdmin(request);
|
||||
|
||||
try {
|
||||
var summaryFile = _gameRecorder.getRecordedSummary(replayId);
|
||||
String json = new String(summaryFile.readAllBytes());
|
||||
var summary = JsonUtils.Convert(json, ReplayMetadata.class);
|
||||
|
||||
var decks = _deckRenderer.ExtractDecksFromReplaySummary(summary);
|
||||
|
||||
var fragments = new ArrayList<String>();
|
||||
for(var deck : decks) {
|
||||
fragments.add(_deckRenderer.convertDeckToForumFragment(deck, deck.getNotes()));
|
||||
}
|
||||
|
||||
responseWriter.writeHtmlResponse(_deckRenderer.AddDeckReadoutHeaderAndFooter(fragments));
|
||||
}
|
||||
catch(IOException ex) {
|
||||
throw new HttpProcessingException(404);
|
||||
} catch (CardNotFoundException e) {
|
||||
throw new HttpProcessingException(500);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void ServeReplayXML(String replayId, HttpRequest request, ResponseWriter responseWriter) throws Exception {
|
||||
if (!replayId.contains("$"))
|
||||
throw new HttpProcessingException(404);
|
||||
if (replayId.contains("."))
|
||||
throw new HttpProcessingException(404);
|
||||
|
||||
final String[] split = replayId.split("\\$");
|
||||
if (split.length != 2)
|
||||
throw new HttpProcessingException(404);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
final InputStream recordedGame = _gameRecorder.getRecordedGame(split[0], split[1]);
|
||||
try (recordedGame) {
|
||||
if (recordedGame == null)
|
||||
throw new HttpProcessingException(404);
|
||||
byte[] bytes = new byte[1024];
|
||||
int count;
|
||||
while ((count = recordedGame.read(bytes)) != -1)
|
||||
baos.write(bytes, 0, count);
|
||||
} catch (IOException exp) {
|
||||
_log.error("Error 404 response for " + request.uri(), exp);
|
||||
throw new HttpProcessingException(404);
|
||||
}
|
||||
|
||||
Map<AsciiString, String> headers = new HashMap<>();
|
||||
headers.put(CONTENT_TYPE, "application/html; charset=UTF-8");
|
||||
|
||||
responseWriter.writeByteResponse(baos.toByteArray(), headers);
|
||||
}
|
||||
|
||||
private void validateAdmin(HttpRequest request) throws HttpProcessingException {
|
||||
Player player = getResourceOwnerSafely(request, null);
|
||||
|
||||
if (!player.hasType(Player.Type.ADMIN))
|
||||
throw new HttpProcessingException(403);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.gempukku.lotro.async.handler;
|
||||
|
||||
import com.gempukku.lotro.async.HttpProcessingException;
|
||||
import com.gempukku.lotro.async.ResponseWriter;
|
||||
import com.gempukku.lotro.collection.DeckRenderer;
|
||||
import com.gempukku.lotro.competitive.PlayerStanding;
|
||||
import com.gempukku.lotro.game.CardCollection;
|
||||
import com.gempukku.lotro.game.DefaultCardCollection;
|
||||
@@ -36,6 +37,7 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
|
||||
private final LotroFormatLibrary _formatLibrary;
|
||||
private final LotroCardBlueprintLibrary _library;
|
||||
private final SortAndFilterCards _sortAndFilterCards;
|
||||
private final DeckRenderer _deckRenderer;
|
||||
|
||||
private static final Logger _log = Logger.getLogger(TournamentRequestHandler.class);
|
||||
|
||||
@@ -46,6 +48,8 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
|
||||
_formatLibrary = extractObject(context, LotroFormatLibrary.class);
|
||||
_library = extractObject(context, LotroCardBlueprintLibrary.class);
|
||||
_sortAndFilterCards = new SortAndFilterCards();
|
||||
|
||||
_deckRenderer = new DeckRenderer(_library, _formatLibrary, _sortAndFilterCards);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,41 +121,9 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
|
||||
if (deck == null)
|
||||
throw new HttpProcessingException(404);
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("<html><body>");
|
||||
result.append("<h1>" + StringEscapeUtils.escapeHtml(deck.getDeckName()) + "</h1>");
|
||||
result.append("<h2>by " + playerName + "</h2>");
|
||||
String ringBearer = deck.getRingBearer();
|
||||
if (ringBearer != null)
|
||||
result.append("<b>Ring-bearer:</b> " + GameUtils.getFullName(_library.getLotroCardBlueprint(ringBearer)) + "<br/>");
|
||||
String ring = deck.getRing();
|
||||
if (ring != null)
|
||||
result.append("<b>Ring:</b> " + GameUtils.getFullName(_library.getLotroCardBlueprint(ring)) + "<br/>");
|
||||
String fragment = _deckRenderer.convertDeckToHTMLFragment(deck, playerName);
|
||||
|
||||
DefaultCardCollection deckCards = new DefaultCardCollection();
|
||||
for (String card : deck.getAdventureCards())
|
||||
deckCards.addItem(_library.getBaseBlueprintId(card), 1);
|
||||
for (String site : deck.getSites())
|
||||
deckCards.addItem(_library.getBaseBlueprintId(site), 1);
|
||||
|
||||
result.append("<br/>");
|
||||
result.append("<b>Adventure deck:</b><br/>");
|
||||
for (CardCollection.Item item : _sortAndFilterCards.process("cardType:SITE sort:siteNumber,twilight", deckCards.getAll(), _library, _formatLibrary))
|
||||
result.append(GameUtils.getFullName(_library.getLotroCardBlueprint(item.getBlueprintId())) + "<br/>");
|
||||
|
||||
result.append("<br/>");
|
||||
result.append("<b>Free Peoples Draw Deck:</b><br/>");
|
||||
for (CardCollection.Item item : _sortAndFilterCards.process("side:FREE_PEOPLE sort:cardType,culture,name", deckCards.getAll(), _library, _formatLibrary))
|
||||
result.append(item.getCount() + "x " + GameUtils.getFullName(_library.getLotroCardBlueprint(item.getBlueprintId())) + "<br/>");
|
||||
|
||||
result.append("<br/>");
|
||||
result.append("<b>Shadow Draw Deck:</b><br/>");
|
||||
for (CardCollection.Item item : _sortAndFilterCards.process("side:SHADOW sort:cardType,culture,name", deckCards.getAll(), _library, _formatLibrary))
|
||||
result.append(item.getCount() + "x " + GameUtils.getFullName(_library.getLotroCardBlueprint(item.getBlueprintId())) + "<br/>");
|
||||
|
||||
result.append("</body></html>");
|
||||
|
||||
responseWriter.writeHtmlResponse(result.toString());
|
||||
responseWriter.writeHtmlResponse(_deckRenderer.AddDeckReadoutHeaderAndFooter(fragment));
|
||||
}
|
||||
|
||||
private void getTournamentReport(HttpRequest request, String tournamentId, String playerName, ResponseWriter responseWriter) throws Exception {
|
||||
@@ -162,7 +134,7 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
|
||||
if (tournament.getTournamentStage() != Tournament.Stage.FINISHED)
|
||||
throw new HttpProcessingException(403);
|
||||
|
||||
var result = tournament.produceReport(_library, _formatLibrary, _sortAndFilterCards);
|
||||
var result = tournament.produceReport(_deckRenderer);
|
||||
|
||||
responseWriter.writeHtmlResponse(result);
|
||||
}
|
||||
|
||||
@@ -30,4 +30,13 @@ public class Names {
|
||||
.replaceAll("_", "")
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
public static String SanitizeDisplayName(String name) {
|
||||
return Normalizer.normalize(name, Normalizer.Form.NFD)
|
||||
.replaceAll("’", "'")
|
||||
.replaceAll("‘", "'")
|
||||
.replaceAll("”", "\"")
|
||||
.replaceAll("“", "\"")
|
||||
.replaceAll("\\p{M}", "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@ public final class JsonUtils {
|
||||
return JSON.parseObject(json, clazz);
|
||||
}
|
||||
|
||||
public static <T> T Convert(String jsonText, Class<T> clazz) throws IOException {
|
||||
String json = JsonValue.readHjson(jsonText).toString();
|
||||
return JSON.parseObject(json, clazz);
|
||||
}
|
||||
|
||||
public static <T> List<T> ConvertArray(Reader reader, Class<T> clazz) throws IOException {
|
||||
final String json = ReadJson(reader);
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
package com.gempukku.lotro.collection;
|
||||
|
||||
import com.gempukku.lotro.common.Names;
|
||||
import com.gempukku.lotro.competitive.PlayerStanding;
|
||||
import com.gempukku.lotro.game.*;
|
||||
import com.gempukku.lotro.game.formats.LotroFormatLibrary;
|
||||
import com.gempukku.lotro.logic.GameUtils;
|
||||
import com.gempukku.lotro.logic.vo.LotroDeck;
|
||||
import org.apache.commons.lang.StringEscapeUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DeckRenderer {
|
||||
|
||||
private final LotroCardBlueprintLibrary _bpLibrary;
|
||||
private final LotroFormatLibrary _formatLibrary;
|
||||
private final SortAndFilterCards _cardFilter;
|
||||
|
||||
public DeckRenderer(LotroCardBlueprintLibrary library, LotroFormatLibrary formatLibrary, SortAndFilterCards cardfilter) {
|
||||
_bpLibrary = library;
|
||||
_cardFilter = cardfilter;
|
||||
_formatLibrary = formatLibrary;
|
||||
}
|
||||
|
||||
|
||||
public List<LotroDeck> ExtractDecksFromReplaySummary(ReplayMetadata summary) {
|
||||
var decks = new ArrayList<LotroDeck>();
|
||||
for(var deck : summary.Decks.values()) {
|
||||
decks.add(ConvertMetadataDeckToLotroDeck(deck));
|
||||
}
|
||||
return decks;
|
||||
}
|
||||
|
||||
public LotroDeck ConvertMetadataDeckToLotroDeck(ReplayMetadata.DeckMetadata recordedDeck) {
|
||||
var deck = new LotroDeck(recordedDeck.DeckName);
|
||||
deck.setNotes(recordedDeck.Owner);
|
||||
deck.setTargetFormat(recordedDeck.TargetFormat);
|
||||
deck.setRing(recordedDeck.Ring);
|
||||
deck.setRingBearer(recordedDeck.RingBearer);
|
||||
for(var site : recordedDeck.AdventureDeck) {
|
||||
deck.addSite(site);
|
||||
}
|
||||
for(var card : recordedDeck.DrawDeck) {
|
||||
deck.addCard(card);
|
||||
}
|
||||
return deck;
|
||||
}
|
||||
|
||||
public String AddDeckReadoutHeaderAndFooter(String fragment) {
|
||||
return AddDeckReadoutHeaderAndFooter(Collections.singletonList(fragment));
|
||||
}
|
||||
|
||||
public String AddDeckReadoutHeaderAndFooter(List<String> fragments) {
|
||||
String preamble = """
|
||||
<html>
|
||||
<style>
|
||||
body {
|
||||
margin:50;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
border-bottom: 1px dotted black; /* If you want dots under the hoverable text */
|
||||
color:#0000FF;
|
||||
}
|
||||
|
||||
.tooltip span, .tooltip title {
|
||||
display:none;
|
||||
}
|
||||
.tooltip:hover span:not(.click-disabled),.tooltip:active span:not(.click-disabled) {
|
||||
display:block;
|
||||
position:fixed;
|
||||
overflow:hidden;
|
||||
background-color: #FAEBD7;
|
||||
width:auto;
|
||||
z-index:9999;
|
||||
top:20%;
|
||||
left:350px;
|
||||
}
|
||||
/* This prevents tooltip images from automatically shrinking if they are near the window edge.*/
|
||||
.tooltip span > img {
|
||||
max-width:none !important;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
</style>
|
||||
<body>""";
|
||||
String divider = "<br/>[hr]<hr><br/>";
|
||||
String postamble = "</body></html>";
|
||||
return preamble + String.join(divider, fragments) + postamble;
|
||||
|
||||
}
|
||||
|
||||
public String convertDeckToHTMLFragment(LotroDeck deck, String author) throws CardNotFoundException {
|
||||
return convertDeckToHTMLFragment(deck, author, new ArrayList<>());
|
||||
}
|
||||
|
||||
public String convertDeckToHTMLFragment(LotroDeck deck, String author, List<Map.Entry<String, String>> replays) throws CardNotFoundException {
|
||||
|
||||
if (deck == null)
|
||||
return null;
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("<div>")
|
||||
.append("<h1>").append(StringEscapeUtils.escapeHtml(deck.getDeckName())).append("</h1>")
|
||||
.append("<h2>Format: ").append(StringEscapeUtils.escapeHtml(deck.getTargetFormat())).append("</h2>");
|
||||
|
||||
if(author != null) {
|
||||
result.append("<h2>Author: ").append(StringEscapeUtils.escapeHtml(author)).append("</h2>");
|
||||
}
|
||||
|
||||
if(replays != null && replays.size() > 0) {
|
||||
result.append("<h3>Round Replays: </h3>");
|
||||
List<String> rounds = new ArrayList<>();
|
||||
|
||||
for(var replay : replays) {
|
||||
if(replay.getValue().isBlank()) {
|
||||
rounds.add(replay.getKey());
|
||||
}
|
||||
else {
|
||||
rounds.add("<a href='" + replay.getValue() + "' target='_blank'>" + replay.getKey() + "</a>");
|
||||
}
|
||||
}
|
||||
|
||||
result.append(String.join(" • ", rounds));
|
||||
}
|
||||
|
||||
String ringBearer = deck.getRingBearer();
|
||||
if (ringBearer != null) {
|
||||
result.append("<b>Ring-bearer:</b> ").append(generateCardTooltip(ringBearer)).append("<br/>");
|
||||
}
|
||||
String ring = deck.getRing();
|
||||
if (ring != null) {
|
||||
result.append("<b>Ring:</b> ").append(generateCardTooltip(ring)).append("<br/>");
|
||||
}
|
||||
|
||||
DefaultCardCollection deckCards = new DefaultCardCollection();
|
||||
for (String card : deck.getAdventureCards()) {
|
||||
deckCards.addItem(_bpLibrary.getBaseBlueprintId(card), 1);
|
||||
}
|
||||
for (String site : deck.getSites()) {
|
||||
deckCards.addItem(_bpLibrary.getBaseBlueprintId(site), 1);
|
||||
}
|
||||
|
||||
result.append("<br/>");
|
||||
result.append("<b>Adventure deck:</b><br/>");
|
||||
for (CardCollection.Item item : _cardFilter.process("cardType:SITE sort:siteNumber,twilight", deckCards.getAll(),
|
||||
_bpLibrary, _formatLibrary)) {
|
||||
result.append(generateCardTooltip(item)).append("<br/>");
|
||||
}
|
||||
|
||||
result.append("<br/>");
|
||||
result.append("<b>Free Peoples Draw Deck:</b><br/>");
|
||||
for (CardCollection.Item item : _cardFilter.process("side:FREE_PEOPLE sort:cardType,culture,name", deckCards.getAll(),
|
||||
_bpLibrary, _formatLibrary)) {
|
||||
result.append(item.getCount()).append("x ").append(generateCardTooltip(item)).append("<br/>");
|
||||
}
|
||||
|
||||
result.append("<br/>");
|
||||
result.append("<b>Shadow Draw Deck:</b><br/>");
|
||||
for (CardCollection.Item item : _cardFilter.process("side:SHADOW sort:cardType,culture,name", deckCards.getAll(),
|
||||
_bpLibrary, _formatLibrary)) {
|
||||
result.append(item.getCount()).append("x ").append(generateCardTooltip(item)).append("<br/>");
|
||||
}
|
||||
|
||||
result.append("<h3>Notes</h3><br>").append(deck.getNotes().replace("\n", "<br/>"));
|
||||
|
||||
result.append("</div>");
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public String convertDeckToForumFragment(LotroDeck deck, String author) throws CardNotFoundException {
|
||||
return convertDeckToForumFragment(deck, author, new ArrayList<>());
|
||||
}
|
||||
|
||||
public String convertDeckToForumFragment(LotroDeck deck, String author, List<Map.Entry<String, String>> replays) throws CardNotFoundException {
|
||||
|
||||
if (deck == null)
|
||||
return null;
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("<div>")
|
||||
.append("<h1>").append(StringEscapeUtils.escapeHtml(deck.getDeckName())).append("</h1>")
|
||||
.append("<h2>Format: ").append(StringEscapeUtils.escapeHtml(deck.getTargetFormat())).append("</h2>");
|
||||
|
||||
if(author != null) {
|
||||
result.append("<h2>Author: ").append(StringEscapeUtils.escapeHtml(author)).append("</h2>");
|
||||
}
|
||||
|
||||
if(replays != null && replays.size() > 0) {
|
||||
result.append("<h3>[b]Round Replays: [/b]</h3>");
|
||||
List<String> rounds = new ArrayList<>();
|
||||
|
||||
for(var replay : replays) {
|
||||
if(replay.getValue().isBlank()) {
|
||||
rounds.add(replay.getKey());
|
||||
}
|
||||
else {
|
||||
rounds.add("[url=" + replay.getValue() + "]" + replay.getKey() + "[/url]");
|
||||
}
|
||||
}
|
||||
|
||||
result.append(String.join(" • ", rounds));
|
||||
}
|
||||
|
||||
String ringBearer = deck.getRingBearer();
|
||||
if (ringBearer != null) {
|
||||
result.append("<b>[b]Ring-bearer:[/b]</b> ").append(generateCardTooltip(ringBearer)).append("<br/>");
|
||||
}
|
||||
String ring = deck.getRing();
|
||||
if (ring != null) {
|
||||
result.append("<b>[b]Ring:[/b]</b> ").append(generateCardTooltip(ring)).append("<br/>");
|
||||
}
|
||||
|
||||
DefaultCardCollection deckCards = new DefaultCardCollection();
|
||||
for (String card : deck.getAdventureCards()) {
|
||||
deckCards.addItem(_bpLibrary.getBaseBlueprintId(card), 1);
|
||||
}
|
||||
for (String site : deck.getSites()) {
|
||||
deckCards.addItem(_bpLibrary.getBaseBlueprintId(site), 1);
|
||||
}
|
||||
|
||||
result.append("<br/>");
|
||||
result.append("<b>[b]Adventure deck:[/b]</b><br/>");
|
||||
for (CardCollection.Item item : _cardFilter.process("cardType:SITE sort:siteNumber,twilight", deckCards.getAll(),
|
||||
_bpLibrary, _formatLibrary)) {
|
||||
result.append(generateCardTooltip(item)).append("<br/>");
|
||||
}
|
||||
|
||||
result.append("<br/>");
|
||||
result.append("<b>[b]Free Peoples Draw Deck:[/b]</b><br/>");
|
||||
for (CardCollection.Item item : _cardFilter.process("side:FREE_PEOPLE sort:cardType,culture,name", deckCards.getAll(),
|
||||
_bpLibrary, _formatLibrary)) {
|
||||
result.append(item.getCount()).append("x ").append(generateCardTooltip(item)).append("<br/>");
|
||||
}
|
||||
|
||||
result.append("<br/>");
|
||||
result.append("<b>[b]Shadow Draw Deck:[/b]</b><br/>");
|
||||
for (CardCollection.Item item : _cardFilter.process("side:SHADOW sort:cardType,culture,name", deckCards.getAll(),
|
||||
_bpLibrary, _formatLibrary)) {
|
||||
result.append(item.getCount()).append("x ").append(generateCardTooltip(item)).append("<br/>");
|
||||
}
|
||||
|
||||
result.append("</div>");
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private String generateCardTooltip(CardCollection.Item item) throws CardNotFoundException {
|
||||
return generateCardTooltip(_bpLibrary.getLotroCardBlueprint(item.getBlueprintId()), item.getBlueprintId());
|
||||
}
|
||||
|
||||
private String generateCardTooltip(String bpid) throws CardNotFoundException {
|
||||
return generateCardTooltip(_bpLibrary.getLotroCardBlueprint(bpid), bpid);
|
||||
}
|
||||
|
||||
private String generateCardTooltip(LotroCardBlueprint bp, String bpid) throws CardNotFoundException {
|
||||
String[] parts = bpid.split("_");
|
||||
int setnum = Integer.parseInt(parts[0]);
|
||||
String set = String.format("%02d", setnum);
|
||||
String subset = "S";
|
||||
int version = 0;
|
||||
if(setnum >= 50 && setnum <= 69) {
|
||||
setnum -= 50;
|
||||
set = String.format("%02d", setnum);
|
||||
subset = "E";
|
||||
version = 1;
|
||||
}
|
||||
else if(setnum >= 70 && setnum <= 89) {
|
||||
setnum -= 70;
|
||||
set = String.format("%02d", setnum);
|
||||
subset = "E";
|
||||
version = 1;
|
||||
}
|
||||
else if(setnum >= 100 && setnum <= 149) {
|
||||
setnum -= 100;
|
||||
set = "V" + setnum;
|
||||
}
|
||||
int cardnum = Integer.parseInt(parts[1].replace("*", "").replace("T", ""));
|
||||
|
||||
String id = "LOTR-EN" + set + subset + String.format("%03d", cardnum) + "." + String.format("%01d", version);
|
||||
String displayName = Names.SanitizeDisplayName(GameUtils.getFullName(bp));
|
||||
if(subset.equals("E")) {
|
||||
displayName += " (Errata)";
|
||||
}
|
||||
String result = "<span class=\"tooltip\">" + displayName
|
||||
+ "<span><img class=\"ttimage\" src=\"https://wiki.lotrtcgpc.net/images/" + id + "_card.jpg\" ></span></span>";
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -272,4 +272,18 @@ public class GameRecorder {
|
||||
|
||||
return new InflaterInputStream(new FileInputStream(recordingFile));
|
||||
}
|
||||
|
||||
public InputStream getRecordedSummary(String recordId) throws IOException {
|
||||
var history = _gameHistoryService.getGameHistory(recordId);
|
||||
|
||||
if(history == null)
|
||||
return null;
|
||||
|
||||
File recordingFile = getSummaryFile(history);
|
||||
|
||||
if (recordingFile == null || !recordingFile.exists() || !recordingFile.isFile())
|
||||
return null;
|
||||
|
||||
return new FileInputStream(recordingFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@ public class ReplayMetadata {
|
||||
|
||||
public HashSet<Integer> PlayedCards = new HashSet<>();
|
||||
|
||||
public ReplayMetadata() {
|
||||
|
||||
}
|
||||
public ReplayMetadata(DBDefs.GameHistory game, Map<String, LotroDeck> decks) {
|
||||
GameReplayInfo = game;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.gempukku.lotro.tournament;
|
||||
|
||||
import com.gempukku.lotro.DateUtils;
|
||||
import com.gempukku.lotro.collection.CollectionsManager;
|
||||
import com.gempukku.lotro.collection.DeckRenderer;
|
||||
import com.gempukku.lotro.competitive.BestOfOneStandingsProducer;
|
||||
import com.gempukku.lotro.competitive.PlayerStanding;
|
||||
import com.gempukku.lotro.db.vo.CollectionType;
|
||||
@@ -464,8 +465,12 @@ public class DefaultTournament implements Tournament {
|
||||
}
|
||||
}
|
||||
|
||||
private Map.Entry<String, String> createEntry(String label, String url) {
|
||||
return new AbstractMap.SimpleEntry<>(label, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String produceReport(LotroCardBlueprintLibrary bpLibrary, LotroFormatLibrary formatLibrary, SortAndFilterCards cardFilter) throws CardNotFoundException {
|
||||
public String produceReport(DeckRenderer renderer) throws CardNotFoundException {
|
||||
if(_tournamentReport != null)
|
||||
return _tournamentReport;
|
||||
|
||||
@@ -495,8 +500,8 @@ public class DefaultTournament implements Tournament {
|
||||
}
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("<html><body>")
|
||||
StringBuilder summary = new StringBuilder();
|
||||
summary.append("<html><body>")
|
||||
.append("<h1>").append(StringEscapeUtils.escapeHtml(_tournamentName)).append("</h1>")
|
||||
.append("<ul>")
|
||||
.append("<li>Format: ").append(_format).append("</li>")
|
||||
@@ -506,19 +511,20 @@ public class DefaultTournament implements Tournament {
|
||||
.append("<li>End: ").append(DateUtils.FormatStandardDateTime(end)).append("</li>")
|
||||
.append("</ul><br/><br/><hr>");
|
||||
|
||||
var decks = new ArrayList<String>();
|
||||
decks.add(summary.toString());
|
||||
|
||||
for(var standing : getCurrentStandings()) {
|
||||
var playerName = standing.playerName();
|
||||
result.append("<h2>by ").append(playerName).append("</h2><br/>")
|
||||
.append("<h3>Round Replays</h3>");
|
||||
|
||||
var rounds = new ArrayList<String>();
|
||||
var rounds = new ArrayList<Map.Entry<String, String>>();
|
||||
|
||||
var playerRounds = _finishedTournamentMatches.stream()
|
||||
.filter((x) -> x.getPlayerOne().equals(playerName) || x.getPlayerTwo().equals(playerName))
|
||||
.toList();
|
||||
for(int i = 1; i <= _tournamentRound; i++) {
|
||||
if(_playerByes.containsKey(playerName) && _playerByes.get(playerName) == i) {
|
||||
rounds.add("[bye]");
|
||||
rounds.add(createEntry("[bye]", ""));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -527,7 +533,7 @@ public class DefaultTournament implements Tournament {
|
||||
.findFirst().orElse(null);
|
||||
|
||||
if(match == null) {
|
||||
rounds.add("[dropped]");
|
||||
rounds.add(createEntry("[dropped]", ""));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -541,67 +547,20 @@ public class DefaultTournament implements Tournament {
|
||||
replayId = game.lose_recording_id;
|
||||
}
|
||||
|
||||
rounds.add("<a href='https://play.lotrtcgpc.net/gemp-lotr/game.html?replayId=" +
|
||||
playerName.replace("_", "%5F") + "$" + replayId +
|
||||
"' target='_blank'>Round " + i + "</a>");
|
||||
String label = "Round " + i;
|
||||
String url = "https://play.lotrtcgpc.net/gemp-lotr/game.html?replayId=" +
|
||||
playerName.replace("_", "%5F") + "$" + replayId;
|
||||
|
||||
rounds.add(createEntry(label, url));
|
||||
}
|
||||
|
||||
result.append(String.join(" • ", rounds));
|
||||
|
||||
result.append("<h3>Deck</h3>");
|
||||
|
||||
LotroDeck deck = _tournamentService.getPlayerDeck(_tournamentId, playerName, _format);
|
||||
|
||||
String ringBearer = deck.getRingBearer();
|
||||
if (ringBearer != null) {
|
||||
result.append("<b>Ring-bearer:</b> ")
|
||||
.append(GameUtils.getFullName(bpLibrary.getLotroCardBlueprint(ringBearer)))
|
||||
.append("<br/>");
|
||||
}
|
||||
var fragment = renderer.convertDeckToForumFragment(deck, playerName, rounds);
|
||||
decks.add(fragment);
|
||||
|
||||
String ring = deck.getRing();
|
||||
if (ring != null) {
|
||||
result.append("<b>Ring:</b> ")
|
||||
.append(GameUtils.getFullName(bpLibrary.getLotroCardBlueprint(ring)))
|
||||
.append("<br/>");
|
||||
}
|
||||
|
||||
DefaultCardCollection deckCards = new DefaultCardCollection();
|
||||
for (String card : deck.getAdventureCards()) {
|
||||
deckCards.addItem(bpLibrary.getBaseBlueprintId(card), 1);
|
||||
}
|
||||
for (String site : deck.getSites()) {
|
||||
deckCards.addItem(bpLibrary.getBaseBlueprintId(site), 1);
|
||||
}
|
||||
|
||||
result.append("<br/>");
|
||||
result.append("<b>Adventure deck:</b><br/>");
|
||||
for (CardCollection.Item item : cardFilter.process("cardType:SITE sort:siteNumber,twilight", deckCards.getAll(), bpLibrary, formatLibrary)) {
|
||||
result.append(GameUtils.getFullName(bpLibrary.getLotroCardBlueprint(item.getBlueprintId())))
|
||||
.append("<br/>");
|
||||
}
|
||||
|
||||
result.append("<br/>");
|
||||
result.append("<b>Free Peoples Draw Deck:</b><br/>");
|
||||
for (CardCollection.Item item : cardFilter.process("side:FREE_PEOPLE sort:cardType,culture,name", deckCards.getAll(), bpLibrary, formatLibrary)) {
|
||||
result.append(item.getCount()).append("x ")
|
||||
.append(GameUtils.getFullName(bpLibrary.getLotroCardBlueprint(item.getBlueprintId())))
|
||||
.append("<br/>");
|
||||
}
|
||||
|
||||
result.append("<br/>");
|
||||
result.append("<b>Shadow Draw Deck:</b><br/>");
|
||||
for (CardCollection.Item item : cardFilter.process("side:SHADOW sort:cardType,culture,name", deckCards.getAll(), bpLibrary, formatLibrary)) {
|
||||
result.append(item.getCount()).append("x ")
|
||||
.append(GameUtils.getFullName(bpLibrary.getLotroCardBlueprint(item.getBlueprintId())))
|
||||
.append("<br/>");
|
||||
}
|
||||
|
||||
result.append("<br/><br/><hr>");
|
||||
}
|
||||
|
||||
result.append("</body></html>");
|
||||
|
||||
return result.toString();
|
||||
return renderer.AddDeckReadoutHeaderAndFooter(decks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.gempukku.lotro.tournament;
|
||||
|
||||
import com.gempukku.lotro.collection.CollectionsManager;
|
||||
import com.gempukku.lotro.collection.DeckRenderer;
|
||||
import com.gempukku.lotro.competitive.PlayerStanding;
|
||||
import com.gempukku.lotro.db.vo.CollectionType;
|
||||
import com.gempukku.lotro.draft.Draft;
|
||||
@@ -92,5 +93,5 @@ public interface Tournament {
|
||||
|
||||
public boolean isPlayerInCompetition(String player);
|
||||
|
||||
public String produceReport(LotroCardBlueprintLibrary bpLibrary, LotroFormatLibrary formatLibrary, SortAndFilterCards cardFilter) throws CardNotFoundException;
|
||||
public String produceReport(DeckRenderer renderer) throws CardNotFoundException;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user