From 633cc64e4f571568f2f4d7575de01800612061df Mon Sep 17 00:00:00 2001 From: Restart2008 Date: Wed, 24 Dec 2025 11:47:11 -0800 Subject: [PATCH 1/5] testing --- src/client/ClientGameRunner.ts | 1 + src/client/graphics/layers/PlayerPanel.ts | 19 +++++++++++++++++++ src/core/GameRunner.ts | 5 +++++ src/core/Schemas.ts | 1 + src/core/game/Game.ts | 1 + src/core/game/GameView.ts | 5 +++++ src/server/GameServer.ts | 1 + 7 files changed, 33 insertions(+) diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index 5089b6be22..baf317045e 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -191,6 +191,7 @@ async function createClientGame( lobbyConfig.clientID, lobbyConfig.gameStartInfo.gameID, lobbyConfig.gameStartInfo.players, + lobbyConfig.gameStartInfo.lobbyCreatorID, ); const canvas = createCanvas(); diff --git a/src/client/graphics/layers/PlayerPanel.ts b/src/client/graphics/layers/PlayerPanel.ts index 560c1fe227..087ecd0994 100644 --- a/src/client/graphics/layers/PlayerPanel.ts +++ b/src/client/graphics/layers/PlayerPanel.ts @@ -2,6 +2,7 @@ import { html, LitElement } from "lit"; import { customElement, state } from "lit/decorators.js"; import allianceIcon from "../../../../resources/images/AllianceIconWhite.svg"; import chatIcon from "../../../../resources/images/ChatIconWhite.svg"; +import disabledIcon from "../../../../resources/images/DisabledIcon.svg"; import donateGoldIcon from "../../../../resources/images/DonateGoldIconWhite.svg"; import donateTroopIcon from "../../../../resources/images/DonateTroopIconWhite.svg"; import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg"; @@ -31,6 +32,7 @@ import { SendEmbargoAllIntentEvent, SendEmbargoIntentEvent, SendEmojiIntentEvent, + SendKickPlayerIntentEvent, SendTargetPlayerIntentEvent, } from "../../Transport"; import { @@ -274,6 +276,12 @@ export class PlayerPanel extends LitElement implements Layer { this.hide(); } + private handleKickClick(e: Event, other: PlayerView) { + e.stopPropagation(); + this.eventBus.emit(new SendKickPlayerIntentEvent(other.clientID()!)); + this.hide(); + } + private identityChipProps(type: PlayerType) { switch (type) { case PlayerType.Nation: @@ -618,6 +626,7 @@ export class PlayerPanel extends LitElement implements Layer { const canBreakAlliance = this.actions?.interaction?.canBreakAlliance; const canTarget = this.actions?.interaction?.canTarget; const canEmbargo = this.actions?.interaction?.canEmbargo; + const canKick = this.actions?.interaction?.canKick; return html`
@@ -718,6 +727,16 @@ export class PlayerPanel extends LitElement implements Layer { type: "indigo", }) : ""} + ${canKick + ? actionButton({ + onClick: (e: MouseEvent) => this.handleKickClick(e, other), + icon: disabledIcon, + iconAlt: "Kick", + title: translateText("player_panel.kick"), + label: translateText("player_panel.kick"), + type: "red", + }) + : ""}
${other === my diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index ed8c8cd7b2..9c6af48eca 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -77,6 +77,7 @@ export async function createGameRunner( game, new Executor(game, gameStart.gameID, clientID), callBack, + gameStart.lobbyCreatorID, ); gr.init(); return gr; @@ -93,6 +94,7 @@ export class GameRunner { public game: Game, private execManager: Executor, private callBack: (gu: GameUpdateViewData | ErrorUpdate) => void, + private lobbyCreatorID: ClientID | undefined, ) {} init() { @@ -207,6 +209,9 @@ export class GameRunner { canDonateGold: player.canDonateGold(other), canDonateTroops: player.canDonateTroops(other), canEmbargo: !player.hasEmbargoAgainst(other), + canKick: + this.lobbyCreatorID === player.clientID() && + player.clientID() !== other.clientID(), }; const alliance = player.allianceWith(other as Player); if (alliance) { diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts index 9370d6a477..958b552561 100644 --- a/src/core/Schemas.ts +++ b/src/core/Schemas.ts @@ -437,6 +437,7 @@ export const GameStartInfoSchema = z.object({ lobbyCreatedAt: z.number(), config: GameConfigSchema, players: PlayerSchema.array(), + lobbyCreatorID: ID.optional(), }); export const WinnerSchema = z diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 9c5ef95ffd..33e8d81608 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -796,6 +796,7 @@ export interface PlayerInteraction { canDonateGold: boolean; canDonateTroops: boolean; canEmbargo: boolean; + canKick?: boolean; allianceExpiresAt?: Tick; } diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts index a35416364e..2afef7aac3 100644 --- a/src/core/game/GameView.ts +++ b/src/core/game/GameView.ts @@ -601,6 +601,7 @@ export class GameView implements GameMap { private _myClientID: ClientID, private _gameID: GameID, private humans: Player[], + private _lobbyCreatorID: ClientID | undefined, ) { this._map = this._mapData.gameMap; this.lastUpdate = null; @@ -616,6 +617,10 @@ export class GameView implements GameMap { } } + isLobbyCreator(player: PlayerView): boolean { + return player.clientID() === this._lobbyCreatorID; + } + isOnEdgeOfMap(ref: TileRef): boolean { return this._map.isOnEdgeOfMap(ref); } diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts index 0f26df3688..f87be41536 100644 --- a/src/server/GameServer.ts +++ b/src/server/GameServer.ts @@ -462,6 +462,7 @@ export class GameServer { clientID: c.clientID, cosmetics: c.cosmetics, })), + lobbyCreatorID: this.lobbyCreatorID, }); if (!result.success) { const error = z.prettifyError(result.error); From 17231ff0e694a2576542ef8ca68a886b6372cd46 Mon Sep 17 00:00:00 2001 From: Restart2008 Date: Wed, 24 Dec 2025 13:49:01 -0800 Subject: [PATCH 2/5] add translation for player kick --- resources/lang/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/lang/en.json b/resources/lang/en.json index e45598c076..e1bebea9eb 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -693,7 +693,8 @@ "send_alliance": "Send Alliance", "send_troops": "Send Troops", "send_gold": "Send Gold", - "emotes": "Emojis" + "emotes": "Emojis", + "kick": "Kick Player" }, "send_troops_modal": { "title_with_name": "Send Troops to {name}", From 5c0f5abc18b5ba62a58ad7b8d313bb65c014051c Mon Sep 17 00:00:00 2001 From: Restart2008 Date: Wed, 24 Dec 2025 14:21:43 -0800 Subject: [PATCH 3/5] Update src/client/graphics/layers/PlayerPanel.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- src/client/graphics/layers/PlayerPanel.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/client/graphics/layers/PlayerPanel.ts b/src/client/graphics/layers/PlayerPanel.ts index 087ecd0994..6ece2050e5 100644 --- a/src/client/graphics/layers/PlayerPanel.ts +++ b/src/client/graphics/layers/PlayerPanel.ts @@ -278,7 +278,12 @@ export class PlayerPanel extends LitElement implements Layer { private handleKickClick(e: Event, other: PlayerView) { e.stopPropagation(); - this.eventBus.emit(new SendKickPlayerIntentEvent(other.clientID()!)); + const targetClientID = other.clientID(); + if (!targetClientID) { + console.warn("Cannot kick player without clientID"); + return; + } + this.eventBus.emit(new SendKickPlayerIntentEvent(targetClientID)); this.hide(); } From cf0e53c412c388a5b306fcdfc790057bbc39402c Mon Sep 17 00:00:00 2001 From: Restart2008 Date: Thu, 25 Dec 2025 09:47:36 -0800 Subject: [PATCH 4/5] add measure to prevent lobby creator from kicking themselves out of the game --- src/client/graphics/layers/PlayerPanel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/graphics/layers/PlayerPanel.ts b/src/client/graphics/layers/PlayerPanel.ts index 6ece2050e5..ab95ac44d8 100644 --- a/src/client/graphics/layers/PlayerPanel.ts +++ b/src/client/graphics/layers/PlayerPanel.ts @@ -732,7 +732,7 @@ export class PlayerPanel extends LitElement implements Layer { type: "indigo", }) : ""} - ${canKick + ${canKick && !this.g.isLobbyCreator(other) ? actionButton({ onClick: (e: MouseEvent) => this.handleKickClick(e, other), icon: disabledIcon, From 83eb1e04dc4607ef27d119ad70ce6326a0612496 Mon Sep 17 00:00:00 2001 From: Restart2008 Date: Thu, 25 Dec 2025 09:53:00 -0800 Subject: [PATCH 5/5] kick confirmation with translation key --- resources/lang/en.json | 3 ++- src/client/graphics/layers/PlayerPanel.ts | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/resources/lang/en.json b/resources/lang/en.json index e1bebea9eb..4a3629e067 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -694,7 +694,8 @@ "send_troops": "Send Troops", "send_gold": "Send Gold", "emotes": "Emojis", - "kick": "Kick Player" + "kick": "Kick Player", + "kick_confirm": "Are you sure you want to kick {name}?" }, "send_troops_modal": { "title_with_name": "Send Troops to {name}", diff --git a/src/client/graphics/layers/PlayerPanel.ts b/src/client/graphics/layers/PlayerPanel.ts index ab95ac44d8..bad07c19a1 100644 --- a/src/client/graphics/layers/PlayerPanel.ts +++ b/src/client/graphics/layers/PlayerPanel.ts @@ -278,6 +278,13 @@ export class PlayerPanel extends LitElement implements Layer { private handleKickClick(e: Event, other: PlayerView) { e.stopPropagation(); + if ( + !window.confirm( + translateText("player_panel.kick_confirm", { name: other.name() }), + ) + ) { + return; + } const targetClientID = other.clientID(); if (!targetClientID) { console.warn("Cannot kick player without clientID");