From 59604a849a33378560fb5bbb6468a707173da6ef Mon Sep 17 00:00:00 2001 From: Christian 'ketura' McCarty Date: Sat, 15 Jun 2024 17:04:48 -0500 Subject: [PATCH 01/28] ...fixing Sams apparently having been broken for a few weeks? Oh dear. --- .../src/main/resources/cards/official/set01/set1-shire.hjson | 1 - .../src/main/resources/cards/official/set19/set19.hjson | 1 - .../main/resources/cards/unofficial/pc/setV01/V1_shire.hjson | 1 - .../unofficial/pc/errata/set01/Card_01_311_ErrataTests.java | 2 +- 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set01/set1-shire.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set01/set1-shire.hjson index 0cb4443cd..f614de6ee 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set01/set1-shire.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set01/set1-shire.hjson @@ -713,7 +713,6 @@ } { type: ActivatedTrigger - optional: true trigger: { type: killed filter: name(Frodo),ring bearer diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set19/set19.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set19/set19.hjson index b7ff5bcf4..691882b4a 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set19/set19.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/official/set19/set19.hjson @@ -1222,7 +1222,6 @@ } { type: ActivatedTrigger - optional: true trigger: { type: killed filter: name(Frodo),ring bearer diff --git a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV01/V1_shire.hjson b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV01/V1_shire.hjson index 748a75e56..1faeb1b61 100644 --- a/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV01/V1_shire.hjson +++ b/gemp-lotr/gemp-lotr-cards/src/main/resources/cards/unofficial/pc/setV01/V1_shire.hjson @@ -208,7 +208,6 @@ } { type: ActivatedTrigger - optional: true trigger: { type: killed filter: name(Frodo),ring bearer diff --git a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set01/Card_01_311_ErrataTests.java b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set01/Card_01_311_ErrataTests.java index 21ec8e4ba..5acefde9c 100644 --- a/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set01/Card_01_311_ErrataTests.java +++ b/gemp-lotr/gemp-lotr-server/src/test/java/com/gempukku/lotro/cards/unofficial/pc/errata/set01/Card_01_311_ErrataTests.java @@ -162,7 +162,7 @@ public class Card_01_311_ErrataTests scn.PassCurrentPhaseActions(); - assertTrue(scn.FreepsActionAvailable("Optional Trigger")); + assertTrue(scn.FreepsHasOptionalTriggerAvailable()); scn.FreepsAcceptOptionalTrigger(); assertSame(scn.GetRingBearer(), sam); } From dbce30b436e562a1d41a54f6688c938c6d6561ad Mon Sep 17 00:00:00 2001 From: Christian 'ketura' McCarty Date: Thu, 20 Jun 2024 00:02:21 -0500 Subject: [PATCH 02/28] League auto-awarding custom prizes - Added ability for constructed leagues to have a custom top X placement / Y game participation prize - Overhauled constructed league interface so that both preview + creation use the same validation - Overhauled constructed league admin page section - Added testing for custom prizes allocation - Have not yet figured out exactly how to test real-world league ending causing the correct rewards to go out. --- .../async/handler/AdminRequestHandler.java | 267 +++++++++--------- .../handler/LotroServerRequestHandler.java | 177 +++++++++++- .../async/handler/ReplayRequestHandler.java | 7 - .../src/main/web/css/gemp-001/hall.css | 61 ++++ .../main/web/includes/admin/leagueAdmin.html | 197 +++++++++---- .../src/main/web/js/gemp-022/communication.js | 39 +-- .../com/gempukku/lotro/logic/GameUtils.java | 4 + .../gempukku/lotro/game/CardCollection.java | 29 +- .../lotro/game/DefaultCardCollection.java | 7 + .../lotro/game/MutableCardCollection.java | 1 + .../lotro/league/ConstructedLeagueData.java | 2 +- .../lotro/league/EventAutoPrizes.java | 11 + .../lotro/league/FixedLeaguePrizes.java | 3 +- .../lotro/league/IncentiveLeaguePrizes.java | 47 +++ .../gempukku/lotro/league/LeaguePrizes.java | 5 +- .../lotro/league/LeagueSerieData.java | 18 +- .../league/NewConstructedLeagueData.java | 26 +- .../lotro/league/NewSealedLeagueData.java | 2 +- .../lotro/league/SealedLeagueData.java | 2 +- .../lotro/league/SoloDraftLeagueData.java | 2 +- .../com/gempukku/lotro/packs/PackBox.java | 6 +- .../lotro/league/LeaguePrizesTest.java | 81 ++++++ .../lotro/league/LeagueServiceTest.java | 18 +- 23 files changed, 753 insertions(+), 259 deletions(-) create mode 100644 gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/league/EventAutoPrizes.java create mode 100644 gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/league/IncentiveLeaguePrizes.java diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/AdminRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/AdminRequestHandler.java index 32332bd54..a793df74e 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/AdminRequestHandler.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/AdminRequestHandler.java @@ -17,6 +17,7 @@ import com.gempukku.lotro.game.formats.LotroFormatLibrary; import com.gempukku.lotro.hall.GameTimer; import com.gempukku.lotro.hall.HallServer; import com.gempukku.lotro.league.*; +import com.gempukku.lotro.logic.GameUtils; import com.gempukku.lotro.logic.vo.LotroDeck; import com.gempukku.lotro.packs.ProductLibrary; import com.gempukku.lotro.service.AdminService; @@ -24,6 +25,7 @@ import com.gempukku.lotro.tournament.TournamentService; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger;import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -88,9 +90,9 @@ public class AdminRequestHandler extends LotroServerRequestHandler implements Ur } else if (uri.equals("/addSealedLeague") && request.method() == HttpMethod.POST) { addSealedLeague(request, responseWriter); } else if (uri.equals("/previewConstructedLeague") && request.method() == HttpMethod.POST) { - previewConstructedLeague(request, responseWriter); + processConstructedLeague(request, responseWriter, true); } else if (uri.equals("/addConstructedLeague") && request.method() == HttpMethod.POST) { - addConstructedLeague(request, responseWriter); + processConstructedLeague(request, responseWriter, false); } else if (uri.equals("/addTables") && request.method() == HttpMethod.POST) { addTables(request, responseWriter); } else if (uri.equals("/previewSoloDraftLeague") && request.method() == HttpMethod.POST) { @@ -266,7 +268,7 @@ public class AdminRequestHandler extends LotroServerRequestHandler implements Ur String product = getFormParameterSafely(postDecoder, "product"); String collectionType = getFormParameterSafely(postDecoder, "collectionType"); - Collection productItems = getProductItems(product); + var productItems = CardCollection.Item.createItems(product); Map playersCollection = _collectionManager.getPlayersCollection(collectionType); @@ -288,9 +290,9 @@ public class AdminRequestHandler extends LotroServerRequestHandler implements Ur String product = getFormParameterSafely(postDecoder, "product"); String collectionType = getFormParameterSafely(postDecoder, "collectionType"); - Collection productItems = getProductItems(product); + var productItems = CardCollection.Item.createItems(product); - List playerNames = getItems(players); + List playerNames = getPlayerNames(players); for (String playerName : playerNames) { Player player = _playerDao.getPlayer(playerName); @@ -304,30 +306,16 @@ public class AdminRequestHandler extends LotroServerRequestHandler implements Ur } } - private List getItems(String values) { + private List getPlayerNames(String values) { List result = new LinkedList<>(); for (String pack : values.split("\n")) { String blueprint = pack.trim(); - if (blueprint.length() > 0) + if (!blueprint.isEmpty()) result.add(blueprint); } return result; } - private Collection getProductItems(String values) { - List result = new LinkedList<>(); - for (String item : values.split("\n")) { - item = item.trim(); - if (item.length() > 0) { - final String[] itemSplit = item.split("x", 2); - if (itemSplit.length != 2) - throw new RuntimeException("Unable to parse the items"); - result.add(CardCollection.Item.createItem(itemSplit[1].trim(), Integer.parseInt(itemSplit[0].trim()))); - } - } - return result; - } - private CollectionType createCollectionType(String collectionType) { final CollectionType result = CollectionType.parseCollectionCode(collectionType); if (result != null) @@ -395,128 +383,147 @@ public class AdminRequestHandler extends LotroServerRequestHandler implements Ur } } - private void addConstructedLeague(HttpRequest request, ResponseWriter responseWriter) throws Exception { + /** + * Processes the passed parameters for a theoretical Constructed League. Based on the preview parameter, this will + * either create the league for real, or just return the parsed values to the client so the admin can preview + * the input. + * @param request the request + * @param responseWriter the response writer + * @param preview If true, no league will be created and the client will have an XML payload returned representing + * what the league would be upon creation. If false, the league will be created for real. + * @throws Exception + */ + private void processConstructedLeague(HttpRequest request, ResponseWriter responseWriter, boolean preview) throws Exception { validateLeagueAdmin(request); - HttpPostRequestDecoder postDecoder = new HttpPostRequestDecoder(request); - try { - String start = getFormParameterSafely(postDecoder, "start"); - String collectionType = getFormParameterSafely(postDecoder, "collectionType"); - String prizeMultiplier = getFormParameterSafely(postDecoder, "prizeMultiplier"); - List formats = getFormMultipleParametersSafely(postDecoder, "format[]"); - List serieDurations = getFormMultipleParametersSafely(postDecoder, "serieDuration[]"); - List maxMatches = getFormMultipleParametersSafely(postDecoder, "maxMatches[]"); - String name = getFormParameterSafely(postDecoder, "name"); - String costStr = getFormParameterSafely(postDecoder, "cost"); + var postDecoder = new HttpPostRequestDecoder(request); - if(start == null || start.trim().isEmpty() - ||collectionType == null || collectionType.trim().isEmpty() - ||prizeMultiplier == null || prizeMultiplier.trim().isEmpty() - ||name == null || name.trim().isEmpty() - ||costStr == null || costStr.trim().isEmpty()) { - throw new HttpProcessingException(400); - } + String name = getFormParameterSafely(postDecoder, "name"); + String costStr = getFormParameterSafely(postDecoder, "cost"); + String startStr = getFormParameterSafely(postDecoder, "start"); + String collectionType = getFormParameterSafely(postDecoder, "collectionType"); - if(formats.size() != serieDurations.size() || formats.size() != maxMatches.size()) - throw new HttpProcessingException(400); + String maxRepeatMatchesStr = getFormParameterSafely(postDecoder, "maxRepeatMatches"); + String topPrizeStr = getFormParameterSafely(postDecoder, "topPrize"); + String topCutoffStr = getFormParameterSafely(postDecoder, "topCutoff"); + String participationPrizeStr = getFormParameterSafely(postDecoder, "participationPrize"); + String participationGamesStr = getFormParameterSafely(postDecoder, "participationGames"); + //Individual serie definitions + List formats = getFormMultipleParametersSafely(postDecoder, "format[]"); + List serieDurationsStr = getFormMultipleParametersSafely(postDecoder, "serieDuration[]"); + List maxMatchesStr = getFormMultipleParametersSafely(postDecoder, "maxMatches[]"); - int cost = Integer.parseInt(costStr); + Throw400IfStringNull("name", name); + int cost = Throw400IfNullOrNonInteger("cost", costStr); + if(startStr.length() != 8) + throw new HttpProcessingException(400, "Parameter 'start' must be exactly 8 digits long: YYYYMMDD"); + int start = Throw400IfNullOrNonInteger("start", startStr); + Throw400IfStringNull("collectionType", collectionType); + int maxRepeatMatches = Throw400IfNullOrNonInteger("maxRepeatMatches", maxRepeatMatchesStr); + int topCutoff = Throw400IfNullOrNonInteger("topCutoff", topCutoffStr); + int participationGames = Throw400IfNullOrNonInteger("participationGames", participationGamesStr); + Throw400IfAnyStringNull("formats", formats); + List serieDurations = Throw400IfAnyNullOrNonInteger("serieDurations", serieDurationsStr); + List maxMatches = Throw400IfAnyNullOrNonInteger("maxMatches", maxMatchesStr); - String code = String.valueOf(System.currentTimeMillis()); + if(formats.size() != serieDurations.size() || formats.size() != maxMatches.size()) + throw new HttpProcessingException(400, "Size mismatch between provided formats, serieDurations, and maxMatches"); - StringBuilder sb = new StringBuilder(); - //The 1 is a hard-coded maximum number of player matches per league. - //TODO: Get this put into the UI properly. - sb.append(start + "," + collectionType + "," + prizeMultiplier + "," + "1" + "," + formats.size()); - for (int i = 0; i < formats.size(); i++) - sb.append("," + formats.get(i) + "," + serieDurations.get(i) + "," + maxMatches.get(i)); + String code = String.valueOf(System.currentTimeMillis()); - String parameters = sb.toString(); - LeagueData leagueData = new NewConstructedLeagueData(_productLibrary, _formatLibrary, parameters); - List series = leagueData.getSeries(); - int leagueStart = series.get(0).getStart(); - int displayEnd = DateUtils.offsetDate(series.get(series.size() - 1).getEnd(), 2); + String parameters = start + "," + collectionType + "," + maxRepeatMatches + + "," + topPrizeStr + "," + topCutoff + "," + participationPrizeStr + "," + participationGames + + "," + formats.size(); + for (int i = 0; i < formats.size(); i++) { + parameters += "," + formats.get(i) + "," + serieDurations.get(i) + "," + maxMatches.get(i); + } + LeagueData leagueData = new NewConstructedLeagueData(_productLibrary, _formatLibrary, parameters); + List series = leagueData.getSeries(); + + int leagueStart = series.getFirst().getStart(); + int displayEnd = DateUtils.offsetDate(series.getLast().getEnd(), 2); + + if(!preview) { _leagueDao.addLeague(cost, name, code, leagueData.getClass().getName(), parameters, leagueStart, displayEnd); _leagueService.clearCache(); responseWriter.writeHtmlResponse("OK"); - } finally { - postDecoder.destroy(); + return; } - } - private void previewConstructedLeague(HttpRequest request, ResponseWriter responseWriter) throws Exception { - validateLeagueAdmin(request); + //We aren't creating the league for real, so instead we will return the league in XML format for the + // admin panel preview. - HttpPostRequestDecoder postDecoder = new HttpPostRequestDecoder(request); + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + + Document doc = documentBuilder.newDocument(); + + Element leagueElem = doc.createElement("league"); + + leagueElem.setAttribute("name", name); + leagueElem.setAttribute("cost", String.valueOf(cost)); + leagueElem.setAttribute("start", String.valueOf(series.getFirst().getStart())); + leagueElem.setAttribute("end", String.valueOf(displayEnd)); + leagueElem.setAttribute("collection", collectionType); + + leagueElem.setAttribute("maxRepeatMatches", String.valueOf(maxRepeatMatches)); + var topPrize = CardCollection.Item.createItem(topPrizeStr); + var partPrize = CardCollection.Item.createItem(participationPrizeStr); + + String topID = topPrize.getBlueprintId(); + String top = ""; try { - String start = getFormParameterSafely(postDecoder, "start"); - String collectionType = getFormParameterSafely(postDecoder, "collectionType"); - String prizeMultiplier = getFormParameterSafely(postDecoder, "prizeMultiplier"); - List formats = getFormMultipleParametersSafely(postDecoder, "format[]"); - List serieDurations = getFormMultipleParametersSafely(postDecoder, "serieDuration[]"); - List maxMatches = getFormMultipleParametersSafely(postDecoder, "maxMatches[]"); - String name = getFormParameterSafely(postDecoder, "name"); - String costStr = getFormParameterSafely(postDecoder, "cost"); - - if(start == null || start.trim().isEmpty() - ||collectionType == null || collectionType.trim().isEmpty() - ||prizeMultiplier == null || prizeMultiplier.trim().isEmpty() - ||name == null || name.trim().isEmpty() - ||costStr == null || costStr.trim().isEmpty()) { - throw new HttpProcessingException(400); + if(!StringUtils.isBlank(topID)) { + if (_cardLibrary.getLotroCardBlueprint(topID) != null) { + top = topPrize.getCount() + "x " + GameUtils.getDeluxeCardLink(topID, _cardLibrary.getLotroCardBlueprint(topID)); + } else if (_productLibrary.GetProduct(topID) != null) { + top = topPrize.getCount() + "x " + GameUtils.getProductLink(topID); + } } - - if(formats.size() != serieDurations.size() || formats.size() != maxMatches.size()) - throw new HttpProcessingException(400); - - int cost = Integer.parseInt(costStr); - - StringBuilder sb = new StringBuilder(); - sb.append(start + "," + collectionType + "," + prizeMultiplier + "," + "1" + "," + formats.size()); - for (int i = 0; i < formats.size(); i++) - sb.append("," + formats.get(i) + "," + serieDurations.get(i) + "," + maxMatches.get(i)); - - String parameters = sb.toString(); - LeagueData leagueData = new NewConstructedLeagueData(_productLibrary, _formatLibrary, parameters); - - DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); - - Document doc = documentBuilder.newDocument(); - - final List series = leagueData.getSeries(); - - int end = series.get(series.size() - 1).getEnd(); - - Element leagueElem = doc.createElement("league"); - - leagueElem.setAttribute("name", name); - leagueElem.setAttribute("cost", String.valueOf(cost)); - leagueElem.setAttribute("start", String.valueOf(series.get(0).getStart())); - leagueElem.setAttribute("end", String.valueOf(end)); - - for (LeagueSerieData serie : series) { - Element serieElem = doc.createElement("serie"); - serieElem.setAttribute("type", serie.getName()); - serieElem.setAttribute("maxMatches", String.valueOf(serie.getMaxMatches())); - serieElem.setAttribute("start", String.valueOf(serie.getStart())); - serieElem.setAttribute("end", String.valueOf(serie.getEnd())); - serieElem.setAttribute("format", serie.getFormat().getName()); - serieElem.setAttribute("collection", serie.getCollectionType().getFullName()); - serieElem.setAttribute("limited", String.valueOf(serie.isLimited())); - - leagueElem.appendChild(serieElem); - } - - doc.appendChild(leagueElem); - - responseWriter.writeXmlResponse(doc); - } finally { - postDecoder.destroy(); } + catch (Exception ex){ + top = topPrize.getCount() + "x " + "[UNKNOWN: " + topID + "]"; + } + + String partID = partPrize.getBlueprintId(); + String part = ""; + try { + if(!StringUtils.isBlank(partID)) { + if (_cardLibrary.getLotroCardBlueprint(partID) != null) { + part = partPrize.getCount() + "x " + GameUtils.getDeluxeCardLink(partID, _cardLibrary.getLotroCardBlueprint(partID)); + } else if (_productLibrary.GetProduct(partID) != null) { + part = partPrize.getCount() + "x " + GameUtils.getProductLink(partID); + } + } + } + catch (Exception ex){ + top = partPrize.getCount() + "x " + "[UNKNOWN: " + topID + "]"; + } + + leagueElem.setAttribute("topPrize", top); + leagueElem.setAttribute("topCutoff", String.valueOf(topCutoff)); + leagueElem.setAttribute("participationPrize", part); + leagueElem.setAttribute("participationGames", String.valueOf(participationGames)); + + for (LeagueSerieData serie : series) { + Element serieElem = doc.createElement("serie"); + serieElem.setAttribute("type", serie.getName()); + serieElem.setAttribute("maxMatches", String.valueOf(serie.getMaxMatches())); + serieElem.setAttribute("start", String.valueOf(serie.getStart())); + serieElem.setAttribute("end", String.valueOf(serie.getEnd())); + serieElem.setAttribute("format", serie.getFormat().getName()); + + serieElem.setAttribute("limited", String.valueOf(serie.isLimited())); + + leagueElem.appendChild(serieElem); + } + + doc.appendChild(leagueElem); + + responseWriter.writeXmlResponse(doc); } private void addSoloDraftLeague(HttpRequest request, ResponseWriter responseWriter) throws Exception { @@ -817,18 +824,4 @@ public class AdminRequestHandler extends LotroServerRequestHandler implements Ur responseWriter.writeHtmlResponse("Before: " + before + "

After: " + after); } - - private void validateAdmin(HttpRequest request) throws HttpProcessingException { - Player player = getResourceOwnerSafely(request, null); - - if (!player.hasType(Player.Type.ADMIN)) - throw new HttpProcessingException(403); - } - - private void validateLeagueAdmin(HttpRequest request) throws HttpProcessingException { - Player player = getResourceOwnerSafely(request, null); - - if (!player.hasType(Player.Type.LEAGUE_ADMIN)) - throw new HttpProcessingException(403); - } } diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/LotroServerRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/LotroServerRequestHandler.java index d1f07c2f8..30d8640cf 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/LotroServerRequestHandler.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/LotroServerRequestHandler.java @@ -17,6 +17,7 @@ import io.netty.handler.codec.http.cookie.ServerCookieEncoder; import io.netty.handler.codec.http.multipart.Attribute; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; import io.netty.handler.codec.http.multipart.InterfaceHttpData; +import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.IOException; @@ -115,15 +116,15 @@ public class LotroServerRequestHandler { Player resourceOwner = _playerDao.getPlayer("Librarian"); if (resourceOwner == null) - throw new HttpProcessingException(401); + throw new HttpProcessingException(401, "Librarian user not found."); return resourceOwner; } protected String getQueryParameterSafely(QueryStringDecoder queryStringDecoder, String parameterName) { List parameterValues = queryStringDecoder.parameters().get(parameterName); - if (parameterValues != null && parameterValues.size() > 0) - return parameterValues.get(0); + if (parameterValues != null && !parameterValues.isEmpty()) + return parameterValues.getFirst(); else return null; } @@ -180,4 +181,174 @@ public class LotroServerRequestHandler { String sessionId = _loggedUserHolder.logUser(login); return Collections.singletonMap(SET_COOKIE.toString(), ServerCookieEncoder.STRICT.encode("loggedUser", sessionId)); } + + /** + * This function is for validating incoming parameters on POST requests. If the passed parameter is empty or null, + * then HTTP 400 (Bad Request) will be thrown to inform the user that they need to fix their call. + * This should only be used for required fields. If a string is optional, then of course it should be fine to send + * a blank entry. + * @param paramName The name of the API parameter being evaluated. This is purely used to attach a sensible + * error message to the error. + * @param value The value being validated. Should be a non-empty non-null string. + * @throws HttpProcessingException This function throws HTTP 400 (Bad Request) if the value is not a valid string. + */ + protected void Throw400IfStringNull(String paramName, String value) throws HttpProcessingException { + if(StringUtils.isEmpty(value)) { + throw new HttpProcessingException(400, "Parameter '" + paramName + "' cannot be blank."); + } + } + + /** + * This function is for validating incoming parameters on POST requests. If the passed parameter is not a + * representation of a list of valid strings, then HTTP 400 (Bad Request) will be thrown to inform the user + * that they need to fix their call. + * This is for a batch of strings, such as those submitted via form data. + * This should only be used for required fields. If a string is optional, then of course it should be fine to send + * a blank entry. + * @param paramName The name of the API parameter being evaluated. This is purely used to attach a sensible + * error message to the error. + * @param values The values being validated. Should be a list of valid non-empty non-null strings. + * @throws HttpProcessingException This function throws HTTP 400 (Bad Request) if any value is not a valid string. + */ + protected void Throw400IfAnyStringNull(String paramName, List values) throws HttpProcessingException { + for (String value : values) { + if(StringUtils.isEmpty(value)) { + throw new HttpProcessingException(400, "Parameter '" + paramName + "' cannot be blank."); + } + } + } + + /** + * This function is for validating incoming parameters on POST requests. If the passed parameter is not a + * representation of a valid integer, then HTTP 400 (Bad Request) will be thrown to inform the user + * that they need to fix their call. + * @param paramName The name of the API parameter being evaluated. This is purely used to attach a sensible + * error message to the error. + * @param value The value being validated. Should be a valid integer. + * @return For convenience, the converted int will be returned, meaning that higher-level functions can + * validate and convert in a single function call. + * @throws HttpProcessingException This function throws HTTP 400 (Bad Request) if the value is not a valid integer. + */ + protected int Throw400IfNullOrNonInteger(String paramName, String value) throws HttpProcessingException { + if(StringUtils.isEmpty(value)) { + throw new HttpProcessingException(400, "Parameter '" + paramName + "' cannot be blank."); + } + int newValue; + try { + newValue = Integer.parseInt(value); + } + catch (NumberFormatException ex) { + throw new HttpProcessingException(400, "Parameter '" + paramName + "' must be a valid numeric integer."); + } + + return newValue; + } + + /** + * This function is for validating incoming parameters on POST requests. If the passed parameter is not a + * representation of a valid float, then HTTP 400 (Bad Request) will be thrown to inform the user + * that they need to fix their call. + * @param paramName The name of the API parameter being evaluated. This is purely used to attach a sensible + * error message to the error. + * @param value The value being validated. Should be a valid float. + * @return For convenience, the converted float will be returned, meaning that higher-level functions can + * validate and convert in a single function call. + * @throws HttpProcessingException This function throws HTTP 400 (Bad Request) if the value is not a valid float. + */ + protected float Throw400IfNullOrNonFloat(String paramName, String value) throws HttpProcessingException { + if(StringUtils.isEmpty(value)) { + throw new HttpProcessingException(400, "Parameter '" + paramName + "' cannot be blank."); + } + float newValue; + try { + newValue = Float.parseFloat(value); + } + catch (NumberFormatException ex) { + throw new HttpProcessingException(400, "Parameter '" + paramName + "' must be a valid numeric float."); + } + + return newValue; + } + + /** + * This function is for validating incoming parameters on POST requests. If the passed parameter is not a + * representation of a list of valid integers, then HTTP 400 (Bad Request) will be thrown to inform the user + * that they need to fix their call. + * This is for a batch of integers, such as those submitted via form data. + * @param paramName The name of the API parameter being evaluated. This is purely used to attach a sensible + * error message to the error. + * @param values The values being validated. Should be a list of valid integers. + * @return For convenience, the converted ints will be returned, meaning that higher-level functions can + * validate and convert in a single function call. + * @throws HttpProcessingException This function throws HTTP 400 (Bad Request) if any value is not a valid integer. + */ + protected List Throw400IfAnyNullOrNonInteger(String paramName, List values) throws HttpProcessingException { + List newValues = new ArrayList<>(); + + for(String value : values) { + if(StringUtils.isEmpty(value)) { + throw new HttpProcessingException(400, "Parameter '" + paramName + "' cannot be blank."); + } + int newValue; + try { + newValue = Integer.parseInt(value); + } + catch (NumberFormatException ex) { + throw new HttpProcessingException(400, "Parameter '" + paramName + "' must be a valid numeric integer: '" + value + "'."); + } + newValues.add(newValue); + } + + return newValues; + } + + /** + * This function is for validating incoming parameters on POST requests. If the passed parameter is not a + * representation of a valid boolean variable, then HTTP 400 (Bad Request) will be thrown to inform the user + * that they need to fix their call. + * @param paramName The name of the API parameter being evaluated. This is purely used to attach a sensible + * error message to the error. + * @param value The value being validated. Should be some representation of "true" or "false". + * @return For convenience, the converted boolean will be returned, meaning that higher-level functions can + * validate and convert in a single function call. + * @throws HttpProcessingException This function throws HTTP 400 (Bad Request) if the value is not a valid boolean. + */ + protected boolean Throw400IfNullOrNonBoolean(String paramName, String value) throws HttpProcessingException { + if(StringUtils.isEmpty(value)) { + throw new HttpProcessingException(400, "Parameter '" + paramName + "' cannot be blank."); + } + boolean newValue; + try { + newValue = Boolean.parseBoolean(value); + } + catch (NumberFormatException ex) { + throw new HttpProcessingException(400, "Parameter '" + paramName + "' must be a valid boolean value ('true' or 'false')."); + } + + return newValue; + } + + /** + * Verifies the request is from a full admin user and nothing less. + * @param request the HTTP Request sent from communication.js + * @throws HttpProcessingException This function throws HTTP 403 (Forbidden) if the user is not a full admin. + */ + protected void validateAdmin(HttpRequest request) throws HttpProcessingException { + Player player = getResourceOwnerSafely(request, null); + + if (!player.hasType(Player.Type.ADMIN)) + throw new HttpProcessingException(403); + } + + /** + * Verifies the request is from an admin (or league admin) user. + * @param request the HTTP Request sent from communication.js + * @throws HttpProcessingException This function throws HTTP 403 (Forbidden) if the user is not a league admin. + */ + protected void validateLeagueAdmin(HttpRequest request) throws HttpProcessingException { + Player player = getResourceOwnerSafely(request, null); + + if (!player.hasType(Player.Type.ADMIN) && !player.hasType(Player.Type.LEAGUE_ADMIN)) + throw new HttpProcessingException(403); + } } diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/ReplayRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/ReplayRequestHandler.java index 30ef2ca62..f77d63ce4 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/ReplayRequestHandler.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/ReplayRequestHandler.java @@ -112,11 +112,4 @@ public class ReplayRequestHandler extends LotroServerRequestHandler implements U 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); - } } diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/css/gemp-001/hall.css b/gemp-lotr/gemp-lotr-async/src/main/web/css/gemp-001/hall.css index 29910837b..8357b3874 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/css/gemp-001/hall.css +++ b/gemp-lotr/gemp-lotr-async/src/main/web/css/gemp-001/hall.css @@ -16,6 +16,15 @@ html, body { gap: 2px; } +.flex-fill { + flex-grow: 1; +} + +.flex-static { + flex-grow: 0; +} + + a:visited { color: #A63F03; } @@ -734,3 +743,55 @@ table.tables tr.privateForPlayer { font-style: italic; padding: 1em; } + +.league-form { + display: flex; + flex-direction: column; + width: fit-content; + max-width: 60%; + margin: auto; + align-items: stretch; + gap: 0.4em; +} + +.serieData { + border-style: solid; + padding: 1em; + margin: 0.5em; + gap: 0.4em; + display: flex; + flex-direction: column; +} + +.label { + padding: 1em; + +} + +#constructed-league-result-label { + align-items: center; +} + +#constructed-league-result { + text-wrap: balance; + padding: 0.5em; + margin: 0.5em; + background: #333333; + border-style: solid; + border-color: #555555; + border-width: 1px; +} + +#prize-info { + text-wrap: balance; + padding: 1em; + margin: 1em; + background: #222222; + border-style: solid; + border-color: #555555; + border-width: 1px; +} + +.label-column { + width: 50%; +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/includes/admin/leagueAdmin.html b/gemp-lotr/gemp-lotr-async/src/main/web/includes/admin/leagueAdmin.html index 8a8bb82d7..f31d73008 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/includes/admin/leagueAdmin.html +++ b/gemp-lotr/gemp-lotr-async/src/main/web/includes/admin/leagueAdmin.html @@ -1,8 +1,9 @@ + @@ -27,6 +32,7 @@
  • General Admin
  • League Admin
  • +
  • Tournament Admin
  • Manage Users
  • diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/includes/admin/leagueAdmin.html b/gemp-lotr/gemp-lotr-async/src/main/web/includes/admin/leagueAdmin.html index f31d73008..4d3e3fe81 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/includes/admin/leagueAdmin.html +++ b/gemp-lotr/gemp-lotr-async/src/main/web/includes/admin/leagueAdmin.html @@ -100,8 +100,8 @@ $(document).ready( var now = new Date(); var nowStr = "" + now.getFullYear() + String(1 + now.getMonth()).padStart(2, '0') + String(now.getDate()).padStart(2, '0'); - $("#sealed-start").val(nowStr); - $("#solo-draft-start").val(nowStr); + $("#sealed-start").val(now.toISOString().substr(0, 10)); + $("#solo-draft-start").val(now.toISOString().substr(0, 10)); $("#constructed-start").val(now.toISOString().substr(0, 10)); var previewError = function (xhr) { @@ -109,18 +109,31 @@ $(document).ready( alert("Invalid parameters specified - error code: " + xhr.status); }; + updateDescPreview("#sealed-desc", "#sealed-desc-preview"); + $("#preview-sealed-league-button").button().click( function () { let resultdiv = $("#sealed-league-result"); resultdiv.html("Processing..."); - - hall.comm.previewSealedLeague( - $("#sealed-format").val(), - $("#sealed-start").val(), + + hall.comm.processSealedLeague( + true, + $("#sealed-start").val().replaceAll("-",""), + $("#sealed-name").val(), + $("#sealed-cost").val(), + $("#sealed-format").val(), $("#sealed-duration").val(), $("#sealed-matches").val(), - $("#sealed-name").val(), - $("#sealed-cost").val(), + + $("#sealed-repeat-games").val(), + $("#sealed-invite-only").prop('checked'), + $("#sealed-desc").val(), + + $("#sealed-top-prize").val(), + $("#sealed-top-pos").val(), + $("#sealed-part-prize").val(), + $("#sealed-part-games").val(), + function (xml) { previewDialog.html(""); displayPreview(xml); @@ -133,32 +146,55 @@ $(document).ready( function () { let resultdiv = $("#sealed-league-result"); resultdiv.html("Processing..."); - - hall.comm.addSealedLeague( - $("#sealed-format").val(), - $("#sealed-start").val(), + + hall.comm.processSealedLeague( + false, + $("#sealed-start").val().replaceAll("-",""), + $("#sealed-name").val(), + $("#sealed-cost").val(), + $("#sealed-format").val(), $("#sealed-duration").val(), $("#sealed-matches").val(), - $("#sealed-name").val(), - $("#sealed-cost").val(), + + $("#sealed-repeat-games").val(), + $("#sealed-invite-only").prop('checked'), + $("#sealed-desc").val(), + + $("#sealed-top-prize").val(), + $("#sealed-top-pos").val(), + $("#sealed-part-prize").val(), + $("#sealed-part-games").val(), + function (xml) { - resultdiv.html("OK"); + resultdiv.html("League created successfully."); }, leagueErrorMap(resultdiv)); }); + updateDescPreview("#solo-draft-desc", "#solo-draft-desc-preview"); $("#preview-solo-draft-league-button").button().click( function () { let resultdiv = $("#solo-draft-league-result"); resultdiv.html("Processing..."); - - hall.comm.previewSoloDraftLeague( - $("#solo-draft-format").val(), - $("#solo-draft-start").val(), + + hall.comm.processSoloDraftLeague( + true, + $("#solo-draft-start").val().replaceAll("-",""), + $("#solo-draft-name").val(), + $("#solo-draft-cost").val(), + $("#solo-draft-format").val(), $("#solo-draft-duration").val(), $("#solo-draft-matches").val(), - $("#solo-draft-name").val(), - $("#solo-draft-cost").val(), + + $("#solo-draft-repeat-games").val(), + $("#solo-draft-invite-only").prop('checked'), + $("#solo-draft-desc").val(), + + $("#solo-draft-top-prize").val(), + $("#solo-draft-top-pos").val(), + $("#solo-draft-part-prize").val(), + $("#solo-draft-part-games").val(), + function (xml) { previewDialog.html(""); displayPreview(xml); @@ -171,19 +207,31 @@ $(document).ready( function () { let resultdiv = $("#solo-draft-league-result"); resultdiv.html("Processing..."); - - hall.comm.addSoloDraftLeague( - $("#solo-draft-format").val(), - $("#solo-draft-start").val(), + + hall.comm.processSoloDraftLeague( + false, + $("#solo-draft-start").val().replaceAll("-",""), + $("#solo-draft-name").val(), + $("#solo-draft-cost").val(), + $("#solo-draft-format").val(), $("#solo-draft-duration").val(), $("#solo-draft-matches").val(), - $("#solo-draft-name").val(), - $("#solo-draft-cost").val(), + + $("#solo-draft-repeat-games").val(), + $("#solo-draft-invite-only").prop('checked'), + $("#solo-draft-desc").val(), + + $("#solo-draft-top-prize").val(), + $("#solo-draft-top-pos").val(), + $("#solo-draft-part-prize").val(), + $("#solo-draft-part-games").val(), + function (xml) { - resultdiv.html("OK"); + resultdiv.html("League created successfully."); }, leagueErrorMap(resultdiv)); }); + updateDescPreview("#constructed-desc", "#constructed-desc-preview"); $("#preview-constructed-league-button").button().click( function () { let resultdiv = $("#constructed-league-result"); @@ -198,6 +246,8 @@ $(document).ready( $("#constructed-name").val(), $("#constructed-cost").val(), $("#constructed-repeat-games").val(), + $("#constructed-invite-only").prop('checked'), + $("#constructed-desc").val(), $("#constructed-top-prize").val(), $("#constructed-top-pos").val(), @@ -229,6 +279,8 @@ $(document).ready( $("#constructed-name").val(), $("#constructed-cost").val(), $("#constructed-repeat-games").val(), + $("#constructed-invite-only").prop('checked'), + $("#constructed-desc").val(), $("#constructed-top-prize").val(), $("#constructed-top-pos").val(), @@ -248,29 +300,17 @@ $(document).ready( $(".serieData").last().clone().appendTo(".series"); }); - $("#add-additional-table-button").button().click( - function () { - $(".tabledata").last().clone().appendTo(".tablesgroup"); - }); - - $("#add-tables-button").button().click( + $("#add-league-players-button").button().click( function () { - let resultdiv = $("#tables-result"); + let resultdiv = $("#league-add-result"); resultdiv.html("Processing..."); - - var data = gatherData($(".tablesgroup")) - - console.log(data); - - hall.comm.addTables( - $("#table-name").val(), - $("#table-tournament").val(), - $("#table-format").val(), - $("#table-timer").val(), - data.playerone, - data.playertwo, + + hall.comm.addLeaguePlayers( + $("#league-to-add").val(), + $("#players-to-add").val().split('\n'), + function (xml) { - resultdiv.html("OK"); + resultdiv.html("Added players to league successfully."); }, leagueErrorMap(resultdiv)); }); @@ -279,9 +319,9 @@ $(document).ready( function (json) { //console.log(json); - let drafts = json.draftTemplates; - let formats = json.formats; - let sealed = json.sealedTemplates + let drafts = json.DraftTemplates; + let formats = json.Formats; + let sealed = json.SealedTemplates //console.log(drafts); for (var prop in drafts) { if (Object.prototype.hasOwnProperty.call(drafts, prop)) { @@ -336,29 +376,6 @@ $(document).ready( } sortOptionsByName("#sealed-format"); - for (var prop in formats) { - if (Object.prototype.hasOwnProperty.call(formats, prop)) { - //console.log(prop); - - if(formats[prop].name.includes("Limited")) - continue; - - let num = ("0000" + formats[prop].order).substr(-4); - - var item = $("