Added - queue configurator supports constructed games

This commit is contained in:
jakub.salavec
2025-06-16 17:38:29 +02:00
parent 24bb24ad75
commit 1c24354e52
11 changed files with 100 additions and 55 deletions

View File

@@ -13,6 +13,7 @@ import com.gempukku.lotro.game.LotroCardBlueprintLibrary;
import com.gempukku.lotro.game.Player;
import com.gempukku.lotro.game.SortAndFilterCards;
import com.gempukku.lotro.game.formats.LotroFormatLibrary;
import com.gempukku.lotro.hall.HallException;
import com.gempukku.lotro.hall.HallServer;
import com.gempukku.lotro.logic.vo.LotroDeck;
import com.gempukku.lotro.packs.ProductLibrary;
@@ -84,8 +85,8 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
getTournamentReport(request, uri.substring(1, uri.indexOf("/report/")), responseWriter);
} else if (uri.equals("/create") && request.method() == HttpMethod.POST) {
processPlayerMadeTournament(request, responseWriter);
} else if (uri.equals("/limitedFormats") && request.method() == HttpMethod.GET) {
getLimitedFormats(request, responseWriter);
} else if (uri.equals("/tournamentFormats") && request.method() == HttpMethod.GET) {
getTournamentFormats(request, responseWriter);
} else if (uri.startsWith("/") && request.method() == HttpMethod.GET) {
getTournamentInfo(request, uri.substring(1), responseWriter);
} else {
@@ -93,8 +94,9 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
}
}
private void getLimitedFormats(HttpRequest request, ResponseWriter responseWriter) {
private void getTournamentFormats(HttpRequest request, ResponseWriter responseWriter) {
JSONDefs.PlayerMadeTournamentAvailableFormats data = new JSONDefs.PlayerMadeTournamentAvailableFormats();
Map<String, String> availableSealedFormats = new HashMap<>();
availableSealedFormats.put("single_fotr_block_sealed", "Fellowship Block Sealed");
availableSealedFormats.put("single_ttt_block_sealed", "Towers Block Sealed");
@@ -103,11 +105,16 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
availableSealedFormats.put("single_movie_sealed", "Movie Sealed");
availableSealedFormats.put("single_wotr_block_sealed", "War of the Ring Block Sealed");
availableSealedFormats.put("single_th_block_sealed", "Hunters Block Sealed");
Map<String, String> availableSoloDraftFormats = new HashMap<>();
availableSoloDraftFormats.put("fotr_draft", "Fellowship Block");
availableSoloDraftFormats.put("ttt_draft", "Towers Block");
availableSoloDraftFormats.put("hobbit_random_draft", "Hobbit");
List<String> orderedSoloDrafts = List.of("fotr_draft", "ttt_draft", "hobbit_random_draft");
data.constructed = _formatLibrary.getHallFormats().values().stream()
.map(constructedFormat -> new JSONDefs.ItemStub(constructedFormat.getCode(), constructedFormat.getName()))
.collect(Collectors.toList());
data.sealed = _formatLibrary.GetAllSealedTemplates().values().stream()
.filter(sealedEventDefinition -> availableSealedFormats.containsKey(sealedEventDefinition.GetID()))
.map(sealed -> new JSONDefs.ItemStub(sealed.GetID(), availableSealedFormats.get(sealed.GetID())))
@@ -128,6 +135,7 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
String participantId = getFormParameterSafely(postDecoder, "participantId");
Player resourceOwner = getResourceOwnerSafely(request, participantId);
String deckName = getFormParameterSafely(postDecoder, "deckName");
String typeStr = getFormParameterSafely(postDecoder, "type");
String deckbuildingDurationStr = getFormParameterSafely(postDecoder, "deckbuildingDuration");
@@ -144,6 +152,8 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
String readyCheckStr = getFormParameterSafely(postDecoder, "readyCheck");
int readyCheck = Throw400IfNullOrNonInteger("readyCheck", readyCheckStr);
String constructedFormatCodeStr = getFormParameterSafely(postDecoder, "constructedFormatCode");
String sealedFormatCodeStr = getFormParameterSafely(postDecoder, "sealedFormatCode");
String soloDraftFormatCodeStr = getFormParameterSafely(postDecoder, "soloDraftFormatCode");
@@ -168,7 +178,14 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
String competitivePrefix = "Competitive ";
String prefix = competitive ? competitivePrefix : casualPrefix;
if(type == Tournament.TournamentType.SEALED) {
if (type == Tournament.TournamentType.CONSTRUCTED) {
params.type = Tournament.TournamentType.CONSTRUCTED;
var format = _formatLibrary.getFormat(constructedFormatCodeStr);
Throw400IfValidationFails("constructedFormatCodeStr", constructedFormatCodeStr,format != null);
params.format = constructedFormatCodeStr;
params.name = prefix + format.getName();
params.requiresDeck = true;
} else if(type == Tournament.TournamentType.SEALED) {
var sealedParams = new SealedTournamentParams();
sealedParams.type = Tournament.TournamentType.SEALED;
@@ -181,6 +198,7 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
sealedParams.sealedFormatCode = sealedFormatCodeStr;
sealedParams.format = sealedFormat.GetFormat().getCode();
sealedParams.name = prefix + sealedFormat.GetName().substring(3); // Strip the ordering number for sealed formats
sealedParams.requiresDeck = false;
params = sealedParams;
}
else if (type == Tournament.TournamentType.SOLODRAFT) {
@@ -201,6 +219,7 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
case "hobbit_random_draft" -> soloDraftParams.name = prefix + "Hobbit Solo Draft";
default -> soloDraftParams.name = prefix + soloDraftFormatCodeStr;
}
soloDraftParams.requiresDeck = false;
params = soloDraftParams;
}
else if (type == Tournament.TournamentType.TABLE_SOLODRAFT) {
@@ -216,6 +235,7 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
soloTableDraftParams.soloTableDraftFormatCode = soloTableDraftFormatCodeStr;
soloTableDraftParams.format = tableDraftDefinition.getFormat();
soloTableDraftParams.name = prefix + tableDraftDefinition.getName();
soloTableDraftParams.requiresDeck = false;
params = soloTableDraftParams;
}
else if (type == Tournament.TournamentType.TABLE_DRAFT) {
@@ -234,24 +254,26 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
tableDraftParams.format = tableDraftDefinition.getFormat();
tableDraftParams.draftTimerType = DraftTimer.getTypeFromString(tableDraftTimer);
tableDraftParams.name = prefix + tableDraftDefinition.getName();
tableDraftParams.requiresDeck = false;
params = tableDraftParams;
}
else {
Throw400IfValidationFails("type", typeStr, false, "Only limited games");
Throw400IfValidationFails("type", typeStr, false, "Unknown game type");
return;
}
params.requiresDeck = false;
params.tournamentId = params.format + System.currentTimeMillis();
params.cost = 0; // Gold is not being used, they can be free to enter
params.playoff = Tournament.PairingType.parse(playoff);
params.tiebreaker = "owr";
params.prizes = Tournament.PrizeType.LIMITED; // At 4+ players, get one Event Award for each win or bye
params.prizes = Tournament.PrizeType.WIN_GAME_FOR_AWARD; // At 4+ players, get one Event Award for each win or bye
params.maximumPlayers = maxPlayers;
params.manualKickoff = false;
TournamentInfo info;
if(type == Tournament.TournamentType.SEALED) {
if (type == Tournament.TournamentType.CONSTRUCTED) {
info = new TournamentInfo(_tournamentService, _productLibrary, _formatLibrary, DateUtils.Today(), params);
} else if (type == Tournament.TournamentType.SEALED) {
info = new SealedTournamentInfo(_tournamentService, _productLibrary, _formatLibrary, DateUtils.Today(), (SealedTournamentParams)params);
}
else if (type == Tournament.TournamentType.SOLODRAFT) {
@@ -264,15 +286,18 @@ public class TournamentRequestHandler extends LotroServerRequestHandler implemen
info = new TableDraftTournamentInfo(_tournamentService, _productLibrary, _formatLibrary, DateUtils.Today(), ((TableDraftTournamentParams) params), _tableDraftLibrary);
}
else {
Throw400IfValidationFails("type", typeStr, false, "Only limited games");
Throw400IfValidationFails("type", typeStr, false, "Unknown game type");
return;
}
if (_hallServer.addPlayerMadeLimitedQueue(info, resourceOwner, startable, readyCheck)) {
try {
if (_hallServer.addPlayerMadeQueue(info, resourceOwner, deckName, startable, readyCheck)) {
responseWriter.sendJsonOK();
} else {
Throw400IfValidationFails("Error", "Error", false, "Error while creating queue or joining");
}
} catch (HallException badDeck) {
Throw400IfValidationFails("deckName", deckName, false, "Select valid deck for the requested format");
}
}
private void getTournamentInfo(HttpRequest request, String tournamentId, ResponseWriter responseWriter) throws Exception {

View File

@@ -850,7 +850,7 @@ table.standings {
margin-bottom: 5px;
}
#limitedGameForm {
#queueSpawnerForm {
display: grid;
margin-left: 20px;
padding-top: 8px;
@@ -859,12 +859,12 @@ table.standings {
max-width: 400px;
}
form#limitedGameForm select {
form#queueSpawnerForm select {
width: 100%;
box-sizing: border-box;
}
form#limitedGameForm input {
form#queueSpawnerForm input {
width: 100%;
box-sizing: border-box;
}

View File

@@ -226,14 +226,15 @@
</table>
</div>
<div id="limitedGamesHeader" class='eventHeader limitedGames'>Limited Games</div>
<div id="limitedGamesContent" class='visibilityToggle'>
<form id="limitedGameForm">
<div id="queueSpawnerHeader" class='eventHeader limitedGames'>Tournaments & Limited Games</div>
<div id="queueSpawnerContent" class='visibilityToggle'>
<form id="queueSpawnerForm">
<!-- Game Type Selection -->
<div class="formRow">
<label for="gameTypeSelect">Type of Game:</label>
<select id="gameTypeSelect" name="gameType">
<option value="" disabled selected>Select game type</option>
<option value="constructed">Constructed</option>
<option value="sealed">Sealed</option>
<option value="table_draft">Live Draft</option>
<option value="solodraft">Solo Draft</option>

View File

@@ -1434,17 +1434,17 @@ var GempLotrCommunication = Class.extend({
dataType:"xml"
});
},
getLimitedTournamentAvailableFormats:function (callback, errorMap) {
getTournamentAvailableFormats:function (callback, errorMap) {
$.ajax({
type:"GET",
url:this.url + "/tournament/limitedFormats",
url:this.url + "/tournament/tournamentFormats",
cache:false,
success:this.deliveryCheck(callback),
error:this.errorCheck(errorMap),
dataType:"json"
});
},
createTournament:function (type, maxPlayers, sealedFormatCode, soloDraftFormatCode, tableDraftFormatCode, tableDraftTimer,
createTournament:function (type, deckName, maxPlayers, constructedFormatCode, sealedFormatCode, soloDraftFormatCode, tableDraftFormatCode, tableDraftTimer,
playoff, deckbuildingDuration, competitive, startable, readyCheck, callback, errorMap) {
$.ajax({
type:"POST",
@@ -1452,7 +1452,9 @@ var GempLotrCommunication = Class.extend({
cache:false,
data:{
type:type,
deckName:deckName,
maxPlayers:maxPlayers,
constructedFormatCode:constructedFormatCode,
sealedFormatCode:sealedFormatCode,
soloDraftFormatCode:soloDraftFormatCode,
tableDraftFormatCode:tableDraftFormatCode,

View File

@@ -51,7 +51,7 @@ var GempLotrHallUI = Class.extend({
that.userInfo = json;
});
this.comm.getLimitedTournamentAvailableFormats(function(json)
this.comm.getTournamentAvailableFormats(function(json)
{
that.setupTournamentSpawner(json);
});
@@ -127,7 +127,7 @@ var GempLotrHallUI = Class.extend({
this.initTable(hallSettings[3] == "1", "wcQueuesHeader", "wcQueuesContent");
this.initTable(hallSettings[4] == "1", "wcEventsHeader", "wcEventsContent");
this.initTable(hallSettings[5] == "1", "tournamentQueuesHeader", "tournamentQueuesContent");
this.initTable(hallSettings[6] == "1", "limitedGamesHeader", "limitedGamesContent");
this.initTable(hallSettings[6] == "1", "queueSpawnerHeader", "queueSpawnerContent");
$('#wcQueuesHeader').hide();
$('#wcQueuesContent').hide();
@@ -194,7 +194,7 @@ var GempLotrHallUI = Class.extend({
}
const validPlayerCount = Number.isInteger(playerCount) && playerCount >= 1 && validMaxPlayers;
const validDeckDuration = Number.isInteger(deckDuration) && deckDuration >= 5;
const validDeckDuration = (Number.isInteger(deckDuration) && deckDuration >= 5) || gameType === "constructed";
const draftValid = gameType !== "table_draft" || draftTimer;
const validPlayerReadyCombo = playerCount > 2 || readyCheck < 0;
@@ -229,7 +229,7 @@ var GempLotrHallUI = Class.extend({
$advanced.toggle(advancedVisible);
if (advancedVisible) {
// Scroll to bottom so that the new field can be seen
$("#limitedGameForm").get(0).scrollIntoView({ behavior: "smooth", block: "end" });
$("#queueSpawnerForm").get(0).scrollIntoView({ behavior: "smooth", block: "end" });
}
$(this).text(advancedVisible ? "Hide advanced settings" : "Show advanced settings");
});
@@ -237,6 +237,7 @@ var GempLotrHallUI = Class.extend({
const gameType = $(this).val();
const formatListMap = {
constructed: json.constructed,
sealed: json.sealed,
solodraft: json.soloDrafts,
table_draft: json.tableDrafts
@@ -328,6 +329,9 @@ var GempLotrHallUI = Class.extend({
.append($("<option>").val("false").text("Games can be spectated"))
.append($("<option>").val("true").text("No spectate; hidden names in queue"));
$competitiveRow.append($competitiveLabel, $competitiveSelect);
if (gameType === "constructed") {
$competitiveSelect.val("true");
}
// Early start
const $earlyStartRow = $("<div>").addClass("formRow");
@@ -337,6 +341,10 @@ var GempLotrHallUI = Class.extend({
.append($("<option>").val("true").text("Can be started with less players"))
.append($("<option>").val("false").text("Starts only with all player slots filled"));
$earlyStartRow.append($earlyStartLabel, $earlyStartSelect);
// Set default to false for constructed games
if (gameType === "constructed") {
$earlyStartSelect.val("false");
}
// Ready check
const $readyCheckRow = $("<div>").addClass("formRow");
@@ -357,10 +365,11 @@ var GempLotrHallUI = Class.extend({
$("<div>").append($playersRow)
);
// Append to form
$advanced.append($("<div>").append($pairingRow));
if (gameType !== "constructed") {
$advanced.append($("<div>").append($durationRow));
}
$advanced.append(
$("<div>").append($pairingRow),
$("<div>").append($durationRow),
$("<div>").append($competitiveRow),
$("<div>").append($earlyStartRow),
$("<div>").append($readyCheckRow)
@@ -436,23 +445,28 @@ var GempLotrHallUI = Class.extend({
validateTournamentForm();
// Scroll to bottom so that the new field can be seen
$("#limitedGameForm").get(0).scrollIntoView({ behavior: "smooth", block: "end" });
$("#queueSpawnerForm").get(0).scrollIntoView({ behavior: "smooth", block: "end" });
});
},
createTournament: function() {
var that = this;
const deck = this.decksSelect.val();
const type = $("#gameTypeSelect").val();
// Depending on the type, pick the correct format code from the formatSelect dropdown
const formatCode = $("#formatSelect").val();
// Assign the format codes based on the selected game type
let constructedFormatCode = null;
let sealedFormatCode = null;
let soloDraftFormatCode = null;
let tableDraftFormatCode = null;
if (type === "sealed") {
if (type === "constructed") {
constructedFormatCode = formatCode;
} else if (type === "sealed") {
sealedFormatCode = formatCode;
} else if (type === "solodraft") {
soloDraftFormatCode = formatCode;
@@ -476,7 +490,9 @@ var GempLotrHallUI = Class.extend({
// Call the communication layer with all gathered values
this.comm.createTournament(
type,
deck,
maxPlayers,
constructedFormatCode,
sealedFormatCode,
soloDraftFormatCode,
tableDraftFormatCode,
@@ -565,7 +581,7 @@ var GempLotrHallUI = Class.extend({
that.showErrorDialog("Inactivity error", "You were inactive for too long and have been removed from the Game Hall. If you wish to re-enter, click \"Refresh page\".", true, false);
},
"400":function() {
that.showErrorDialog("Tournament creation error", "Something went wrong, check all tournament parameters.", false, false);
that.showErrorDialog("Tournament creation error", "Something went wrong, check all tournament parameters. If creating a constructed tournament, make sure that your selected deck is valid.", false, false);
}
};
},

View File

@@ -102,6 +102,7 @@ public class JSONDefs {
}
public static class PlayerMadeTournamentAvailableFormats {
public List<ItemStub> constructed;
public List<ItemStub> sealed;
public List<ItemStub> soloDrafts;
public List<LiveDraftInfo> tableDrafts;

View File

@@ -474,10 +474,15 @@ public class HallServer extends AbstractServer {
}
public boolean addPlayerMadeLimitedQueue(TournamentInfo info, Player player, boolean startableEarly, int readyCheckTimeSecs) throws SQLException, IOException {
public boolean addPlayerMadeQueue(TournamentInfo info, Player player, String deckName, boolean startableEarly, int readyCheckTimeSecs) throws SQLException, IOException, HallException {
_hallDataAccessLock.writeLock().lock();
try {
boolean success = _tournamentService.addPlayerMadeLimitedQueue(info, player, startableEarly, readyCheckTimeSecs);
LotroDeck lotroDeck = null;
if (info.Parameters().requiresDeck) {
lotroDeck = validateUserAndDeck(info.Format, player, deckName, info.Collection);
}
boolean success = _tournamentService.addPlayerMadeQueue(info, player, lotroDeck, startableEarly, readyCheckTimeSecs);
if (success) {
hallChanged();
}

View File

@@ -5,8 +5,8 @@ import com.gempukku.lotro.collection.CollectionsManager;
import java.io.IOException;
import java.sql.SQLException;
public class PlayerMadeLimitedQueue extends AbstractTournamentQueue implements TournamentQueue {
public PlayerMadeLimitedQueue(TournamentService tournamentService, String queueId, String queueName, TournamentInfo info, boolean startableEarly, int readyCheckTimeSecs,
public class PlayerMadeQueue extends AbstractTournamentQueue implements TournamentQueue {
public PlayerMadeQueue(TournamentService tournamentService, String queueId, String queueName, TournamentInfo info, boolean startableEarly, int readyCheckTimeSecs,
TournamentQueueCallback tournamentQueueCallback, CollectionsManager collectionsManager) {
super(tournamentService, queueId, queueName, info, startableEarly, readyCheckTimeSecs, tournamentQueueCallback, collectionsManager, true);
}

View File

@@ -88,7 +88,7 @@ public interface Tournament {
NONE,
DAILY,
ON_DEMAND,
LIMITED;
WIN_GAME_FOR_AWARD;
public static PrizeType parse(String name) {
String nameCaps = name.toUpperCase().trim().replace(' ', '_').replace('-', '_');
@@ -111,8 +111,8 @@ public interface Tournament {
//Currently busted, reverting to Daily for now
return new DailyTournamentPrizes(prize.name(), productLibrary);
}
case LIMITED -> {
return new LimitedTournamentPrizes(prize.name(), productLibrary);
case WIN_GAME_FOR_AWARD -> {
return new WinForAwardTournamentPrizes(prize.name(), productLibrary);
}
}

View File

@@ -7,7 +7,6 @@ import com.gempukku.lotro.common.DBDefs;
import com.gempukku.lotro.db.GameHistoryDAO;
import com.gempukku.lotro.db.vo.CollectionType;
import com.gempukku.lotro.draft2.SoloDraftDefinitions;
import com.gempukku.lotro.draft3.TableDraftDefinition;
import com.gempukku.lotro.draft3.TableDraftDefinitions;
import com.gempukku.lotro.game.LotroCardBlueprintLibrary;
import com.gempukku.lotro.game.Player;
@@ -100,14 +99,6 @@ public class TournamentService {
public void reloadQueues() {
_tournamentQueues.clear();
addImmediateRecurringQueue("fotr_queue", "Fellowship Block", "fotr-", "fotr_block");
addImmediateRecurringQueue("pc_fotr_queue", "PC-Fellowship", "pcfotr-", "pc_fotr_block");
addImmediateRecurringQueue("ts_queue", "Towers Standard", "ts-", "towers_standard");
addImmediateRecurringQueue("movie_queue", "Movie Block", "movie-", "movie");
addImmediateRecurringQueue("pc_movie_queue", "PC-Movie", "pcmovie-", "pc_movie");
addImmediateRecurringQueue("expanded_queue", "Expanded", "expanded-", "expanded");
addImmediateRecurringQueue("pc_expanded_queue", "PC-Expanded", "pcexpanded-", "pc_expanded");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
@@ -296,16 +287,20 @@ public class TournamentService {
return true;
}
public boolean addPlayerMadeLimitedQueue(TournamentInfo info, Player player, boolean startableEarly, int readyCheckTimeSecs) throws SQLException, IOException {
public boolean addPlayerMadeQueue(TournamentInfo info, Player player, LotroDeck lotroDeck, boolean startableEarly, int readyCheckTimeSecs) throws SQLException, IOException {
if (_tournamentQueues.containsKey(info._params.tournamentId))
return false;
TournamentQueue tournamentQueue = new PlayerMadeLimitedQueue(this, info.Parameters().tournamentId,
TournamentQueue tournamentQueue = new PlayerMadeQueue(this, info.Parameters().tournamentId,
info.Parameters().name, info, startableEarly, readyCheckTimeSecs,
tournament -> _activeTournaments.put(tournament.getTournamentId(), tournament), _collectionsManager);
_tournamentQueues.put(info._params.tournamentId, tournamentQueue);
if (info._params.requiresDeck) {
tournamentQueue.joinPlayer(player, lotroDeck);
} else {
tournamentQueue.joinPlayer(player);
}
return true;
}

View File

@@ -5,11 +5,11 @@ import com.gempukku.lotro.game.CardCollection;
import com.gempukku.lotro.game.DefaultCardCollection;
import com.gempukku.lotro.packs.ProductLibrary;
public class LimitedTournamentPrizes implements TournamentPrizes {
public class WinForAwardTournamentPrizes implements TournamentPrizes {
private final String _registryRepresentation;
private final ProductLibrary _productLibrary;
public LimitedTournamentPrizes(String registryRepresentation, ProductLibrary productLibrary) {
public WinForAwardTournamentPrizes(String registryRepresentation, ProductLibrary productLibrary) {
_registryRepresentation = registryRepresentation;
_productLibrary = productLibrary;
}