mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 16:40:16 +00:00
Add player limit feature for private lobbies
This commit is contained in:
@@ -127,7 +127,8 @@
|
||||
"checking": "private_lobby.checking",
|
||||
"not_found": "private_lobby.not_found",
|
||||
"error": "private_lobby.error",
|
||||
"joined_waiting": "private_lobby.joined_waiting"
|
||||
"joined_waiting": "private_lobby.joined_waiting",
|
||||
"lobby_full": "private_lobby.lobby_full"
|
||||
},
|
||||
"public_lobby": {
|
||||
"join": "public_lobby.join",
|
||||
@@ -154,7 +155,9 @@
|
||||
"player": "host_modal.player",
|
||||
"players": "host_modal.players",
|
||||
"waiting": "host_modal.waiting",
|
||||
"start": "host_modal.start"
|
||||
"start": "host_modal.start",
|
||||
"player_limit": "host_modal.player_limit",
|
||||
"player_limit_warning": "host_modal.player_limit_warning"
|
||||
},
|
||||
"game_starting_modal": {
|
||||
"title": "game_starting_modal.title",
|
||||
|
||||
@@ -239,7 +239,8 @@
|
||||
"not_found": "Lobby not found. Please check the ID and try again.",
|
||||
"error": "An error occurred. Please try again or contact support.",
|
||||
"joined_waiting": "Joined successfully! Waiting for game to start...",
|
||||
"version_mismatch": "This game was created with a different version. Cannot join."
|
||||
"version_mismatch": "This game was created with a different version. Cannot join.",
|
||||
"lobby_full": "Lobby is full (limit: {limit})."
|
||||
},
|
||||
"public_lobby": {
|
||||
"join": "Join next Game",
|
||||
@@ -292,7 +293,9 @@
|
||||
"assigned_teams": "Assigned Teams",
|
||||
"empty_teams": "Empty Teams",
|
||||
"empty_team": "Empty",
|
||||
"remove_player": "Remove {username}"
|
||||
"remove_player": "Remove {username}",
|
||||
"player_limit": "Player limit",
|
||||
"player_limit_warning": "Limit is below current player count; new players can't join."
|
||||
},
|
||||
"team_colors": {
|
||||
"red": "Red",
|
||||
|
||||
@@ -125,6 +125,36 @@ export function joinLobby(
|
||||
}
|
||||
if (message.type === "error") {
|
||||
if (message.error === "full-lobby") {
|
||||
// Parse the message to get the player limit
|
||||
let limitInfo = "";
|
||||
try {
|
||||
if (message.message) {
|
||||
const data = JSON.parse(message.message);
|
||||
if (data.maxPlayers) {
|
||||
limitInfo = translateText("private_lobby.lobby_full", {
|
||||
limit: data.maxPlayers,
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
limitInfo = translateText("private_lobby.lobby_full", {
|
||||
limit: "?",
|
||||
});
|
||||
}
|
||||
|
||||
// Show the full lobby message
|
||||
if (limitInfo) {
|
||||
showErrorModal(
|
||||
message.error,
|
||||
limitInfo,
|
||||
lobbyConfig.gameID,
|
||||
lobbyConfig.clientID,
|
||||
true,
|
||||
false,
|
||||
"error_modal.connection_error",
|
||||
);
|
||||
}
|
||||
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("leave-lobby", {
|
||||
detail: { lobby: lobbyConfig.gameID },
|
||||
|
||||
@@ -59,6 +59,11 @@ export class HostLobbyModal extends LitElement {
|
||||
@state() private lobbyCreatorClientID: string = "";
|
||||
@state() private lobbyIdVisible: boolean = true;
|
||||
@state() private nationCount: number = 0;
|
||||
@state() private playerLimit: number | null = null; // null = unlimited
|
||||
@state() private playerLimitWarning: boolean = false;
|
||||
|
||||
private static readonly MIN_PLAYER_LIMIT = 2;
|
||||
private static readonly MAX_PLAYER_LIMIT = 1000;
|
||||
|
||||
private playersInterval: NodeJS.Timeout | null = null;
|
||||
// Add a new timer for debouncing bot changes
|
||||
@@ -525,6 +530,56 @@ export class HostLobbyModal extends LitElement {
|
||||
${translateText("host_modal.max_timer")}
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<!-- Player Limit -->
|
||||
<label
|
||||
for="player-limit"
|
||||
class="option-card ${this.playerLimit !== null ? "selected" : ""}"
|
||||
>
|
||||
<div class="checkbox-icon"></div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="player-limit"
|
||||
@change=${(e: Event) => {
|
||||
const checked = (e.target as HTMLInputElement).checked;
|
||||
if (checked) {
|
||||
this.playerLimit = 20; // Default value
|
||||
} else {
|
||||
this.playerLimit = null;
|
||||
}
|
||||
this.updatePlayerLimitWarning();
|
||||
this.putGameConfig();
|
||||
}}
|
||||
.checked=${this.playerLimit !== null}
|
||||
/>
|
||||
${
|
||||
this.playerLimit === null
|
||||
? ""
|
||||
: html`<input
|
||||
type="number"
|
||||
id="player-limit-value"
|
||||
min="${HostLobbyModal.MIN_PLAYER_LIMIT}"
|
||||
max="${HostLobbyModal.MAX_PLAYER_LIMIT}"
|
||||
.value=${String(this.playerLimit ?? "")}
|
||||
style="width: 60px; color: black; text-align: right; border-radius: 8px;"
|
||||
@input=${this.handlePlayerLimitChange}
|
||||
@keydown=${this.handlePlayerLimitKeyDown}
|
||||
/>`
|
||||
}
|
||||
<div class="option-card-title">
|
||||
${translateText("host_modal.player_limit")}
|
||||
</div>
|
||||
</label>
|
||||
${
|
||||
this.playerLimitWarning
|
||||
? html`<div
|
||||
class="player-limit-warning"
|
||||
style="color: #ffa500; font-size: 12px; text-align: center; padding: 4px;"
|
||||
>
|
||||
${translateText("host_modal.player_limit_warning")}
|
||||
</div>`
|
||||
: ""
|
||||
}
|
||||
<hr style="width: 100%; border-top: 1px solid #444; margin: 16px 0;" />
|
||||
|
||||
<!-- Individual disables for structures/weapons -->
|
||||
@@ -549,7 +604,7 @@ export class HostLobbyModal extends LitElement {
|
||||
<!-- Lobby Selection -->
|
||||
<div class="options-section">
|
||||
<div class="option-title">
|
||||
${this.clients.length}
|
||||
${this.clients.length}${this.playerLimit !== null ? `/${this.playerLimit}` : ""}
|
||||
${
|
||||
this.clients.length === 1
|
||||
? translateText("host_modal.player")
|
||||
@@ -735,6 +790,38 @@ export class HostLobbyModal extends LitElement {
|
||||
this.putGameConfig();
|
||||
}
|
||||
|
||||
private handlePlayerLimitKeyDown(e: KeyboardEvent) {
|
||||
if (["-", "+", "e"].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private handlePlayerLimitChange(e: Event) {
|
||||
const input = e.target as HTMLInputElement;
|
||||
// Remove invalid characters
|
||||
input.value = input.value.replace(/[e+-]/gi, "");
|
||||
|
||||
const value = parseInt(input.value);
|
||||
|
||||
// Validate: must be a number between MIN and MAX
|
||||
if (
|
||||
isNaN(value) ||
|
||||
value < HostLobbyModal.MIN_PLAYER_LIMIT ||
|
||||
value > HostLobbyModal.MAX_PLAYER_LIMIT
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.playerLimit = value;
|
||||
this.updatePlayerLimitWarning();
|
||||
this.putGameConfig();
|
||||
}
|
||||
|
||||
private updatePlayerLimitWarning() {
|
||||
this.playerLimitWarning =
|
||||
this.playerLimit !== null && this.clients.length > this.playerLimit;
|
||||
}
|
||||
|
||||
private async handleDisableNPCsChange(e: Event) {
|
||||
this.disableNPCs = Boolean((e.target as HTMLInputElement).checked);
|
||||
console.log(`updating disable npcs to ${this.disableNPCs}`);
|
||||
@@ -786,6 +873,7 @@ export class HostLobbyModal extends LitElement {
|
||||
}),
|
||||
maxTimerValue:
|
||||
this.maxTimer === true ? this.maxTimerValue : undefined,
|
||||
maxPlayers: this.playerLimit ?? undefined,
|
||||
} satisfies Partial<GameConfig>),
|
||||
},
|
||||
);
|
||||
@@ -854,6 +942,7 @@ export class HostLobbyModal extends LitElement {
|
||||
console.log(`got game info response: ${JSON.stringify(data)}`);
|
||||
|
||||
this.clients = data.clients ?? [];
|
||||
this.updatePlayerLimitWarning();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
@state() private message: string = "";
|
||||
@state() private hasJoined = false;
|
||||
@state() private players: string[] = [];
|
||||
@state() private maxPlayers: number | null = null;
|
||||
|
||||
private playersInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
@@ -75,7 +76,9 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
${this.hasJoined && this.players.length > 0
|
||||
? html` <div class="options-section">
|
||||
<div class="option-title">
|
||||
${this.players.length}
|
||||
${this.players.length}${this.maxPlayers !== null
|
||||
? `/${this.maxPlayers}`
|
||||
: ""}
|
||||
${this.players.length === 1
|
||||
? translateText("private_lobby.player")
|
||||
: translateText("private_lobby.players")}
|
||||
@@ -127,6 +130,8 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
this.close();
|
||||
this.hasJoined = false;
|
||||
this.message = "";
|
||||
this.maxPlayers = null;
|
||||
this.players = [];
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("leave-lobby", {
|
||||
detail: { lobby: this.lobbyIdInput.value },
|
||||
@@ -315,6 +320,7 @@ export class JoinPrivateLobbyModal extends LitElement {
|
||||
.then((response) => response.json())
|
||||
.then((data: GameInfo) => {
|
||||
this.players = data.clients?.map((p) => p.username) ?? [];
|
||||
this.maxPlayers = data.gameConfig?.maxPlayers ?? null;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error polling players:", error);
|
||||
|
||||
+1
-1
@@ -170,7 +170,7 @@ export const GameConfigSchema = z.object({
|
||||
infiniteTroops: z.boolean(),
|
||||
instantBuild: z.boolean(),
|
||||
randomSpawn: z.boolean(),
|
||||
maxPlayers: z.number().optional(),
|
||||
maxPlayers: z.number().int().min(2).max(1000).optional(),
|
||||
maxTimerValue: z.number().int().min(1).max(120).optional(),
|
||||
disabledUnits: z.enum(UnitType).array().optional(),
|
||||
playerTeams: TeamCountConfigSchema.optional(),
|
||||
|
||||
@@ -128,6 +128,10 @@ export class GameServer {
|
||||
if (gameConfig.playerTeams !== undefined) {
|
||||
this.gameConfig.playerTeams = gameConfig.playerTeams;
|
||||
}
|
||||
|
||||
if (gameConfig.maxPlayers !== undefined) {
|
||||
this.gameConfig.maxPlayers = gameConfig.maxPlayers;
|
||||
}
|
||||
}
|
||||
|
||||
public joinClient(client: Client) {
|
||||
@@ -152,12 +156,18 @@ export class GameServer {
|
||||
) {
|
||||
this.log.warn(`cannot add client, game full`, {
|
||||
clientID: client.clientID,
|
||||
currentPlayers: this.activeClients.length,
|
||||
maxPlayers: this.gameConfig.maxPlayers,
|
||||
});
|
||||
|
||||
client.ws.send(
|
||||
JSON.stringify({
|
||||
type: "error",
|
||||
error: "full-lobby",
|
||||
message: JSON.stringify({
|
||||
currentPlayers: this.activeClients.length,
|
||||
maxPlayers: this.gameConfig.maxPlayers,
|
||||
}),
|
||||
} satisfies ServerErrorMessage),
|
||||
);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user