Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
9570fe2
feat: add in-lobby chat panel for private games
Phantasm0009 Nov 26, 2025
64d269b
chore: fix husky pre-commit and eslint mock inclusion
Phantasm0009 Nov 26, 2025
f31a3b0
fix: enforce 300-char limit and add __mocks__ to tsconfig
Phantasm0009 Nov 26, 2025
546b608
fix(lobby-chat): scroll after render, improve send failure UX, and na…
Phantasm0009 Nov 26, 2025
4894fa4
fix(lobby-chat): prevent default & stop propagation on Enter key
Phantasm0009 Nov 26, 2025
0d83cf8
Merge branch 'main' into lobby-chat-panel
Phantasm0009 Nov 26, 2025
17647d4
revert: remove unrelated StructureIconsLayer changes from lobby-chat PR
Phantasm0009 Nov 27, 2025
b16587d
fix: update OutlineFilter to use @pixi/filter-outline API
Phantasm0009 Nov 27, 2025
a806606
Merge branch 'main' into lobby-chat-panel
Phantasm0009 Nov 27, 2025
8a2056e
fix(deps): migrate to pixi-filters v6 for PixiJS v8 compatibility
Phantasm0009 Nov 27, 2025
9a5628d
Merge branch 'main' into lobby-chat-panel
Phantasm0009 Dec 1, 2025
f97d5c6
Fix clients able to join above max players (#2547)
Lavodan Dec 1, 2025
7de7f24
Add the Lisbon Map and Credit Copernicus DEM (#2545)
jachisc Dec 1, 2025
40be1d6
Fixes lobby team preview: clan players aren't assigned a team + add n…
VariableVince Dec 2, 2025
f241ac0
Correct Gulf of St. Lawrence map (#2555)
jachisc Dec 3, 2025
1ce87d2
fix failing nationNameLength test (#2556)
Lavodan Dec 3, 2025
dec23ce
Fix: firefox back button not working (#2557)
Lavodan Dec 3, 2025
df3e5d7
Special bot names (#2552)
VariableVince Dec 4, 2025
d0a047b
Fix spacing in player team label display (#2560)
VariableVince Dec 4, 2025
93cf256
Alert frame: add to in-game settings, orange for attack instead of re…
VariableVince Dec 4, 2025
d933b25
refactor: address code review feedback for lobby chat feature
Phantasm0009 Dec 4, 2025
7a28c49
Merge branch 'main' into lobby-chat-panel
Phantasm0009 Dec 4, 2025
255a767
fix: update ServerLobbyChatSchema to use username and isHost fields
Phantasm0009 Dec 4, 2025
762ac48
feat: improve lobby chat UX with local/remote message styling
Phantasm0009 Dec 4, 2025
254469d
fix: use lobbyConfig.playerName instead of undefined lobby variable
Phantasm0009 Dec 4, 2025
d60786f
Merge branch 'main' into lobby-chat-panel
Phantasm0009 Dec 11, 2025
d3abda1
Revamp nation/bot enemy selection 🗡️ (#2550)
FloPinguin Dec 11, 2025
4fce44c
refactor: remove redundant self-documenting comments and merge latest…
Phantasm0009 Dec 11, 2025
fbda9b3
refactor: use satisfies pattern for type safety in lobby chat message
Phantasm0009 Dec 11, 2025
a5cf249
Merge branch 'main' into lobby-chat-panel
Phantasm0009 Dec 11, 2025
b7ce424
Merge branch 'main' into lobby-chat-panel
Phantasm0009 Dec 14, 2025
669b524
Merge branch 'main' into lobby-chat-panel
Phantasm0009 Dec 15, 2025
f7c2255
Improve lobby chat typing and validation
Phantasm0009 Dec 15, 2025
7400ff9
Fix asset module declarations and build types
Phantasm0009 Dec 15, 2025
b78fecc
Merge upstream/main into lobby-chat-panel - Resolved conflicts and in…
Phantasm0009 Jan 4, 2026
7178053
fixed errors
Phantasm0009 Jan 4, 2026
6ec0106
fixed errors
Phantasm0009 Jan 4, 2026
4b0ebd9
Fix build errors: update tsconfig, add Window type extensions, fix te…
Phantasm0009 Jan 4, 2026
1ac4062
Merge branch 'main' into lobby-chat-panel
Phantasm0009 Jan 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified .husky/pre-commit
100644 → 100755
Empty file.
8 changes: 8 additions & 0 deletions __mocks__/jose.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this used for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its being used to mock the jose library for testing and its configured in the jest.config,test. Its imported by TestServerConfig.ts and it provides mock implementations for JWT functions during tesing.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Minimal mock of jose for tests
export const base64url = {
encode: (input) => Buffer.from(String(input)).toString("base64url"),
decode: (input) => Buffer.from(String(input), "base64url").toString("utf8"),
};
export const jwtVerify = async () => ({ payload: {}, protectedHeader: {} });
export const decodeJwt = () => ({ sub: "test" });
export const JWK = {};
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export default [
projectService: {
allowDefaultProject: [
"__mocks__/fileMock.js",
"__mocks__/jose.js",
"eslint.config.js",
"postcss.config.js",
"tailwind.config.js",
Expand Down
6 changes: 6 additions & 0 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,12 @@
"stop_trading": "Stop trading with [P1]!"
}
},
"lobby_chat": {
"title": "Lobby Chat",
"placeholder": "Type a message...",
"enable": "Enable Lobby Chat",
"send": "Send"
},
"build_menu": {
"desc": {
"atom_bomb": "Small explosion",
Expand Down
36 changes: 36 additions & 0 deletions src/client/ClientGameRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
GameStartInfo,
PlayerCosmeticRefs,
PlayerRecord,
ServerLobbyChatMessage,
ServerMessage,
} from "../core/Schemas";
import { createPartialGameRecord, replacer } from "../core/Util";
Expand Down Expand Up @@ -64,6 +65,18 @@ export interface LobbyConfig {
gameRecord?: GameRecord;
}

function isValidLobbyChatMessage(
message: ServerMessage,
): message is ServerLobbyChatMessage {
if (message.type !== "lobby_chat") return false;
const candidate = message as Record<string, unknown>;
return (
typeof candidate.username === "string" &&
typeof candidate.isHost === "boolean" &&
typeof candidate.text === "string"
);
}

export function joinLobby(
eventBus: EventBus,
lobbyConfig: LobbyConfig,
Expand All @@ -74,6 +87,12 @@ export function joinLobby(
`joining lobby: gameID: ${lobbyConfig.gameID}, clientID: ${lobbyConfig.clientID}`,
);

window.__eventBus = eventBus;
window.__username = lobbyConfig.playerName;
document.dispatchEvent(
new CustomEvent("event-bus:ready", { bubbles: true, composed: true }),
);

const userSettings: UserSettings = new UserSettings();
startGame(lobbyConfig.gameID, lobbyConfig.gameStartInfo?.config ?? {});

Expand Down Expand Up @@ -144,6 +163,23 @@ export function joinLobby(
);
}
}
if (message.type === "lobby_chat") {
if (!isValidLobbyChatMessage(message)) {
console.error("Malformed lobby_chat message:", message);
return;
}
document.dispatchEvent(
new CustomEvent("lobby-chat:message", {
detail: {
username: message.username,
isHost: message.isHost,
text: message.text,
},
bubbles: true,
composed: true,
}),
);
}
};
transport.connect(onconnect, onmessage);
return () => {
Expand Down
38 changes: 38 additions & 0 deletions src/client/HostLobbyModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { generateID } from "../core/Util";
import "./components/baseComponents/Modal";
import "./components/Difficulties";
import "./components/FluentSlider";
import "./components/LobbyChatPanel";
import "./components/LobbyTeamView";
import "./components/Maps";
import { crazyGamesSDK } from "./CrazyGamesSDK";
Expand Down Expand Up @@ -56,6 +57,7 @@ export class HostLobbyModal extends LitElement {
@state() private instantBuild: boolean = false;
@state() private randomSpawn: boolean = false;
@state() private compactMap: boolean = false;
@state() private chatEnabled: boolean = false;
@state() private lobbyId = "";
@state() private copySuccess = false;
@state() private clients: ClientInfo[] = [];
Expand Down Expand Up @@ -411,6 +413,28 @@ export class HostLobbyModal extends LitElement {
</div>
</label>

<!-- Enable Lobby Chat (Private only) -->
<label
for="enable-chat"
class="option-card ${this.chatEnabled ? "selected" : ""}"
>
<div class="checkbox-icon"></div>
<input
type="checkbox"
id="enable-chat"
@change=${(e: Event) => {
this.chatEnabled = Boolean(
(e.target as HTMLInputElement).checked,
);
this.putGameConfig();
}}
.checked=${this.chatEnabled}
/>
<div class="option-card-title">
${translateText("lobby_chat.enable")}
</div>
</label>

<label
for="donate-gold"
class="option-card ${this.donateGold ? "selected" : ""}"
Expand Down Expand Up @@ -614,6 +638,19 @@ export class HostLobbyModal extends LitElement {
.nationCount=${this.getEffectiveNationCount()}
.onKickPlayer=${(clientID: string) => this.kickPlayer(clientID)}
></lobby-team-view>

${
this.chatEnabled
? html`
<div class="mt-4">
<div class="option-title">
${translateText("lobby_chat.title")}
</div>
<lobby-chat-panel></lobby-chat-panel>
</div>
`
: ""
}
</div>

<div class="start-game-button-container">
Expand Down Expand Up @@ -850,6 +887,7 @@ export class HostLobbyModal extends LitElement {
: {
disableNations: this.disableNations,
}),
chatEnabled: this.chatEnabled,
maxTimerValue:
this.maxTimer === true ? this.maxTimerValue : undefined,
} satisfies Partial<GameConfig>,
Expand Down
43 changes: 31 additions & 12 deletions src/client/JoinPrivateLobbyModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { generateID } from "../core/Util";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { getApiBase } from "./Api";
import { JoinLobbyEvent } from "./Main";
import "./components/LobbyChatPanel";
import "./components/baseComponents/Button";
import "./components/baseComponents/Modal";
@customElement("join-private-lobby-modal")
Expand All @@ -18,6 +19,8 @@ export class JoinPrivateLobbyModal extends LitElement {
@state() private message: string = "";
@state() private hasJoined = false;
@state() private players: string[] = [];
// Whether the host enabled lobby chat (private lobbies only)
@state() private chatEnabled: boolean = false;

private playersInterval: NodeJS.Timeout | null = null;

Expand Down Expand Up @@ -73,20 +76,34 @@ export class JoinPrivateLobbyModal extends LitElement {
</div>
<div class="options-layout">
${this.hasJoined && this.players.length > 0
? html` <div class="options-section">
<div class="option-title">
${this.players.length}
${this.players.length === 1
? translateText("private_lobby.player")
: translateText("private_lobby.players")}
? html`
<div class="options-section">
<div class="option-title">
${this.players.length}
${this.players.length === 1
? translateText("private_lobby.player")
: translateText("private_lobby.players")}
</div>

<div class="players-list">
${this.players.map(
(player) =>
html`<span class="player-tag">${player}</span>`,
)}
</div>
</div>

<div class="players-list">
${this.players.map(
(player) => html`<span class="player-tag">${player}</span>`,
)}
</div>
</div>`
${this.chatEnabled
? html`
<div class="options-section" style="margin-top: 12px;">
<div class="option-title">
${translateText("lobby_chat.title")}
</div>
<lobby-chat-panel></lobby-chat-panel>
</div>
`
: ""}
`
: ""}
</div>
<div class="flex justify-center">
Expand Down Expand Up @@ -336,6 +353,8 @@ export class JoinPrivateLobbyModal extends LitElement {
.then((response) => response.json())
.then((data: GameInfo) => {
this.players = data.clients?.map((p) => p.username) ?? [];
// Reflect server-configured chat toggle for joiners
this.chatEnabled = Boolean(data.gameConfig?.chatEnabled);
})
.catch((error) => {
console.error("Error polling players:", error);
Expand Down
1 change: 1 addition & 0 deletions src/client/SinglePlayerModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ export class SinglePlayerModal extends LitElement {
: GameMapSize.Normal,
gameType: GameType.Singleplayer,
gameMode: this.gameMode,
chatEnabled: false,
playerTeams: this.teamCount,
difficulty: this.selectedDifficulty,
maxTimerValue: this.maxTimer ? this.maxTimerValue : undefined,
Expand Down
15 changes: 15 additions & 0 deletions src/client/Transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ClientHashMessage,
ClientIntentMessage,
ClientJoinMessage,
ClientLobbyChatMessage,
ClientMessage,
ClientPingMessage,
ClientRejoinMessage,
Expand Down Expand Up @@ -176,6 +177,11 @@ export class SendKickPlayerIntentEvent implements GameEvent {
constructor(public readonly target: string) {}
}

// New event: send a basic lobby chat message
export class SendLobbyChatEvent implements GameEvent {
constructor(public readonly text: string) {}
}

export class SendUpdateGameConfigIntentEvent implements GameEvent {
constructor(public readonly config: Partial<GameConfig>) {}
}
Expand Down Expand Up @@ -265,6 +271,7 @@ export class Transport {
this.eventBus.on(SendKickPlayerIntentEvent, (e) =>
this.onSendKickPlayerIntent(e),
);
this.eventBus.on(SendLobbyChatEvent, (e) => this.onSendLobbyChat(e));

this.eventBus.on(SendUpdateGameConfigIntentEvent, (e) =>
this.onSendUpdateGameConfigIntent(e),
Expand Down Expand Up @@ -668,6 +675,14 @@ export class Transport {
});
}

private onSendLobbyChat(event: SendLobbyChatEvent) {
this.sendMsg({
type: "lobby_chat",
text: event.text,
clientID: this.lobbyConfig.clientID,
} satisfies ClientLobbyChatMessage);
}

private onSendUpdateGameConfigIntent(event: SendUpdateGameConfigIntentEvent) {
this.sendIntent({
type: "update_game_config",
Expand Down
Loading
Loading