mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 13:30:43 +00:00
Fix for v29: Add nation count loading for JoinPrivateLobbyModal; change HvN difficulty (#2941)
## Description: 1. In JoinPrivateLobbyModal the nation count loading was missing. That caused the team preview UI to show different player counts compared to the HostLobbyModal. For example it showed 0/0 nations for the HumansVsNations team mode (instead of 2/2): <img width="726" height="217" alt="Screenshot 2026-01-16 211337" src="https://github.com/user-attachments/assets/8b4219de-e2b2-46ff-a600-c86915e5bdb3" /> 2. Turn down HvN difficulty from Impossible to Hard. We steamrolled over Hard nations in the playtest (at least in two of the three games) because we donated lots of troops to each other. But after some API data research I noticed that only 33% of players in public team games ever use the donate functionality. And we probably have less skilled players in public games than in the playtest. So its probably better to use the Hard difficulty to ensure balanced gameplay. I know, I'm overthinking this 😂 ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [X] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: FloPinguin
This commit is contained in:
@@ -14,7 +14,6 @@ import {
|
||||
UnitType,
|
||||
mapCategories,
|
||||
} from "../core/game/Game";
|
||||
import { getCompactMapNationCount } from "../core/game/NationCreation";
|
||||
import {
|
||||
ClientInfo,
|
||||
GameConfig,
|
||||
@@ -28,7 +27,7 @@ import { BaseModal } from "./components/BaseModal";
|
||||
import "./components/CopyButton";
|
||||
import "./components/Difficulties";
|
||||
import "./components/FluentSlider";
|
||||
import "./components/LobbyTeamView";
|
||||
import "./components/LobbyPlayerView";
|
||||
import "./components/Maps";
|
||||
import { modalHeader } from "./components/ui/ModalHeader";
|
||||
import { crazyGamesSDK } from "./CrazyGamesSDK";
|
||||
@@ -851,33 +850,16 @@ export class HostLobbyModal extends BaseModal {
|
||||
</div>
|
||||
|
||||
<!-- Player List -->
|
||||
<div class="border-t border-white/10 pt-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div
|
||||
class="text-xs font-bold text-white/40 uppercase tracking-widest"
|
||||
>
|
||||
${this.clients.length}
|
||||
${this.clients.length === 1
|
||||
? translateText("host_modal.player")
|
||||
: translateText("host_modal.players")}
|
||||
<span style="margin: 0 8px;">•</span>
|
||||
${this.getEffectiveNationCount()}
|
||||
${this.getEffectiveNationCount() === 1
|
||||
? translateText("host_modal.nation_player")
|
||||
: translateText("host_modal.nation_players")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<lobby-team-view
|
||||
class="block rounded-lg border border-white/10 bg-white/5 p-2"
|
||||
.gameMode=${this.gameMode}
|
||||
.clients=${this.clients}
|
||||
.lobbyCreatorClientID=${this.lobbyCreatorClientID}
|
||||
.teamCount=${this.teamCount}
|
||||
.nationCount=${this.getEffectiveNationCount()}
|
||||
.onKickPlayer=${(clientID: string) => this.kickPlayer(clientID)}
|
||||
></lobby-team-view>
|
||||
</div>
|
||||
<lobby-player-view
|
||||
.gameMode=${this.gameMode}
|
||||
.clients=${this.clients}
|
||||
.lobbyCreatorClientID=${this.lobbyCreatorClientID}
|
||||
.teamCount=${this.teamCount}
|
||||
.nationCount=${this.nationCount}
|
||||
.disableNations=${this.disableNations}
|
||||
.isCompactMap=${this.compactMap}
|
||||
.onKickPlayer=${(clientID: string) => this.kickPlayer(clientID)}
|
||||
></lobby-player-view>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1340,31 +1322,22 @@ export class HostLobbyModal extends BaseModal {
|
||||
}
|
||||
|
||||
private async loadNationCount() {
|
||||
const currentMap = this.selectedMap;
|
||||
try {
|
||||
const mapData = this.mapLoader.getMapData(this.selectedMap);
|
||||
const mapData = this.mapLoader.getMapData(currentMap);
|
||||
const manifest = await mapData.manifest();
|
||||
this.nationCount = manifest.nations.length;
|
||||
// Only update if the map hasn't changed
|
||||
if (this.selectedMap === currentMap) {
|
||||
this.nationCount = manifest.nations.length;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to load nation count", error);
|
||||
this.nationCount = 0;
|
||||
// Only update if the map hasn't changed
|
||||
if (this.selectedMap === currentMap) {
|
||||
this.nationCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effective nation count for display purposes.
|
||||
* In HumansVsNations mode, this equals the number of human players.
|
||||
* For compact maps, only 25% of nations are used.
|
||||
* Otherwise, it uses the manifest nation count (or 0 if nations are disabled).
|
||||
*/
|
||||
private getEffectiveNationCount(): number {
|
||||
if (this.disableNations) {
|
||||
return 0;
|
||||
}
|
||||
if (this.gameMode === GameMode.Team && this.teamCount === HumansVsNations) {
|
||||
return this.clients.length;
|
||||
}
|
||||
return getCompactMapNationCount(this.nationCount, this.compactMap);
|
||||
}
|
||||
}
|
||||
|
||||
async function createLobby(creatorClientID: string): Promise<GameInfo> {
|
||||
|
||||
@@ -10,13 +10,14 @@ import {
|
||||
} from "../core/Schemas";
|
||||
import { generateID } from "../core/Util";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { GameMode } from "../core/game/Game";
|
||||
import { GameMapSize, GameMode } from "../core/game/Game";
|
||||
import { getApiBase } from "./Api";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
|
||||
import { BaseModal } from "./components/BaseModal";
|
||||
import "./components/CopyButton";
|
||||
import "./components/Difficulties";
|
||||
import "./components/LobbyTeamView";
|
||||
import "./components/LobbyPlayerView";
|
||||
import { modalHeader } from "./components/ui/ModalHeader";
|
||||
@customElement("join-private-lobby-modal")
|
||||
export class JoinPrivateLobbyModal extends BaseModal {
|
||||
@@ -27,8 +28,10 @@ export class JoinPrivateLobbyModal extends BaseModal {
|
||||
@state() private gameConfig: GameConfig | null = null;
|
||||
@state() private lobbyCreatorClientID: string | null = null;
|
||||
@state() private currentLobbyId: string = "";
|
||||
@state() private nationCount: number = 0;
|
||||
|
||||
private playersInterval: NodeJS.Timeout | null = null;
|
||||
private mapLoader = terrainMapFileLoader;
|
||||
|
||||
private leaveLobbyOnClose = true;
|
||||
|
||||
@@ -93,26 +96,17 @@ export class JoinPrivateLobbyModal extends BaseModal {
|
||||
${this.renderGameConfig()}
|
||||
${this.hasJoined && this.players.length > 0
|
||||
? html`
|
||||
<div class="mt-6 border-t border-white/10 pt-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div
|
||||
class="text-xs font-bold text-white/40 uppercase tracking-widest"
|
||||
>
|
||||
${this.players.length}
|
||||
${this.players.length === 1
|
||||
? translateText("private_lobby.player")
|
||||
: translateText("private_lobby.players")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<lobby-team-view
|
||||
class="block rounded-lg border border-white/10 bg-white/5 p-2"
|
||||
.gameMode=${this.gameConfig?.gameMode ?? GameMode.FFA}
|
||||
.clients=${this.players}
|
||||
.lobbyCreatorClientID=${this.lobbyCreatorClientID}
|
||||
.teamCount=${this.gameConfig?.playerTeams ?? 2}
|
||||
></lobby-team-view>
|
||||
</div>
|
||||
<lobby-player-view
|
||||
class="mt-6"
|
||||
.gameMode=${this.gameConfig?.gameMode ?? GameMode.FFA}
|
||||
.clients=${this.players}
|
||||
.lobbyCreatorClientID=${this.lobbyCreatorClientID}
|
||||
.teamCount=${this.gameConfig?.playerTeams ?? 2}
|
||||
.nationCount=${this.nationCount}
|
||||
.disableNations=${this.gameConfig?.disableNations ?? false}
|
||||
.isCompactMap=${this.gameConfig?.gameMapSize ===
|
||||
GameMapSize.Compact}
|
||||
></lobby-player-view>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
@@ -296,6 +290,7 @@ export class JoinPrivateLobbyModal extends BaseModal {
|
||||
this.hasJoined = false;
|
||||
this.message = "";
|
||||
this.currentLobbyId = "";
|
||||
this.nationCount = 0;
|
||||
|
||||
this.leaveLobbyOnClose = true;
|
||||
}
|
||||
@@ -512,11 +507,38 @@ export class JoinPrivateLobbyModal extends BaseModal {
|
||||
this.lobbyCreatorClientID = data.clients?.[0]?.clientID ?? null;
|
||||
this.players = data.clients ?? [];
|
||||
if (data.gameConfig) {
|
||||
const mapChanged =
|
||||
this.gameConfig?.gameMap !== data.gameConfig.gameMap;
|
||||
this.gameConfig = data.gameConfig;
|
||||
if (mapChanged) {
|
||||
this.loadNationCount();
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error polling players:", error);
|
||||
});
|
||||
}
|
||||
|
||||
private async loadNationCount() {
|
||||
if (!this.gameConfig) {
|
||||
this.nationCount = 0;
|
||||
return;
|
||||
}
|
||||
const currentMap = this.gameConfig.gameMap;
|
||||
try {
|
||||
const mapData = this.mapLoader.getMapData(currentMap);
|
||||
const manifest = await mapData.manifest();
|
||||
// Only update if the map hasn't changed
|
||||
if (this.gameConfig?.gameMap === currentMap) {
|
||||
this.nationCount = manifest.nations.length;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("Failed to load nation count", error);
|
||||
// Only update if the map hasn't changed
|
||||
if (this.gameConfig?.gameMap === currentMap) {
|
||||
this.nationCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
Team,
|
||||
Trios,
|
||||
} from "../../core/game/Game";
|
||||
import { getCompactMapNationCount } from "../../core/game/NationCreation";
|
||||
import { assignTeamsLobbyPreview } from "../../core/game/TeamAssignment";
|
||||
import { ClientInfo, TeamCountConfig } from "../../core/Schemas";
|
||||
import { translateText } from "../Utils";
|
||||
@@ -22,7 +23,7 @@ export interface TeamPreviewData {
|
||||
players: ClientInfo[];
|
||||
}
|
||||
|
||||
@customElement("lobby-team-view")
|
||||
@customElement("lobby-player-view")
|
||||
export class LobbyTeamView extends LitElement {
|
||||
@property({ type: String }) gameMode: GameMode = GameMode.FFA;
|
||||
@property({ type: Array }) clients: ClientInfo[] = [];
|
||||
@@ -32,6 +33,8 @@ export class LobbyTeamView extends LitElement {
|
||||
@property({ attribute: "team-count" }) teamCount: TeamCountConfig = 2;
|
||||
@property({ type: Function }) onKickPlayer?: (clientID: string) => void;
|
||||
@property({ type: Number }) nationCount: number = 0;
|
||||
@property({ type: Boolean }) disableNations: boolean = false;
|
||||
@property({ type: Boolean }) isCompactMap: boolean = false;
|
||||
|
||||
private theme: PastelTheme = new PastelTheme();
|
||||
@state() private showTeamColors: boolean = false;
|
||||
@@ -52,11 +55,32 @@ export class LobbyTeamView extends LitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<div class="players-list">
|
||||
${this.gameMode === GameMode.Team
|
||||
? this.renderTeamMode()
|
||||
: this.renderFreeForAll()}
|
||||
</div>`;
|
||||
return html`
|
||||
<div class="border-t border-white/10 pt-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div
|
||||
class="text-xs font-bold text-white/40 uppercase tracking-widest"
|
||||
>
|
||||
${this.clients.length}
|
||||
${this.clients.length === 1
|
||||
? translateText("host_modal.player")
|
||||
: translateText("host_modal.players")}
|
||||
<span style="margin: 0 8px;">•</span>
|
||||
${this.getEffectiveNationCount()}
|
||||
${this.getEffectiveNationCount() === 1
|
||||
? translateText("host_modal.nation_player")
|
||||
: translateText("host_modal.nation_players")}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="players-list block rounded-lg border border-white/10 bg-white/5 p-2"
|
||||
>
|
||||
${this.gameMode === GameMode.Team
|
||||
? this.renderTeamMode()
|
||||
: this.renderFreeForAll()}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
createRenderRoot() {
|
||||
@@ -148,14 +172,15 @@ export class LobbyTeamView extends LitElement {
|
||||
}
|
||||
|
||||
private renderTeamCard(preview: TeamPreviewData, isEmpty: boolean = false) {
|
||||
const effectiveNationCount = this.getEffectiveNationCount();
|
||||
const displayCount =
|
||||
preview.team === ColoredTeams.Nations
|
||||
? this.nationCount
|
||||
? effectiveNationCount
|
||||
: preview.players.length;
|
||||
|
||||
const maxTeamSize =
|
||||
preview.team === ColoredTeams.Nations
|
||||
? this.nationCount
|
||||
? effectiveNationCount
|
||||
: this.teamMaxSize;
|
||||
|
||||
return html`
|
||||
@@ -308,4 +333,20 @@ export class LobbyTeamView extends LitElement {
|
||||
players: buckets.get(t) ?? [],
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the effective nation count for display purposes.
|
||||
* In HumansVsNations mode, this equals the number of human players.
|
||||
* For compact maps, only 25% of nations are used.
|
||||
* Otherwise, it uses the manifest nation count (or 0 if nations are disabled).
|
||||
*/
|
||||
private getEffectiveNationCount(): number {
|
||||
if (this.disableNations) {
|
||||
return 0;
|
||||
}
|
||||
if (this.gameMode === GameMode.Team && this.teamCount === HumansVsNations) {
|
||||
return this.clients.length;
|
||||
}
|
||||
return getCompactMapNationCount(this.nationCount, this.isCompactMap);
|
||||
}
|
||||
}
|
||||
@@ -546,7 +546,6 @@ label.option-card:hover {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
/* News Button Notification */
|
||||
|
||||
@@ -127,9 +127,7 @@ export class MapPlaylist {
|
||||
publicGameModifiers: { isCompact, isRandomSpawn, startingGold },
|
||||
startingGold,
|
||||
difficulty:
|
||||
playerTeams === HumansVsNations
|
||||
? Difficulty.Impossible
|
||||
: Difficulty.Easy,
|
||||
playerTeams === HumansVsNations ? Difficulty.Hard : Difficulty.Easy,
|
||||
infiniteGold: false,
|
||||
infiniteTroops: false,
|
||||
maxTimerValue: undefined,
|
||||
|
||||
Reference in New Issue
Block a user