Adding ability for admins to manually create tournament tables

This commit is contained in:
Christian 'ketura' McCarty
2023-10-21 05:19:55 -05:00
parent a63072fd5d
commit 26231745cc
9 changed files with 255 additions and 51 deletions

View File

@@ -103,7 +103,12 @@ public class GempukkuHttpRequestHandler extends SimpleChannelInboundHandler<Full
_log.error("HTTP code " + code + " response for " + requestInformation.remoteIp + ": " + requestInformation.uri, exp);
}
responseSender.writeError(exp.getStatus());
if(exp.getMessage() != null) {
responseSender.writeError(exp.getStatus(), Collections.singletonMap("message", exp.getMessage()));
}
else {
responseSender.writeError(exp.getStatus());
}
} catch (Exception exp) {
_log.error("Error response for " + uri, exp);
responseSender.writeError(500);

View File

@@ -2,12 +2,22 @@ package com.gempukku.lotro.async;
public class HttpProcessingException extends Exception {
private final int _status;
private final String _message;
public HttpProcessingException(int status) {
this(status, "");
}
public HttpProcessingException(int status, String message) {
_status = status;
_message = message;
}
public int getStatus() {
return _status;
}
public String getMessage() {
return _message;
}
}

View File

@@ -14,8 +14,10 @@ import com.gempukku.lotro.game.CardCollection;
import com.gempukku.lotro.game.LotroCardBlueprintLibrary;
import com.gempukku.lotro.game.Player;
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.vo.LotroDeck;
import com.gempukku.lotro.packs.ProductLibrary;
import com.gempukku.lotro.service.AdminService;
import com.gempukku.lotro.tournament.TournamentService;
@@ -32,10 +34,8 @@ import java.io.IOException;
import java.lang.reflect.Type;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Stream;
public class AdminRequestHandler extends LotroServerRequestHandler implements UriRequestHandler {
private final LotroCardBlueprintLibrary _cardLibrary;
@@ -91,6 +91,8 @@ public class AdminRequestHandler extends LotroServerRequestHandler implements Ur
previewConstructedLeague(request, responseWriter);
} else if (uri.equals("/addConstructedLeague") && request.method() == HttpMethod.POST) {
addConstructedLeague(request, responseWriter);
} else if (uri.equals("/addTables") && request.method() == HttpMethod.POST) {
addTables(request, responseWriter);
} else if (uri.equals("/previewSoloDraftLeague") && request.method() == HttpMethod.POST) {
previewSoloDraftLeague(request, responseWriter);
} else if (uri.equals("/addSoloDraftLeague") && request.method() == HttpMethod.POST) {
@@ -334,6 +336,65 @@ public class AdminRequestHandler extends LotroServerRequestHandler implements Ur
return _leagueService.getCollectionTypeByCode(collectionType);
}
private void addTables(HttpRequest request, ResponseWriter responseWriter) throws Exception {
validateLeagueAdmin(request);
HttpPostRequestDecoder postDecoder = new HttpPostRequestDecoder(request);
try {
String name = getFormParameterSafely(postDecoder, "name");
String tournamentID = getFormParameterSafely(postDecoder, "tournament");
String formatCode = getFormParameterSafely(postDecoder, "format");
String timerCode = getFormParameterSafely(postDecoder, "timer");
List<String> playerones = getFormMultipleParametersSafely(postDecoder, "playerones[]");
List<String> playertwos = getFormMultipleParametersSafely(postDecoder, "playertwos[]");
var tournament = _tournamentService.getTournamentById(tournamentID);
if(tournament == null) {
throw new HttpProcessingException(400, "Tournament '" + tournamentID + "' not found.");
}
var formats = _formatLibrary.getAllFormats();
if(!formats.containsKey(formatCode)) {
throw new HttpProcessingException(400, "Format code '" + formatCode + "' not found.");
}
var format = formats.get(formatCode);
var timer = GameTimer.ResolveTimer(timerCode);
var submittedPlayers = Stream.concat(playerones.stream(), playertwos.stream()).toList();
var decks = new HashMap<String, LotroDeck>();
for(String playerName : submittedPlayers) {
var player = _playerDAO.getPlayer(playerName);
if(player == null)
throw new HttpProcessingException(400, "Player '" + playerName + "' does not exist.");
var deck = _tournamentService.getPlayerDeck(tournament.getTournamentId(), player.getName(), format.getName());
if(deck == null)
throw new HttpProcessingException(400, "Player '" + playerName + "' has no deck registered for '" + tournamentID + "'.");
if(decks.containsKey(player.getName())) {
throw new HttpProcessingException(400, "Player '" + playerName + "' was listed twice.");
}
decks.put(player.getName(), deck);
}
var spawner = _hallServer.GetManualGameSpawner(tournament, format, timer, name);
for(int i = 0; i < playerones.size(); i++) {
String p1 = playerones.get(i);
String p2 = playertwos.get(i);
spawner.createGame(p1, decks.get(p1), p2, decks.get(p2));
}
responseWriter.writeHtmlResponse("OK");
} finally {
postDecoder.destroy();
}
}
private void addConstructedLeague(HttpRequest request, ResponseWriter responseWriter) throws Exception {
validateLeagueAdmin(request);

View File

@@ -4,6 +4,7 @@ function gatherData(formElem) {
var inputs = $("input[type='text'], option:selected", formElem).each(
function () {
var input = $(this);
var name = null;
var value = null;
@@ -221,6 +222,32 @@ $(document).ready(
$(".serieData").last().clone().appendTo(".series");
});
$("#add-additional-table-button").button().click(
function () {
$(".tabledata").last().clone().appendTo(".tablesgroup");
});
$("#add-tables-button").button().click(
function () {
let resultdiv = $("#tables-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,
function (xml) {
resultdiv.html("OK");
}, leagueErrorMap(resultdiv));
});
hall.comm.getFormats(true,
function (json)
@@ -282,6 +309,30 @@ $(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 = $("<option/>")
.attr("value", prop)
.text("" + num + " - " + formats[prop].name);
$("#table-format").append(item);
}
}
sortOptionsByName("#table-format");
$("#table-format option").each(function(index) {
//console.log(this);
let newText = $(this).text().replace(/\d+ - /, '');
//console.log(newText);
$(this).text(newText);
});
},
{
"400":function ()
@@ -306,8 +357,14 @@ function leagueErrorMap(outputControl, callback=null) {
if(callback!=null)
callback();
},
"400":function() {
outputControl.html("400: One of the provided parameters was malformed. Double-check your input and try again.");
"400":function(xhr, status, request) {
var message = xhr.getResponseHeader("message");
if(message != null) {
outputControl.html("400; malformed input: " + message);
}
else {
outputControl.html("400: One of the provided parameters was malformed. Double-check your input and try again.");
}
if(callback!=null)
callback();
},
@@ -412,46 +469,6 @@ function leagueErrorMap(outputControl, callback=null) {
<b>Series definition:</b><br/>
Format:
<select id="constructed-format" name="format">
<!-- <option value="fotr_block">Fellowship block</option>
<option value="pc_fotr_block">Fellowship block (PC)</option>
<option value="fotr1_block">Fellowship block - Set 1</option>
<option value="fotr2_block">Fellowship block - Sets 1&2</option>
<option value="fotr_poorman">Fellowship block - Poorman</option>
<option value="fotr_highlander">Fellowship block - Highlander</option>
<option value="ttt_block">Towers block</option>
<option value="ttt1_block">Towers block - Set 4</option>
<option value="ttt2_block">Towers block - Sets 4&5</option>
<option value="towers_standard">Towers standard</option>
<option value="ttt_standard">Towers standard - Sets 1-4</option>
<option value="bohd_standard">Towers standard - Sets 1-5</option>
<option value="ts_no_fotr">Towers standard - Sets 2-6</option>
<option value="ts_reflections">Towers standard - Sets 1-6,9,14&16</option>
<option value="king_block">King block</option>
<option value="king1_block">King block - Set 7</option>
<option value="king2_block">King block - Sets 7-8</option>
<option value="movie">Movie block</option>
<option value="pc_movie">Movie block (PC)</option>
<option value="movie_exp">Movie block, no GLR (10R11)</option>
<option value="movie7">Movie block - Sets 1-7</option>
<option value="movie8">Movie block - Sets 1-8</option>
<option value="movie9">Movie block - Sets 1-9</option>
<option value="movie_sans9">Movie block - Sets 1-8,10</option>
<option value="movie_highlander">Movie block - Highlander</option>
<option value="war_block">War of the Ring block</option>
<option value="war_block11">War of the Ring block - Set 11</option>
<option value="war_block12">War of the Ring block - Sets 11-12</option>
<option value="war_block14">War of the Ring block - Sets 10-14</option>
<option value="hunter_block">Hunters block</option>
<option value="war_standard">War of the Ring standard</option>
<option value="standard">Standard</option>
<option value="open">Open</option>
<option value="open_legacy">Open - Sets 1-4</option>
<option value="expanded">Expanded</option>
<option value="pc_expanded">Expanded (PC)</option>
<option value="test_pc_fotr_block">PLAYTEST FOTR (PC)</option>
<option value="test_pc_movie">PLAYTEST Movie (PC)</option>
<option value="test_pc_expanded">PLAYTEST Expanded (PC)</option>
<option value="french">French</option> -->
</select><br/>
Serie duration in days: <input type="text" name="serieDuration" value="7"><br/>
Maximum matches per serie: <input type="text" name="maxMatches" value="5"><br/>
@@ -471,5 +488,40 @@ function leagueErrorMap(outputControl, callback=null) {
Add Constructed League
</button> <span id="constructed-league-result" style="display:inline-block;">Ready.</span>
</div>
<h1>Manually Add Elimination Tables</h1>
<div>
Series Name:
<input type="text" id="table-name"><br/>
Tournament (for decks):
<input type="text" id="table-tournament"><br/>
Format:
<select id="table-format" name="format">
</select><br/>
Timer (player clock / timeout):
<select id="table-timer">
<option value="Competitive">Competitive (40/5)</option>
<option value="WC">Championship (20/10)</option>
<option value="WC_Expanded">Championship Expanded (25/10)</option>
<option value="Blitz!">Blitz (30/5)</option>
<option value="Default">Default (80/5)</option>
<option value="Slow">Slow (120/10)</option>
</select><br/>
<div class="tablesgroup">
<div class="tabledata">
<br/><b>Table definition:</b><br/>
Player 1: <input type="text" name="playerone" ><br/>
Player 2: <input type="text" name="playertwo" ><br/>
</div>
</div>
<button id="add-additional-table-button" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" role="button" aria-disabled="false" style="padding:4px;">
Add Another Table
</button>
<button id="add-tables-button" class="ui-button ui-widget ui-state-default ui-corner-all ui-button-text-only" role="button" aria-disabled="false" style="padding:4px;">
Add Tables
</button> <span id="tables-result" style="display:inline-block;">Ready.</span>
</div>
</div>

View File

@@ -1045,6 +1045,25 @@ var GempLotrCommunication = Class.extend({
});
},
addTables:function (name, tournament, format, timer, playerones, playertwos, callback, errorMap) {
$.ajax({
type:"POST",
url:this.url + "/admin/addTables",
cache:false,
data:{
name:name,
tournament:tournament,
format:format,
timer:timer,
playerones:playerones,
playertwos:playertwos
},
success:this.deliveryCheck(callback),
error:this.errorCheck(errorMap),
dataType:"html"
});
},
//NEVER EVER EVER use this for actual authentication

View File

@@ -653,7 +653,7 @@ var GempLotrHallUI = Class.extend({
//TODO: Replace this with an actual fix on the server side
if(name.includes("Casual - WC")) {
name = "<td><b>Group Stage - 2023 World Championship</b></td>"
name = "<td><b>2023 World Championship - " + userDesc + "</b></td>"
}
row.append(name);
row.append("<td>" + statusDescription + "</td>");

View File

@@ -106,7 +106,7 @@ public class LotroServer extends AbstractServer {
if(gameSettings.getLeague() != null) {
spectate = true;
}
else if(gameSettings.isCompetitive() || gameSettings.isPrivateGame() || gameSettings.isHiddenGame()) {
else if(gameSettings.isPrivateGame() || gameSettings.isHiddenGame()) {
spectate = false;
}

View File

@@ -21,6 +21,14 @@ public record GameTimer(boolean longGame, String name, int maxSecondsPerPlayer,
return GameTimer.SLOW_TIMER;
case "glacial":
return GameTimer.GLACIAL_TIMER;
case "competitive":
return GameTimer.COMPETITIVE_TIMER;
case "wc":
return GameTimer.CHAMPIONSHIP_TIMER;
case "wc_expanded":
return GameTimer.EXPANDED_CHAMPIONSHIP_TIMER;
case "tournament":
return GameTimer.TOURNAMENT_TIMER;
}
}
return GameTimer.DEFAULT_TIMER;

View File

@@ -802,10 +802,10 @@ public class HallServer extends AbstractServer {
private HallTournamentCallback(Tournament tournament) {
_tournament = tournament;
tournamentGameSettings = new GameSettings(null, _formatLibrary.getFormat(_tournament.getFormat()),
null, null, true, false, false, false, GameTimer.TOURNAMENT_TIMER, null);
null, null, true, true, false, false, GameTimer.TOURNAMENT_TIMER, null);
wcGameSettings = new GameSettings(null, _formatLibrary.getFormat(_tournament.getFormat()),
null, null, true, false, false, false, GameTimer.CHAMPIONSHIP_TIMER, null);
null, null, true, true, false, false, GameTimer.CHAMPIONSHIP_TIMER, null);
}
@Override
@@ -856,4 +856,53 @@ public class HallServer extends AbstractServer {
}
}
}
public ManualGameSpawner GetManualGameSpawner(Tournament tournament, LotroFormat format, GameTimer timer, String description) {
return new ManualGameSpawner(tournament, format, timer, description);
}
public class ManualGameSpawner implements TournamentCallback {
private final Tournament _tournament;
private final GameSettings _settings;
private ManualGameSpawner(Tournament tournament, LotroFormat format, GameTimer timer, String description) {
_tournament = tournament;
_settings = new GameSettings(null, format,
null, null, true, false, false, false, timer, description);
}
@Override
public void createGame(String playerOne, LotroDeck deckOne, String playerTwo, LotroDeck deckTwo) {
final LotroGameParticipant[] participants = new LotroGameParticipant[2];
participants[0] = new LotroGameParticipant(playerOne, deckOne);
participants[1] = new LotroGameParticipant(playerTwo, deckTwo);
createGameInternal(participants);
}
private void createGameInternal(final LotroGameParticipant[] participants) {
_hallDataAccessLock.writeLock().lock();
try {
if (!_shutdown) {
final GameTable gameTable = tableHolder.setupTournamentTable(_settings, participants);
final LotroGameMediator mediator = createGameMediator(participants,
_notifyHallListeners, _tournament.getTournamentName(), _settings);
gameTable.startGame(mediator);
}
} finally {
_hallDataAccessLock.writeLock().unlock();
}
}
@Override
public void broadcastMessage(String message) {
try {
//check-in callback
_hallChat.sendMessage("TournamentSystem", message, true);
} catch (PrivateInformationException exp) {
// Ignore, sent as admin
} catch (ChatCommandErrorException e) {
// Ignore, no command
}
}
}
}