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:
Christian 'ketura' McCarty
2023-10-29 06:05:24 -05:00
parent 26231745cc
commit 60abc1af79
9 changed files with 442 additions and 131 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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}", "");
}
}

View File

@@ -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 {

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;
}