diff --git a/index.html b/index.html index fdd0275..32b6e87 100644 --- a/index.html +++ b/index.html @@ -5,9 +5,86 @@ webstrafe +
+ diff --git a/public/playermodels/counterterrorist.glb b/public/playermodels/counterterrorist.glb index 0fcff2e..cfa6089 100644 Binary files a/public/playermodels/counterterrorist.glb and b/public/playermodels/counterterrorist.glb differ diff --git a/public/playermodels/terrorist.glb b/public/playermodels/terrorist.glb index 433434c..f7b33f3 100644 Binary files a/public/playermodels/terrorist.glb and b/public/playermodels/terrorist.glb differ diff --git a/public/viewmodels/knife/knife.glb b/public/viewmodels/knife/knife.glb index ac8d3c8..d8ecffd 100644 Binary files a/public/viewmodels/knife/knife.glb and b/public/viewmodels/knife/knife.glb differ diff --git a/public/viewmodels/knife/knife_animated.glb b/public/viewmodels/knife/knife_animated.glb index 089d9fe..7692061 100644 Binary files a/public/viewmodels/knife/knife_animated.glb and b/public/viewmodels/knife/knife_animated.glb differ diff --git a/src/app/GameApp.ts b/src/app/GameApp.ts index 8b95aa1..6e6a62c 100644 --- a/src/app/GameApp.ts +++ b/src/app/GameApp.ts @@ -155,6 +155,7 @@ export class GameApp { private localPlayerName = loadPlayerName(); private multiplayerSendAccumulator = 0; private resumeToggleInFlight = false; + private remotePlayersReady: Promise = Promise.resolve(); private readonly tmpForward = new Vector3(); private readonly tmpDesiredCameraPos = new Vector3(); @@ -220,12 +221,15 @@ export class GameApp { listCustomMaps(), this.cosmeticsManager.loadManifest(), ]); - try { - await this.remotePlayers.load(); - } catch (error) { + // The remote player models (~75 MB of GLBs) are only needed once a match + // starts — not for the menu or its character preview, which loads its own + // hero model. Kick the load off in the background so the menu paints + // immediately instead of blocking the first render on 75 MB. Awaited before + // a play session actually enters a map (see startPlaySession). + this.remotePlayersReady = this.remotePlayers.load().catch((error) => { // eslint-disable-next-line no-console console.warn('[Multiplayer] Failed to load remote player models:', error); - } + }); this.rebuildMapSources(builtinMaps, customRecords); this.selectedMapId = builtinMaps.find((map) => map.id === 'surf_skyworld_x')?.id @@ -269,6 +273,7 @@ export class GameApp { savePlayerName(this.localPlayerName); this.menu.setVisible(true); this.setCrosshairVisible(false); + this.dismissBootLoader(); // Pick the transport: Supabase Realtime when configured (serverless deploy), // else the self-hosted WebSocket client (local dev / LAN). @@ -462,6 +467,9 @@ export class GameApp { if (!this.menu) { return; } + // The remote player models were loaded in the background during init; make + // sure they're ready before we drop into a map so other players render. + await this.remotePlayersReady; this.selectedMapId = mapId; if (await this.tryResumeLoadedMap(mapId, 'Could not lock cursor. Press Esc or click Play to resume.')) { return; @@ -1471,6 +1479,16 @@ export class GameApp { return el; } + /** Fades out and removes the instant boot loader painted from index.html. */ + private dismissBootLoader(): void { + const boot = document.getElementById('boot-loader'); + if (!boot) { + return; + } + boot.classList.add('is-hiding'); + window.setTimeout(() => boot.remove(), 360); + } + private createLoadingOverlay(): { root: HTMLDivElement; title: HTMLDivElement; diff --git a/src/multiplayer/playerRig.ts b/src/multiplayer/playerRig.ts index cf28762..ab50e43 100644 --- a/src/multiplayer/playerRig.ts +++ b/src/multiplayer/playerRig.ts @@ -59,14 +59,14 @@ const KNIFE_MODEL_PATH = '/viewmodels/knife/knife.glb'; * Knife child-offset (relative to the right weapon-hand bone) for the static * menu hero pose only. The menu closes the fingers into a fist, so the knife * seats deeper in the palm / finger-curl pocket than the in-game default set in - * {@link attachKnifeModel}. It also slides the knife along its handle so the - * fist grips the balance point just below the guard (rather than pinching the - * butt/pommel end), which leaves far less bare wooden handle exposed between the - * fist and the guard and tucks the pommel behind the fist — reading as a proper - * knife-fight grip. Applied by {@link applyKnifeIdlePose} so the in-game - * third-person hold is never affected. + * {@link attachKnifeModel}. It slides the knife up its handle so the fist grips + * right at the guard (leaving almost no bare wooden handle exposed between the + * fist and the guard) and seats it up into the palm so the pommel tucks under + * the fist rather than dangling below it — reading as a proper hammer-grip + * knife-fight hold from the third-person menu camera. Applied by + * {@link applyKnifeIdlePose} so the in-game third-person hold is never affected. */ -const MENU_KNIFE_GRIP_POSITION = new Vector3(0.0409, 0.0063, 0.0289); +const MENU_KNIFE_GRIP_POSITION = new Vector3(0.0228, 0.0098, 0.0252); const offsetQuat = new Quaternion(); const offsetEuler = new Euler(0, 0, 0, 'XYZ'); diff --git a/src/style.css b/src/style.css index a24bd2f..15d08d1 100644 --- a/src/style.css +++ b/src/style.css @@ -23,9 +23,9 @@ body, margin: 0; overflow: hidden; background: - radial-gradient(1200px 800px at 15% 20%, rgba(89, 224, 195, 0.1), transparent 55%), - radial-gradient(900px 700px at 80% 15%, rgba(255, 194, 109, 0.1), transparent 55%), - linear-gradient(180deg, var(--bg-2), var(--bg-0)); + radial-gradient(120% 80% at 80% 8%, rgba(255, 106, 43, 0.1), transparent 55%), + radial-gradient(90% 70% at 0% 100%, rgba(255, 106, 43, 0.05), transparent 52%), + linear-gradient(180deg, #0a0d12, #050607); color: var(--ink-0); font-family: 'Rajdhani', sans-serif; } @@ -700,7 +700,7 @@ canvas { place-items: center; padding: 20px; pointer-events: none; - background: rgba(5, 11, 18, 0.7); + background: rgba(5, 6, 7, 0.72); backdrop-filter: blur(8px); } @@ -709,9 +709,9 @@ canvas { max-height: 86vh; overflow: auto; border-radius: 12px; - border: 1px solid rgba(160, 201, 223, 0.4); - background: rgba(10, 24, 34, 0.92); - box-shadow: 0 18px 36px rgba(0, 0, 0, 0.35); + border: 1px solid rgba(255, 106, 43, 0.32); + background: rgba(12, 10, 9, 0.94); + box-shadow: 0 18px 36px rgba(0, 0, 0, 0.45); padding: 16px 18px; font-family: 'Space Mono', monospace; } @@ -719,13 +719,13 @@ canvas { .loading-title { font-size: 16px; font-weight: 700; - color: #e9f6ff; + color: #f4f6fa; } .loading-progress { margin-top: 6px; margin-bottom: 10px; - color: var(--accent); + color: #ff6a2b; font-size: 14px; } @@ -733,7 +733,7 @@ canvas { margin: 0; white-space: pre-wrap; line-height: 1.35; - color: #c5d9e8; + color: #b9a99f; font-size: 12px; }