Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
86 changes: 86 additions & 0 deletions resources/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -699,7 +699,89 @@
"user_setting": {
"title": "Settings",
"tab_basic": "Basic Settings",
"tab_sounds": "Sounds",
"tab_effects": "Effects",
"tab_notifications": "Notifications",
"tab_keybinds": "Keybinds",
"notifications_game_start_label": "Game start notification",
"notifications_game_start_desc": "Show a browser notification when a game starts",
"notifications_permission_label": "Browser permission",
"notifications_permission_granted": "Granted",
"notifications_permission_denied": "Denied",
"notifications_permission_default": "Not granted",
"notifications_permission_unsupported": "Not supported",
"notifications_permission_request": "Request permission",
"sounds_background_music_label": "Background Music Volume",
"sounds_background_music_desc": "Adjust the volume of the background music",
"sounds_effects_label": "Sound Effects Volume",
"sounds_effects_desc": "Adjust the volume of in-game sound effects",
"sounds_category_volume": "Volume",
"sounds_category_weapons": "Weapons & Combat",
"sounds_category_diplomacy": "Diplomacy",
"sounds_category_construction": "Construction",
"sounds_category_ui": "UI & Notifications",
"sounds_effect_atom_launch_label": "Atomic Bomb Launch",
"sounds_effect_atom_launch_desc": "Played when an atomic bomb is launched",
"sounds_effect_atom_hit_label": "Atomic Bomb Hit",
"sounds_effect_atom_hit_desc": "Played when an atomic bomb hits a target",
"sounds_effect_hydrogen_launch_label": "Hydrogen Bomb Launch",
"sounds_effect_hydrogen_launch_desc": "Played when a hydrogen bomb is launched",
"sounds_effect_hydrogen_hit_label": "Hydrogen Bomb Hit",
"sounds_effect_hydrogen_hit_desc": "Played when a hydrogen bomb hits a target",
"sounds_effect_mirv_launch_label": "MIRV Launch",
"sounds_effect_mirv_launch_desc": "Played when a MIRV missile is launched",
"sounds_effect_alliance_suggested_label": "Alliance Suggested",
"sounds_effect_alliance_suggested_desc": "Played when another player proposes an alliance",
"sounds_effect_alliance_broken_label": "Alliance Broken",
"sounds_effect_alliance_broken_desc": "Played when an alliance is broken",
"sounds_effect_build_city_label": "City Built",
"sounds_effect_build_city_desc": "Played when a city is constructed",
"sounds_effect_build_port_label": "Port Built",
"sounds_effect_build_port_desc": "Played when a port is constructed",
"sounds_effect_build_defense_post_label": "Defense Post Built",
"sounds_effect_build_defense_post_desc": "Played when a defense post is constructed",
"sounds_effect_build_warship_label": "Warship Built",
"sounds_effect_build_warship_desc": "Played when a warship is constructed",
"sounds_effect_sam_built_label": "SAM Site Built",
"sounds_effect_sam_built_desc": "Played when a SAM site is constructed",
"sounds_effect_upgrade_label": "Structure Upgraded",
"sounds_effect_upgrade_desc": "Played when a building, port or factory is upgraded",
"sounds_effect_add_ammo_label": "Ammo Added",
"sounds_effect_add_ammo_desc": "Played when a missile silo or SAM is upgraded",
"fx_category_nuclear": "Nuclear",
"fx_category_combat": "Combat",
"fx_category_environment": "Environment",
"fx_category_alerts": "Screen Alerts",
"fx_nuke_telegraph_label": "Nuke Telegraph",
"fx_nuke_telegraph_desc": "Trajectory line shown when a nuke is launched by you or a teammate",
"fx_alert_land_attack_label": "Attack Alert",
"fx_alert_land_attack_desc": "Orange border flash when you are being attacked",
"fx_alert_betrayal_label": "Betrayal Alert",
"fx_alert_betrayal_desc": "Red border flash when an ally breaks your alliance",
"fx_nuke_explosion_label": "Nuclear Explosion",
"fx_nuke_explosion_desc": "Main explosion animation and shockwave when a nuke detonates",
"fx_nuke_debris_label": "Nuclear Debris",
"fx_nuke_debris_desc": "Fire and smoke debris scattered around the nuke impact zone",
"fx_sam_interception_label": "SAM Interception",
"fx_sam_interception_desc": "Explosion and shockwave when a SAM intercepts a missile",
"fx_building_explosion_label": "Building Explosion",
"fx_building_explosion_desc": "Explosion animation when a building is destroyed",
"fx_shell_impact_label": "Shell Impact",
"fx_shell_impact_desc": "Small explosion when a shell or train reaches its target",
"fx_warship_sinking_label": "Warship Sinking",
"fx_warship_sinking_desc": "Explosion and sinking animation when a warship is destroyed",
"fx_conquest_label": "Conquest Sword",
"fx_conquest_desc": "Sword animation displayed when you conquer enemy territory",
"fx_dust_label": "Railroad Dust",
"fx_dust_desc": "Dust particles when railroad tiles are destroyed",
"sounds_effect_ka_ching_label": "Gold Received",
"sounds_effect_ka_ching_desc": "Played for gold or economy events",
"sounds_effect_message_label": "Message",
"sounds_effect_message_desc": "Played when a chat message is received",
"sounds_effect_click_label": "Click",
"sounds_effect_click_desc": "Played on UI button clicks",
"sounds_effect_game_start_label": "Game Start",
"sounds_effect_game_start_desc": "Played when a game begins",
"keybinds_hint": "Click a key to rebind it. You can assign a single key or Shift + key combination.",
"dark_mode_label": "Dark Mode",
"dark_mode_desc": "Toggle the site’s appearance between light and dark themes",
Expand Down Expand Up @@ -1270,5 +1352,9 @@
"fullscreen": {
"enter": "Enter fullscreen",
"exit": "Exit fullscreen"
},
"game_start_notification": {
"title": "The game is starting",
"body": "Choose your starting position"
}
Comment on lines 1352 to 1359
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Move game_start_notification under fullscreen to match expected key path.

This block is currently top-level, but the stack contract for this PR expects fullscreen.game_start_notification.title/body. In the current shape, lookups on that path will fail.

Proposed fix
   "fullscreen": {
-    "enter": "Enter fullscreen",
-    "exit": "Exit fullscreen"
-  },
-  "game_start_notification": {
-    "title": "The game is starting",
-    "body": "Choose your starting position"
+    "enter": "Enter fullscreen",
+    "exit": "Exit fullscreen",
+    "game_start_notification": {
+      "title": "The game is starting",
+      "body": "Choose your starting position"
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"fullscreen": {
"enter": "Enter fullscreen",
"exit": "Exit fullscreen"
},
"game_start_notification": {
"title": "The game is starting",
"body": "Choose your starting position"
}
"fullscreen": {
"enter": "Enter fullscreen",
"exit": "Exit fullscreen",
"game_start_notification": {
"title": "The game is starting",
"body": "Choose your starting position"
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/lang/en.json` around lines 1352 - 1359, The JSON key
"game_start_notification" is placed at the top level but should be nested under
the "fullscreen" object; move the entire "game_start_notification" object so its
keys become fullscreen.game_start_notification.title and
fullscreen.game_start_notification.body to match the expected key path (update
the "fullscreen" object in resources/lang/en.json to include
"game_start_notification" rather than leaving it top-level).

}
Binary file added resources/sounds/effects/add-ammo.mp3
Binary file not shown.
Binary file added resources/sounds/effects/start.mp3
Binary file not shown.
Binary file added resources/sounds/effects/upgrade.mp3
Binary file not shown.
23 changes: 22 additions & 1 deletion src/client/ClientGameRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import { createCanvas } from "./Utils";
import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
import { GoToPlayerEvent } from "./graphics/TransformHandler";
import { SoundManager } from "./sound/SoundManager";
import { PlaySoundEffectEvent } from "./sound/Sounds";

export interface LobbyConfig {
cosmetics: PlayerCosmeticRefs;
Expand Down Expand Up @@ -99,6 +100,7 @@ export function joinLobby(
startGame(lobbyConfig.gameID, lobbyConfig.gameStartInfo?.config ?? {});

const transport = new Transport(lobbyConfig, eventBus);
const soundManager = new SoundManager(eventBus, userSettings);

Comment on lines +103 to 104
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Dispose pre-start SoundManager on early lobby exit.

SoundManager is created at Line 103, but if the user leaves before game start, stop() takes the currentGameRunner === null path and never disposes it. That leaves audio/event resources alive.

Suggested fix
   return {
     stop: (force: boolean = false) => {
       if (!force && currentGameRunner?.shouldPreventWindowClose()) {
         console.log("Player is active, prevent leaving game");
         return false;
       }
       console.log("leaving game");
       if (currentGameRunner) {
         currentGameRunner.stop();
         currentGameRunner = null;
       } else {
+        soundManager.dispose();
         transport.leaveGame();
       }
       return true;
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/ClientGameRunner.ts` around lines 103 - 104, SoundManager is
instantiated at SoundManager(eventBus, userSettings) but if the user leaves the
lobby before a game starts the existing stop() behavior (which checks
currentGameRunner === null) doesn't dispose the SoundManager, leaving
audio/event resources alive; to fix, update the early-lobby-exit path (the
handler that runs when a user leaves before start) to explicitly dispose the
pre-start SoundManager instance—either call a disposal method (e.g.,
soundManager.dispose() or soundManager.destroy()) or modify SoundManager.stop()
to perform full cleanup when currentGameRunner === null; reference the
SoundManager instance created at the top of ClientGameRunner and ensure the same
variable is cleaned up on the early-exit code path so eventBus subscriptions and
audio resources are released.

let currentGameRunner: ClientGameRunner | null = null;

Expand Down Expand Up @@ -130,6 +132,23 @@ export function joinLobby(
if (message.type === "start") {
// Trigger prestart for singleplayer games
resolvePrestart();

eventBus.emit(new PlaySoundEffectEvent("game-start"));

if (
"Notification" in window &&
Notification.permission === "granted" &&
document.hidden &&
userSettings.gameStartNotificationsEnabled()
) {
try {
new Notification(translateText("game_start_notification.title"), {
body: translateText("game_start_notification.body"),
});
} catch (err) {
console.warn("Failed to show game start notification:", err);
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
console.log(
`lobby: game started: ${JSON.stringify(message, replacer, 2)}`,
);
Expand All @@ -144,6 +163,7 @@ export function joinLobby(
eventBus,
transport,
userSettings,
soundManager,
terrainLoad,
terrainMapFileLoader,
)
Expand Down Expand Up @@ -231,12 +251,14 @@ async function createClientGame(
eventBus: EventBus,
transport: Transport,
userSettings: UserSettings,
soundManager: SoundManager,
terrainLoad: Promise<TerrainMapData> | null,
mapLoader: GameMapLoader,
): Promise<ClientGameRunner> {
if (lobbyConfig.gameStartInfo === undefined) {
throw new Error("missing gameStartInfo");
}

const config = new Config(
lobbyConfig.gameStartInfo.config,
userSettings,
Expand Down Expand Up @@ -267,7 +289,6 @@ async function createClientGame(
);

const canvas = createCanvas();
const soundManager = new SoundManager(eventBus, userSettings);
try {
const gameRenderer = createRenderer(
canvas,
Expand Down
10 changes: 10 additions & 0 deletions src/client/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,16 @@ class Client {
return;
}

try {
if ("Notification" in window && Notification.permission === "default") {
Notification.requestPermission().catch(() => {
// Ignore permission request errors
});
}
} catch (err) {
console.warn("Failed to request notification permission:", err);
}

console.log(`joining lobby ${lobby.gameID}`);
if (this.lobbyHandle !== null) {
console.log("joining lobby, stopping existing game");
Expand Down
Loading
Loading