From edfa007ad0e96dfd277c5d403def53fe470a052f Mon Sep 17 00:00:00 2001 From: Christian 'ketura' McCarty Date: Mon, 22 Mar 2021 01:47:24 -0500 Subject: [PATCH] Playtesting tab Adding a new playtesting tab that explains the PC playtesting initiative. A new 't' flag is now part of the user type as a result. The three PC Playtesting formats will not show unless the user opts in. --- .../async/GempukkuHttpRequestHandler.java | 7 +- .../async/handler/HallRequestHandler.java | 6 + .../async/handler/PlaytestRequestHandler.java | 105 ++++++++++++++++++ .../async/handler/RootUriRequestHandler.java | 6 + .../gemp-lotr-async/src/main/web/hall.html | 7 ++ .../src/main/web/includes/playtesting.html | 16 +++ .../gemp-lotr-async/src/main/web/index.html | 2 +- .../src/main/web/js/gemp-022/communication.js | 27 +++++ .../src/main/web/js/gemp-022/hallUi.js | 21 +++- .../gempukku/lotro/db/CachedPlayerDAO.java | 16 +++ .../com/gempukku/lotro/db/DbPlayerDAO.java | 24 ++++ .../java/com/gempukku/lotro/db/PlayerDAO.java | 3 + .../src/main/resources/lotrFormats.json | 9 +- 13 files changed, 243 insertions(+), 6 deletions(-) create mode 100644 gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/PlaytestRequestHandler.java create mode 100644 gemp-lotr/gemp-lotr-async/src/main/web/includes/playtesting.html diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/GempukkuHttpRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/GempukkuHttpRequestHandler.java index 2e0eb3231..4a717211e 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/GempukkuHttpRequestHandler.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/GempukkuHttpRequestHandler.java @@ -83,8 +83,13 @@ public class GempukkuHttpRequestHandler extends SimpleChannelInboundHandler -1) uri = uri.substring(0, uri.indexOf("?")); + String ip = httpRequest.headers().get("X-Forwarded-For"); + + if(ip == null) + ip = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress(); + final RequestInformation requestInformation = new RequestInformation(httpRequest.getUri(), - httpRequest.headers().get("X-Forwarded-For"), + ip, System.currentTimeMillis()); ResponseSender responseSender = new ResponseSender(ctx, httpRequest); diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/HallRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/HallRequestHandler.java index 1690740ac..1867a5d77 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/HallRequestHandler.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/HallRequestHandler.java @@ -286,11 +286,17 @@ public class HallRequestHandler extends LotroServerRequestHandler implements Uri Document doc = documentBuilder.newDocument(); + Player player = getResourceOwnerSafely(request, null); + Element hall = doc.createElement("hall"); hall.setAttribute("currency", String.valueOf(_collectionManager.getPlayerCollection(resourceOwner, CollectionType.MY_CARDS.getCode()).getCurrency())); _hallServer.signupUserForHall(resourceOwner, new SerializeHallInfoVisitor(doc, hall)); for (Map.Entry format : _formatLibrary.getHallFormats().entrySet()) { + //playtest formats are opt-in + if (format.getKey().startsWith("test") && !player.getType().contains("t")) + continue; + Element formatElem = doc.createElement("format"); formatElem.setAttribute("type", format.getKey()); formatElem.appendChild(doc.createTextNode(format.getValue().getName())); diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/PlaytestRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/PlaytestRequestHandler.java new file mode 100644 index 000000000..469a1fddb --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/PlaytestRequestHandler.java @@ -0,0 +1,105 @@ +package com.gempukku.lotro.async.handler; + +import com.gempukku.lotro.DateUtils; +import com.gempukku.lotro.async.HttpProcessingException; +import com.gempukku.lotro.async.ResponseWriter; +import com.gempukku.lotro.cache.CacheManager; +import com.gempukku.lotro.collection.CollectionsManager; +import com.gempukku.lotro.common.ApplicationConfiguration; +import com.gempukku.lotro.db.LeagueDAO; +import com.gempukku.lotro.db.PlayerDAO; +import com.gempukku.lotro.db.vo.CollectionType; +import com.gempukku.lotro.draft2.SoloDraftDefinitions; +import com.gempukku.lotro.game.CardCollection; +import com.gempukku.lotro.game.CardSets; +import com.gempukku.lotro.game.LotroCardBlueprintLibrary; +import com.gempukku.lotro.game.Player; +import com.gempukku.lotro.game.formats.LotroFormatLibrary; +import com.gempukku.lotro.hall.HallServer; +import com.gempukku.lotro.league.*; +import com.gempukku.lotro.service.AdminService; +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.QueryStringDecoder; +import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.lang.reflect.Type; +import java.text.SimpleDateFormat; +import java.util.*; + +public class PlaytestRequestHandler extends LotroServerRequestHandler implements UriRequestHandler { + + private PlayerDAO _playerDAO; + + public PlaytestRequestHandler(Map context) { + super(context); + _playerDAO = extractObject(context, PlayerDAO.class); + + } + + @Override + public void handleRequest(String uri, HttpRequest request, Map context, ResponseWriter responseWriter, String remoteIp) throws Exception { + if (uri.equals("/addTesterFlag") && request.getMethod() == HttpMethod.POST) { + addTesterFlag(request, responseWriter); + } else if (uri.equals("/removeTesterFlag") && request.getMethod() == HttpMethod.POST) { + removeTesterFlag(request, responseWriter); + } else if (uri.equals("/getTesterFlag") && request.getMethod() == HttpMethod.GET) { + getTesterFlag(request, responseWriter); + } else { + throw new HttpProcessingException(404); + } + } + + private void addTesterFlag(HttpRequest request, ResponseWriter responseWriter) throws Exception { + HttpPostRequestDecoder postDecoder = new HttpPostRequestDecoder(request); + try { + Player player = getResourceOwnerSafely(request, null); + + _playerDAO.addPlayerFlag(player.getName(), "t"); + + responseWriter.writeHtmlResponse("OK"); + + } finally { + postDecoder.destroy(); + } + } + + private void removeTesterFlag(HttpRequest request, ResponseWriter responseWriter) throws Exception { + HttpPostRequestDecoder postDecoder = new HttpPostRequestDecoder(request); + try { + Player player = getResourceOwnerSafely(request, null); + + _playerDAO.removePlayerFlag(player.getName(), "t"); + + responseWriter.writeHtmlResponse("OK"); + + } finally { + postDecoder.destroy(); + } + } + + private void getTesterFlag(HttpRequest request, ResponseWriter responseWriter) throws Exception { + HttpPostRequestDecoder postDecoder = new HttpPostRequestDecoder(request); + try { + Player player = getResourceOwnerSafely(request, null); + + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); + Document doc = documentBuilder.newDocument(); + Element hasTester = doc.createElement("hasTester"); + + hasTester.setAttribute("result", String.valueOf(player.getType().contains("t"))); + + responseWriter.writeXmlResponse(doc); + + } finally { + postDecoder.destroy(); + } + } + +} diff --git a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/RootUriRequestHandler.java b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/RootUriRequestHandler.java index 91128254a..12f63933f 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/RootUriRequestHandler.java +++ b/gemp-lotr/gemp-lotr-async/src/main/java/com/gempukku/lotro/async/handler/RootUriRequestHandler.java @@ -33,6 +33,7 @@ public class RootUriRequestHandler implements UriRequestHandler { private PlayerStatsRequestHandler _playerStatsRequestHandler; private TournamentRequestHandler _tournamentRequestHandler; private SoloDraftRequestHandler _soloDraftRequestHandler; + private PlaytestRequestHandler _playtestRequestHandler; private Pattern originPattern; @@ -58,6 +59,7 @@ public class RootUriRequestHandler implements UriRequestHandler { _playerStatsRequestHandler = new PlayerStatsRequestHandler(context); _tournamentRequestHandler = new TournamentRequestHandler(context); _soloDraftRequestHandler = new SoloDraftRequestHandler(context); + _playtestRequestHandler = new PlaytestRequestHandler(context); } @Override @@ -110,6 +112,10 @@ public class RootUriRequestHandler implements UriRequestHandler { _tournamentRequestHandler.handleRequest(uri.substring(_serverContextPath.length() + 10), request, context, responseWriter, remoteIp); } else if (uri.startsWith(_serverContextPath + "soloDraft")) { _soloDraftRequestHandler.handleRequest(uri.substring(_serverContextPath.length() + 9), request, context, responseWriter, remoteIp); + + } else if (uri.startsWith(_serverContextPath + "playtesting")) { + _playtestRequestHandler.handleRequest(uri.substring(_serverContextPath.length() + 11), request, context, responseWriter, remoteIp); + } else { throw new HttpProcessingException(404); } diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/hall.html b/gemp-lotr/gemp-lotr-async/src/main/web/hall.html index c8cf42305..153f9d0bd 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/hall.html +++ b/gemp-lotr/gemp-lotr-async/src/main/web/hall.html @@ -45,6 +45,12 @@ + + diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/includes/playtesting.html b/gemp-lotr/gemp-lotr-async/src/main/web/includes/playtesting.html new file mode 100644 index 000000000..5e0f8e7f4 --- /dev/null +++ b/gemp-lotr/gemp-lotr-async/src/main/web/includes/playtesting.html @@ -0,0 +1,16 @@ +
+

The Player's Council is dedicated to cleaning up the game's fossilized meta, and we aim to do so both through errata of broken (or underperforming) cards and the introduction of new cards compatible with existing formats.

+ +

Although we spend weeks or months arguing the finer points of card balancing, there is no argument as powerful as the result of dedicated playtesting. Once a card has been developed to the point of requiring playtesting, we release it here on Gemp and unlock it within special hidden playtesting formats.

+ +

If you would like to help us do this by playing matches with these cards, then use the button below to enable the playtesting formats on your account! If later you decide you no longer want to see how the sausage is made, then return to this screen and click the disable button instead.

+ + + + +

After playing playtest games, you can leave feedback using this form here.

+ +

If you have any questions or would like to discuss our card design decisions in-depth, then feel free to join us on the PC Discord server.

+ +
+ diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/index.html b/gemp-lotr/gemp-lotr-async/src/main/web/index.html index eceadaef5..94dd7d833 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/index.html +++ b/gemp-lotr/gemp-lotr-async/src/main/web/index.html @@ -40,7 +40,7 @@ margin-right: auto; } - .status { + .status, .error { margin: 0; margin-left: auto; margin-right: auto; diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/communication.js b/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/communication.js index fbad21339..31281bf5b 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/communication.js +++ b/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/communication.js @@ -615,6 +615,33 @@ var GempLotrCommunication = Class.extend({ dataType:"html" }); }, + + addTesterFlag:function (callback, errorMap) { + $.ajax({ + type:"POST", + url:this.url + "/playtesting/addTesterFlag", + cache:false, + data:{ + participantId:getUrlParam("participantId")}, + success:this.deliveryCheck(callback), + error:this.errorCheck(errorMap), + dataType:"html" + }); + }, + + removeTesterFlag:function (callback, errorMap) { + $.ajax({ + type:"POST", + url:this.url + "/playtesting/removeTesterFlag", + cache:false, + data:{ + participantId:getUrlParam("participantId")}, + success:this.deliveryCheck(callback), + error:this.errorCheck(errorMap), + dataType:"html" + }); + }, + getStatus:function (callback, errorMap) { $.ajax({ type:"GET", diff --git a/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/hallUi.js b/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/hallUi.js index 0e1f77943..3a7e70cd6 100644 --- a/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/hallUi.js +++ b/gemp-lotr/gemp-lotr-async/src/main/web/js/gemp-022/hallUi.js @@ -402,6 +402,22 @@ var GempLotrHallUI = Class.extend({ var myAudio = document.getElementById(soundObj); myAudio.play(); }, + + AddTesterFlag: function() { + var that = this; + + that.comm.addTesterFlag(function () { + window.location.reload(true); + }); + }, + + RemoveTesterFlag: function() { + var that = this; + + that.comm.removeTesterFlag(function () { + window.location.reload(true); + }); + }, processHall:function (xml) { var that = this; @@ -698,7 +714,10 @@ var GempLotrHallUI = Class.extend({ for (var i = 0; i < formats.length; i++) { var format = formats[i].childNodes[0].nodeValue; var type = formats[i].getAttribute("type"); - this.supportedFormatsSelect.append(""); + + var item = "" + + this.supportedFormatsSelect.append(item); } this.supportedFormatsInitialized = true; } diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/CachedPlayerDAO.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/CachedPlayerDAO.java index c052257b1..43705a202 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/CachedPlayerDAO.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/CachedPlayerDAO.java @@ -53,6 +53,22 @@ public class CachedPlayerDAO implements PlayerDAO, Cached { return success; } + @Override + public boolean addPlayerFlag(String login, String flag) throws SQLException { + final boolean success = _delegate.addPlayerFlag(login, flag); + if (success) + clearCache(); + return success; + } + + @Override + public boolean removePlayerFlag(String login, String flag) throws SQLException { + final boolean success = _delegate.removePlayerFlag(login, flag); + if (success) + clearCache(); + return success; + } + @Override public List findSimilarAccounts(String login) throws SQLException { return _delegate.findSimilarAccounts(login); diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/DbPlayerDAO.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/DbPlayerDAO.java index d587dfb37..8bfa841ae 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/DbPlayerDAO.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/DbPlayerDAO.java @@ -106,6 +106,30 @@ public class DbPlayerDAO implements PlayerDAO { } } + @Override + public boolean addPlayerFlag(String login, String flag) throws SQLException { + try (Connection conn = _dbAccess.getDataSource().getConnection()) { + try (PreparedStatement statement = conn.prepareStatement("UPDATE player SET type = CONCAT(type, ?) WHERE name=? AND type NOT LIKE CONCAT('%', ?, '%');")) { + statement.setString(1, flag); + statement.setString(2, login); + statement.setString(3, flag); + return statement.executeUpdate() == 1; + } + } + } + + @Override + public boolean removePlayerFlag(String login, String flag) throws SQLException { + try (Connection conn = _dbAccess.getDataSource().getConnection()) { + try (PreparedStatement statement = conn.prepareStatement("UPDATE player SET type = REPLACE(type, ?, '') WHERE name=? AND type LIKE CONCAT('%', ?, '%');")) { + statement.setString(1, flag); + statement.setString(2, login); + statement.setString(3, flag); + return statement.executeUpdate() == 1; + } + } + } + @Override public Player loginUser(String login, String password) throws SQLException { try (Connection conn = _dbAccess.getDataSource().getConnection()) { diff --git a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/PlayerDAO.java b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/PlayerDAO.java index 53e41e586..d4185a4fb 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/PlayerDAO.java +++ b/gemp-lotr/gemp-lotr-server/src/main/java/com/gempukku/lotro/db/PlayerDAO.java @@ -16,6 +16,9 @@ public interface PlayerDAO { public boolean unBanPlayer(String login) throws SQLException; + public boolean addPlayerFlag(String login, String flag) throws SQLException; + public boolean removePlayerFlag(String login, String flag) throws SQLException; + public List findSimilarAccounts(String login) throws SQLException; public Player loginUser(String login, String password) throws SQLException; diff --git a/gemp-lotr/gemp-lotr-server/src/main/resources/lotrFormats.json b/gemp-lotr/gemp-lotr-server/src/main/resources/lotrFormats.json index 0c2a4c2f4..da5ea02b7 100644 --- a/gemp-lotr/gemp-lotr-server/src/main/resources/lotrFormats.json +++ b/gemp-lotr/gemp-lotr-server/src/main/resources/lotrFormats.json @@ -429,27 +429,30 @@ }, { - "name":"PLAYTEST - PC-FOTR", + "name":"PLAYTEST - PC Fellowship Block", "code":"test_pc_fotr_block", "sites":"FELLOWSHIP", "cancelRingBearerSkirmish":true, "hall":true, + "playtest":true, "set":[1, 2, 3, 51, 52, 53, 73, 74, 75, 101, 151], "banned":["1_40","1_311","2_32","3_42","3_68",] }, { - "name":"PLAYTEST - PC-Movie", + "name":"PLAYTEST - PC Movie Block", "code":"test_pc_movie", "sites":"KING", "hall":true, + "playtest":true, "set":[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 51, 52, 53, 73, 74, 75, 101, 151], "banned":["1_40","1_311","2_32","3_42","3_68", "1_45", "1_80", "1_108", "1_139", "1_195", "1_234", "1_248", "1_313", "3_106", "3_108", "3_38", "3_67", "7_49", "7_96", "8_1", "10_2", "10_91"], }, { - "name":"PLAYTEST - PC-Expanded", + "name":"PLAYTEST - PC Expanded", "code":"test_pc_expanded", "sites":"SHADOWS", "hall":true, + "playtest":true, "banned":["1_40","1_311","2_32","3_42","3_68","1_45", "1_138", "1_234", "1_313", "1_316", "2_121", "3_17", "3_38", "3_67", "3_108", "3_113", "4_73", "7_49", "8_1", "10_2", "10_11", "10_91", "11_31", "11_100", "11_132"], "restricted":["1_80", "1_108", "1_139", "1_195", "1_248", "2_75", "3_106", "4_276", "4_304"], "set":[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 51, 52, 53, 73, 74, 75, 101, 151]