openvibers/source.openvibe.games Commits
Next phase
Changes
@@@ -1,8 +1,2 @@--// OpenVibe: Source client defaults--con_enable 1--developer 1--con_timestamp 1--cl_showfps 0----// These are server-side commands unless a matching client DLL is loaded.--echo "[OpenVibe] autoexec loaded. If ov_join/ov_menu are unknown, the custom client DLL is not loaded."++// OpenVibe autoexec. Safe in both native and Proton modes.++exec openvibe_proton_client.cfgnnew file mode 100644@@@ -0,0 +1,35 @@++// OpenVibe Proton client fallback aliases.++// This file is executed by tools/run-client-proton.sh.++// Proton Windows hl2.exe does not load Linux bin/linux64/client.so, so the++// native OpenVibe client DLL commands are not available in this mode.++++echo "[OpenVibe] Proton fallback cfg loaded. Electron is the custom menu in Proton mode."++++alias ov_help "echo OpenVibe Proton fallback: use ov_join_hub / ov_join_prophunt / ov_join_deathrun / ov_join_fortwars / ov_join_traitortown. Electron remains the full HTML/CSS/JS menu."++alias ov_menu "echo OpenVibe HTML menu is hosted by Electron in Proton mode. Alt-tab to the launcher or run npm run dev."++alias ov_ui "ov_menu"++alias ov_main_menu "ov_menu"++alias ov_menu_main "ov_menu"++alias ov_menu_servers "echo Server browser is in Electron. Quick connects: ov_join_hub, ov_join_prophunt, ov_join_deathrun, ov_join_fortwars, ov_join_traitortown."++alias ov_menu_leaderboard "echo Leaderboard UI is in Electron in Proton mode."++alias ov_menu_inventory "echo Inventory UI is in Electron in Proton mode."++alias ov_menu_shop "echo Shop UI is in Electron in Proton mode."++alias ov_menu_settings "echo Settings UI is in Electron in Proton mode."++alias ov_menu_reload "echo Reload the Electron window with Ctrl+R or restart npm run dev."++alias ov_menu_close "echo Close/minimize the Electron launcher window."++++alias ov_join "echo Usage in Proton fallback: ov_join_hub / ov_join_prophunt / ov_join_deathrun / ov_join_fortwars / ov_join_traitortown. The argument-taking ov_join command needs OpenVibe client.dll/client.so."++alias ov_join_hub "connect 127.0.0.1:27015"++alias ov_join_prophunt "connect 127.0.0.1:27016"++alias ov_join_deathrun "connect 127.0.0.1:27017"++alias ov_join_fortwars "connect 127.0.0.1:27018"++alias ov_join_traitortown "connect 127.0.0.1:27019"++++alias ov_js_status "cmd ov_js_status"++alias ov_js_reload "cmd ov_js_reload"++alias ov_js_cmd "cmd ov_js_cmd"++alias ov_js_fire "cmd ov_js_fire"++++alias ov_prophunt_disguise "cmd ov_prophunt_disguise"++alias ov_prophunt_reset_disguise "cmd ov_prophunt_reset_disguise"++alias ov_fortwars_spawn "cmd ov_fortwars_spawn"nnew file mode 100644@@@ -0,0 +1,14 @@++# OpenVibe loading background++++Source's full-screen loading background is a VTF material, normally:++++- `materials/console/background01.vtf`++- `materials/console/background01_widescreen.vtf`++++This repo includes source art at:++++- `materialsrc/console/openvibe-loading.svg`++++To fully replace the HL2 loading image, convert it to the two VTF filenames above using VTFEdit/VTFCmd or Valve's `vtex`, then place the VTFs in this folder.++++Until those VTF files exist, `resource/LoadingDialog.res` can restyle text/layout, but the old HL2 image may still appear.nnew file mode 100644@@@ -0,0 +1,25 @@++<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080">++ <defs>++ <radialGradient id="g" cx="50%" cy="45%" r="75%">++ <stop offset="0" stop-color="#16334a"/>++ <stop offset="0.45" stop-color="#0b1020"/>++ <stop offset="1" stop-color="#05060b"/>++ </radialGradient>++ <linearGradient id="line" x1="0" x2="1">++ <stop offset="0" stop-color="#00d8ff" stop-opacity="0"/>++ <stop offset="0.5" stop-color="#00d8ff" stop-opacity="1"/>++ <stop offset="1" stop-color="#a855f7" stop-opacity="0"/>++ </linearGradient>++ </defs>++ <rect width="1920" height="1080" fill="url(#g)"/>++ <g opacity="0.18" stroke="#00d8ff" fill="none">++ <path d="M0 770 C300 630 520 890 850 730 C1190 560 1390 810 1920 610"/>++ <path d="M0 830 C330 700 560 960 910 780 C1240 610 1480 860 1920 680"/>++ </g>++ <circle cx="960" cy="450" r="155" fill="none" stroke="#00d8ff" stroke-width="8" opacity="0.8"/>++ <circle cx="960" cy="450" r="112" fill="none" stroke="#a855f7" stroke-width="4" opacity="0.7"/>++ <text x="960" y="465" text-anchor="middle" font-family="Orbitron, Rajdhani, sans-serif" font-size="84" fill="#eaf8ff" font-weight="900">OPENVIBE</text>++ <text x="960" y="530" text-anchor="middle" font-family="Rajdhani, sans-serif" font-size="38" fill="#00d8ff" font-weight="700">SOURCE</text>++ <rect x="660" y="620" width="600" height="6" rx="3" fill="url(#line)"/>++ <text x="960" y="680" text-anchor="middle" font-family="Inter, sans-serif" font-size="28" fill="#b9c7e8">Loading connected game session...</text>++</svg>@@@ -8,45 +8,41 @@"ypos" "0""wide" "f0""tall" "f0"-- "autoResize" "0"-- "pinCorner" "0""visible" "1""enabled" "1"-- "tabPosition" "0"-- "settitlebarvisible" "0"++ "title" "OpenVibe: Source"++ "proportionaltoparent" "1""PaintBackgroundType" "2"-- "bgcolor_override" "7 8 14 255"++ "bgcolor_override" "7 8 14 245"}---- "OpenVibeTitle"++ "LoadingLabel"{"ControlName" "Label"-- "fieldName" "OpenVibeTitle"-- "xpos" "c-260"-- "ypos" "c-72"++ "fieldName" "LoadingLabel"++ "xpos" "cs-260"++ "ypos" "rs1-92""wide" "520"-- "tall" "42"++ "tall" "28""visible" "1""enabled" "1""labelText" "OPENVIBE: SOURCE""textAlignment" "center"-- "font" "DefaultVeryLarge"-- "fgcolor_override" "0 220 255 255"++ "font" "DefaultLarge"++ "fgcolor_override" "0 216 255 255"}---- "OpenVibeStatus"++ "ProgressLabel"{"ControlName" "Label"-- "fieldName" "OpenVibeStatus"-- "xpos" "c-300"-- "ypos" "c-20"-- "wide" "600"-- "tall" "36"++ "fieldName" "ProgressLabel"++ "xpos" "cs-260"++ "ypos" "rs1-58"++ "wide" "520"++ "tall" "24""visible" "1""enabled" "1"-- "labelText" "Loading OpenVibe shell, servers, and scripts..."++ "labelText" "Loading OpenVibe session...""textAlignment" "center"-- "font" "DefaultLarge"-- "fgcolor_override" "220 235 255 255"++ "font" "Default"++ "fgcolor_override" "230 236 255 255"}}@@@ -18,6 +18,63 @@ let gameProcess = null;let uiServerProcess = null;let appIsQuitting = false;++let lastLaunchState = { phase: 'idle', message: 'Ready', pid: null };++++function broadcastLaunchState(state) {++ lastLaunchState = { ...lastLaunchState, ...state, at: Date.now() };++ if (mainWindow && !mainWindow.isDestroyed()) {++ mainWindow.webContents.send('launch-state', lastLaunchState);++ }++}++++function sourceWindowVisible() {++ try {++ const out = execSync('wmctrl -lx 2>/dev/null || true', { encoding: 'utf8' });++ return /OpenVibe: Source|Source SDK Base 2013|hl2\.exe|hl2/i.test(out);++ } catch {++ return false;++ }++}++++function focusSourceWindow() {++ const commands = [++ "wmctrl -a 'OpenVibe: Source'",++ "wmctrl -a 'Source SDK Base 2013'",++ "wmctrl -a 'hl2.exe'",++ ];++ for (const cmd of commands) {++ try {++ execSync(cmd, { stdio: 'ignore' });++ return true;++ } catch {}++ }++ return false;++}++++function waitForStableSourceWindow(timeoutMs = 60000) {++ const started = Date.now();++ let stableTicks = 0;++ return new Promise((resolve) => {++ const tick = () => {++ if (sourceWindowVisible()) stableTicks += 1;++ else stableTicks = 0;++++ broadcastLaunchState({++ phase: stableTicks > 0 ? 'window-detected' : 'starting',++ message: stableTicks > 0++ ? `Source window detected (${stableTicks}/5 stable checks)...`++ : 'Starting Source through Proton...',++ });++++ if (stableTicks >= 5) return resolve(true);++ if (Date.now() - started >= timeoutMs) return resolve(false);++ setTimeout(tick, 1000);++ };++ tick();++ });++}++++const GAME_WINDOW_TITLE = process.env.OPENVIBE_GAME_WINDOW_TITLE || 'OpenVibe: Source';const GAME_READY_TIMEOUT_MS = Number(process.env.OPENVIBE_GAME_WINDOW_READY_TIMEOUT_MS || 45000);@@@ -25,6 +25,7 @@ contextBridge.exposeInMainWorld('OV', {// Events from main → rendereronGameStart: (cb) => ipcRenderer.on('game-started', (_e, pid) => cb(pid)),++ onLaunchState: (cb) => ipcRenderer.on('launch-state', (_e, state) => cb(state)),onLaunchPhase: (cb) => ipcRenderer.on('game-launch-phase', (_e, info) => cb(info)),onGameExit: (cb) => ipcRenderer.on('game-exited', (_e, code) => cb(code)),onRoute: (cb) => ipcRenderer.on('ui:set-route', (_e, route) => cb(route)),@@@ -359,3 +359,20 @@ async function init() {}init();++++++// Launcher-aware Source startup status. This keeps Electron useful while Proton++// shows the default Source loading window and before a native client DLL exists.++if (isElectron && window.OV.onLaunchState) {++ window.OV.onLaunchState((state) => {++ const label = document.getElementById('launch-label');++ const sub = document.getElementById('launch-sub');++ if (label && state?.message) label.textContent = state.message;++ if (sub) {++ sub.textContent = state?.phase === 'ready'++ ? 'Game window is ready. Use Focus Game Window, or keep Electron open as the custom menu.'++ : 'Electron remains open so you are not left staring at a frozen/default Source loading screen.';++ }++ if (state?.phase && state.phase !== 'idle') launchOverlay?.classList.add('show');++ });++}@@@ -771,3 +771,26 @@ html.embedded .hero-sub {flex-wrap: wrap;margin-top: 18px;}++++/* OpenVibe launcher-aware Source loading UX */++.launch-focus {++ margin-top: 10px;++ border: 1px solid rgba(0, 216, 255, 0.55);++ background: rgba(0, 216, 255, 0.14);++ color: #eaf8ff;++ border-radius: 10px;++ padding: 9px 16px;++ font-weight: 700;++ cursor: pointer;++}++.launch-focus:hover {++ background: rgba(0, 216, 255, 0.22);++}++.launch-sub {++ margin-top: 10px;++ max-width: 560px;++ color: rgba(230, 236, 255, 0.76);++ font-size: 13px;++ line-height: 1.35;++ text-align: center;++}@@@ -1,27 +1,35 @@#!/usr/bin/env bashset -euo pipefailROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"++cd "$ROOT"echo "[openvibe] client mode diagnostic"echo "Windows/Proton launcher: $ROOT/tools/run-client-proton.sh"--echo "Linux client module expected at: $ROOT/game/openvibe.games/bin/linux64/client.so"++echo "Linux client module: $ROOT/game/openvibe.games/bin/linux64/client.so"++echo "Fallback aliases: $ROOT/game/openvibe.games/cfg/openvibe_proton_client.cfg"++echo++[[ -f game/openvibe.games/bin/linux64/client.so ]] && echo "[ok] Linux client.so exists" || echo "[missing] Linux client.so missing"++[[ -f game/openvibe.games/cfg/openvibe_proton_client.cfg ]] && echo "[ok] Proton fallback aliases exist" || echo "[missing] Proton fallback aliases missing"--if [[ -f "$ROOT/game/openvibe.games/bin/linux64/client.so" ]]; then-- echo "[ok] Linux client.so exists"--else-- echo "[warn] Linux client.so missing; run tools/build-sdk-linux.sh and tools/setup-openvibe-bin.sh"--fi++echo++cat <<'TXT'++Important:++ Proton Windows hl2.exe loads Windows client.dll, not Linux bin/linux64/client.so.++ If the console says unknown command ov_join/ov_menu, the OpenVibe client DLL is not loaded.--cat <<'MSG'++Current reliable custom UI:++ Electron launcher + local Chromium UI.--If the in-game console says unknown command "ov_join" or "ov_menu", the OpenVibe client DLL is not loaded.--That is expected when launching Windows hl2.exe through Proton while only Linux client.so exists.++Current in-game fallback:++ autoexec.cfg loads openvibe_proton_client.cfg aliases.++ Try: ov_help, ov_join_hub, ov_join_prophunt, ov_join_deathrun.--Current reliable UI path:-- - Electron launcher / local Chromium UI-- - server-side GameDLL commands++Full in-game HTML/CSS/JS menu requires:++ 1) native Linux Source client loading bin/linux64/client.so, or++ 2) a Windows client.dll build for Proton hl2.exe.--Full in-game HTML menu requires one of:-- 1. Run a native Linux Source client that loads bin/linux64/client.so, or-- 2. Build a Windows client.dll/server.dll and place them in game/openvibe.games/bin/ for Proton hl2.exe.--MSG++Full loading image replacement:++ Convert materialsrc/console/openvibe-loading.svg to:++ materials/console/background01.vtf++ materials/console/background01_widescreen.vtf++TXTnnew file mode 100755@@@ -0,0 +1,396 @@++#!/usr/bin/env bash++set -euo pipefail++++ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"++cd "$ROOT"++++echo "[openvibe] next phase: Proton-aware loading UX + fallback menu aliases"++echo "[openvibe] root=$ROOT"++++mkdir -p game/openvibe.games/cfg game/openvibe.games/resource game/openvibe.games/materialsrc/console game/openvibe.games/materials/console launcher++++# -----------------------------------------------------------------------------++# 1) Proton/client fallback aliases.++# Windows hl2.exe cannot load the Linux client.so, so these aliases prevent++# confusing Unknown command spam and keep raw connect flows usable.++# -----------------------------------------------------------------------------++cat > game/openvibe.games/cfg/openvibe_proton_client.cfg <<'CFG'++// OpenVibe Proton client fallback aliases.++// This file is executed by tools/run-client-proton.sh.++// Proton Windows hl2.exe does not load Linux bin/linux64/client.so, so the++// native OpenVibe client DLL commands are not available in this mode.++++echo "[OpenVibe] Proton fallback cfg loaded. Electron is the custom menu in Proton mode."++++alias ov_help "echo OpenVibe Proton fallback: use ov_join_hub / ov_join_prophunt / ov_join_deathrun / ov_join_fortwars / ov_join_traitortown. Electron remains the full HTML/CSS/JS menu."++alias ov_menu "echo OpenVibe HTML menu is hosted by Electron in Proton mode. Alt-tab to the launcher or run npm run dev."++alias ov_ui "ov_menu"++alias ov_main_menu "ov_menu"++alias ov_menu_main "ov_menu"++alias ov_menu_servers "echo Server browser is in Electron. Quick connects: ov_join_hub, ov_join_prophunt, ov_join_deathrun, ov_join_fortwars, ov_join_traitortown."++alias ov_menu_leaderboard "echo Leaderboard UI is in Electron in Proton mode."++alias ov_menu_inventory "echo Inventory UI is in Electron in Proton mode."++alias ov_menu_shop "echo Shop UI is in Electron in Proton mode."++alias ov_menu_settings "echo Settings UI is in Electron in Proton mode."++alias ov_menu_reload "echo Reload the Electron window with Ctrl+R or restart npm run dev."++alias ov_menu_close "echo Close/minimize the Electron launcher window."++++alias ov_join "echo Usage in Proton fallback: ov_join_hub / ov_join_prophunt / ov_join_deathrun / ov_join_fortwars / ov_join_traitortown. The argument-taking ov_join command needs OpenVibe client.dll/client.so."++alias ov_join_hub "connect 127.0.0.1:27015"++alias ov_join_prophunt "connect 127.0.0.1:27016"++alias ov_join_deathrun "connect 127.0.0.1:27017"++alias ov_join_fortwars "connect 127.0.0.1:27018"++alias ov_join_traitortown "connect 127.0.0.1:27019"++++alias ov_js_status "cmd ov_js_status"++alias ov_js_reload "cmd ov_js_reload"++alias ov_js_cmd "cmd ov_js_cmd"++alias ov_js_fire "cmd ov_js_fire"++++alias ov_prophunt_disguise "cmd ov_prophunt_disguise"++alias ov_prophunt_reset_disguise "cmd ov_prophunt_reset_disguise"++alias ov_fortwars_spawn "cmd ov_fortwars_spawn"++CFG++++cat > game/openvibe.games/cfg/autoexec.cfg <<'CFG'++// OpenVibe autoexec. Safe in both native and Proton modes.++exec openvibe_proton_client.cfg++CFG++++# Patch Proton launcher to exec aliases early.++python3 <<'PY'++from pathlib import Path++p = Path('tools/run-client-proton.sh')++s = p.read_text()++if '+exec openvibe_proton_client.cfg' not in s:++ s = s.replace(++ '-console -dev -novid -sw -w 1280 -h 720 \\\n',++ '-console -dev -novid -sw -w 1280 -h 720 +exec openvibe_proton_client.cfg \\\n'++ )++if 'OPENVIBE_PROTON_FALLBACK' not in s:++ s = s.replace('export PROTON_LOG=0\n', 'export PROTON_LOG=0\nexport OPENVIBE_PROTON_FALLBACK=1\n')++p.write_text(s)++PY++chmod +x tools/run-client-proton.sh++++# -----------------------------------------------------------------------------++# 2) Best-effort loading dialog override resources.++# This won't replace the HL2 background material until VTF files are supplied,++# but it customizes text/layout and adds docs + source art placeholders.++# -----------------------------------------------------------------------------++cat > game/openvibe.games/resource/LoadingDialog.res <<'RES'++"Resource/LoadingDialog.res"++{++ "LoadingDialog"++ {++ "ControlName" "Frame"++ "fieldName" "LoadingDialog"++ "xpos" "0"++ "ypos" "0"++ "wide" "f0"++ "tall" "f0"++ "visible" "1"++ "enabled" "1"++ "title" "OpenVibe: Source"++ "proportionaltoparent" "1"++ "PaintBackgroundType" "2"++ "bgcolor_override" "7 8 14 245"++ }++ "LoadingLabel"++ {++ "ControlName" "Label"++ "fieldName" "LoadingLabel"++ "xpos" "cs-260"++ "ypos" "rs1-92"++ "wide" "520"++ "tall" "28"++ "visible" "1"++ "enabled" "1"++ "labelText" "OPENVIBE: SOURCE"++ "textAlignment" "center"++ "font" "DefaultLarge"++ "fgcolor_override" "0 216 255 255"++ }++ "ProgressLabel"++ {++ "ControlName" "Label"++ "fieldName" "ProgressLabel"++ "xpos" "cs-260"++ "ypos" "rs1-58"++ "wide" "520"++ "tall" "24"++ "visible" "1"++ "enabled" "1"++ "labelText" "Loading OpenVibe session..."++ "textAlignment" "center"++ "font" "Default"++ "fgcolor_override" "230 236 255 255"++ }++}++RES++++cat > game/openvibe.games/materialsrc/console/openvibe-loading.svg <<'SVG'++<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080">++ <defs>++ <radialGradient id="g" cx="50%" cy="45%" r="75%">++ <stop offset="0" stop-color="#16334a"/>++ <stop offset="0.45" stop-color="#0b1020"/>++ <stop offset="1" stop-color="#05060b"/>++ </radialGradient>++ <linearGradient id="line" x1="0" x2="1">++ <stop offset="0" stop-color="#00d8ff" stop-opacity="0"/>++ <stop offset="0.5" stop-color="#00d8ff" stop-opacity="1"/>++ <stop offset="1" stop-color="#a855f7" stop-opacity="0"/>++ </linearGradient>++ </defs>++ <rect width="1920" height="1080" fill="url(#g)"/>++ <g opacity="0.18" stroke="#00d8ff" fill="none">++ <path d="M0 770 C300 630 520 890 850 730 C1190 560 1390 810 1920 610"/>++ <path d="M0 830 C330 700 560 960 910 780 C1240 610 1480 860 1920 680"/>++ </g>++ <circle cx="960" cy="450" r="155" fill="none" stroke="#00d8ff" stroke-width="8" opacity="0.8"/>++ <circle cx="960" cy="450" r="112" fill="none" stroke="#a855f7" stroke-width="4" opacity="0.7"/>++ <text x="960" y="465" text-anchor="middle" font-family="Orbitron, Rajdhani, sans-serif" font-size="84" fill="#eaf8ff" font-weight="900">OPENVIBE</text>++ <text x="960" y="530" text-anchor="middle" font-family="Rajdhani, sans-serif" font-size="38" fill="#00d8ff" font-weight="700">SOURCE</text>++ <rect x="660" y="620" width="600" height="6" rx="3" fill="url(#line)"/>++ <text x="960" y="680" text-anchor="middle" font-family="Inter, sans-serif" font-size="28" fill="#b9c7e8">Loading connected game session...</text>++</svg>++SVG++++cat > game/openvibe.games/materials/console/README.md <<'MD'++# OpenVibe loading background++++Source's full-screen loading background is a VTF material, normally:++++- `materials/console/background01.vtf`++- `materials/console/background01_widescreen.vtf`++++This repo includes source art at:++++- `materialsrc/console/openvibe-loading.svg`++++To fully replace the HL2 loading image, convert it to the two VTF filenames above using VTFEdit/VTFCmd or Valve's `vtex`, then place the VTFs in this folder.++++Until those VTF files exist, `resource/LoadingDialog.res` can restyle text/layout, but the old HL2 image may still appear.++MD++++# -----------------------------------------------------------------------------++# 3) Electron launch UX: keep launcher visible, expose focus button, and only++# hide when Source window is stable if env var opts in.++# -----------------------------------------------------------------------------++python3 <<'PY'++from pathlib import Path++p = Path('launcher/main.js')++s = p.read_text()++++if 'function sourceWindowVisible' not in s:++ insert_after = "let appIsQuitting = false;\n"++ block = r'''++let lastLaunchState = { phase: 'idle', message: 'Ready', pid: null };++++function broadcastLaunchState(state) {++ lastLaunchState = { ...lastLaunchState, ...state, at: Date.now() };++ if (mainWindow && !mainWindow.isDestroyed()) {++ mainWindow.webContents.send('launch-state', lastLaunchState);++ }++}++++function sourceWindowVisible() {++ try {++ const out = execSync('wmctrl -lx 2>/dev/null || true', { encoding: 'utf8' });++ return /OpenVibe: Source|Source SDK Base 2013|hl2\.exe|hl2/i.test(out);++ } catch {++ return false;++ }++}++++function focusSourceWindow() {++ const commands = [++ "wmctrl -a 'OpenVibe: Source'",++ "wmctrl -a 'Source SDK Base 2013'",++ "wmctrl -a 'hl2.exe'",++ ];++ for (const cmd of commands) {++ try {++ execSync(cmd, { stdio: 'ignore' });++ return true;++ } catch {}++ }++ return false;++}++++function waitForStableSourceWindow(timeoutMs = 60000) {++ const started = Date.now();++ let stableTicks = 0;++ return new Promise((resolve) => {++ const tick = () => {++ if (sourceWindowVisible()) stableTicks += 1;++ else stableTicks = 0;++++ broadcastLaunchState({++ phase: stableTicks > 0 ? 'window-detected' : 'starting',++ message: stableTicks > 0++ ? `Source window detected (${stableTicks}/5 stable checks)...`++ : 'Starting Source through Proton...',++ });++++ if (stableTicks >= 5) return resolve(true);++ if (Date.now() - started >= timeoutMs) return resolve(false);++ setTimeout(tick, 1000);++ };++ tick();++ });++}++'''++ s = s.replace(insert_after, insert_after + block + "\n")++++old = """ mainWindow?.webContents.send('game-started', gameProcess.pid);\n setTimeout(() => {\n if (gameProcess && mainWindow && !mainWindow.isDestroyed()) mainWindow.hide();\n }, 750);\n"""++new = """ mainWindow?.webContents.send('game-started', gameProcess.pid);\n broadcastLaunchState({\n phase: 'spawned',\n pid: gameProcess.pid,\n message: 'Source spawned. Keeping launcher visible until the game window is stable.',\n });\n\n waitForStableSourceWindow().then((ready) => {\n broadcastLaunchState({\n phase: ready ? 'ready' : 'timeout',\n pid: gameProcess ? gameProcess.pid : null,\n message: ready\n ? 'Source window appears stable. You can focus the game now.'\n : 'Source is still not confirmed stable. Launcher stayed visible to avoid confusion.',\n });\n\n const shouldAutoHide = process.env.OPENVIBE_HIDE_LAUNCHER_ON_GAME_READY === '1';\n if (ready && shouldAutoHide && gameProcess && mainWindow && !mainWindow.isDestroyed()) {\n mainWindow.hide();\n focusSourceWindow();\n }\n });\n"""++if old in s:++ s = s.replace(old, new)++++if "ipcMain.handle('game:focus'" not in s:++ s = s.replace("ipcMain.handle('game:status', () => ({\n running: gameProcess !== null,\n pid: gameProcess?.pid ?? null,\n}));\n",++ "ipcMain.handle('game:status', () => ({\n running: gameProcess !== null,\n pid: gameProcess?.pid ?? null,\n launchState: lastLaunchState,\n}));\n\nipcMain.handle('game:focus', async () => focusSourceWindow());\n")++++p.write_text(s)++PY++++python3 <<'PY'++from pathlib import Path++p = Path('launcher/preload.js')++s = p.read_text()++if "focusGame" not in s:++ s = s.replace("gameStatus: () => ipcRenderer.invoke('game:status'),",++ "gameStatus: () => ipcRenderer.invoke('game:status'),\n focusGame: () => ipcRenderer.invoke('game:focus'),")++if "onLaunchState" not in s:++ s = s.replace("onGameStart: (cb) => ipcRenderer.on('game-started', (_e, pid) => cb(pid)),",++ "onGameStart: (cb) => ipcRenderer.on('game-started', (_e, pid) => cb(pid)),\n onLaunchState: (cb) => ipcRenderer.on('launch-state', (_e, state) => cb(state)),")++p.write_text(s)++PY++++python3 <<'PY'++from pathlib import Path++p = Path('launcher/renderer.js')++s = p.read_text()++if 'focusGame:' not in s:++ s = s.replace("gameStatus: () => isElectron ? window.OV.gameStatus() : Promise.resolve({ running: true, pid: null }),",++ "gameStatus: () => isElectron ? window.OV.gameStatus() : Promise.resolve({ running: true, pid: null }),\n focusGame: () => isElectron ? window.OV.focusGame() : Promise.resolve(false),")++if 'launch-focus' not in s:++ s = s.replace("document.getElementById('launch-cancel')?.addEventListener('click', () => {\n launchOverlay.classList.remove('show');\n});",++ "document.getElementById('launch-cancel')?.addEventListener('click', () => {\n launchOverlay.classList.remove('show');\n});\n\ndocument.getElementById('launch-focus')?.addEventListener('click', async () => {\n const ok = await Bridge.focusGame();\n toast(ok ? 'Focused Source window.' : 'Could not find Source window yet.', !ok);\n});")++if 'onLaunchState' not in s:++ s += r'''++++// Launcher-aware Source startup status. This keeps Electron useful while Proton++// shows the default Source loading window and before a native client DLL exists.++if (isElectron && window.OV.onLaunchState) {++ window.OV.onLaunchState((state) => {++ const label = document.getElementById('launch-label');++ const sub = document.getElementById('launch-sub');++ if (label && state?.message) label.textContent = state.message;++ if (sub) {++ sub.textContent = state?.phase === 'ready'++ ? 'Game window is ready. Use Focus Game Window, or keep Electron open as the custom menu.'++ : 'Electron remains open so you are not left staring at a frozen/default Source loading screen.';++ }++ if (state?.phase && state.phase !== 'idle') launchOverlay?.classList.add('show');++ });++}++'''++p.write_text(s)++PY++++python3 <<'PY'++from pathlib import Path++p = Path('launcher/index.html')++s = p.read_text()++if 'id="launch-focus"' not in s:++ s = s.replace(' <button class="launch-cancel" id="launch-cancel">Cancel</button>\n',++ ' <button class="launch-cancel" id="launch-cancel">Cancel</button>\n <button class="launch-focus" id="launch-focus">Focus Game Window</button>\n <div class="launch-sub" id="launch-sub">Electron stays visible until Source is stable.</div>\n')++p.write_text(s)++PY++++cat >> launcher/styles.css <<'CSS'++++/* OpenVibe launcher-aware Source loading UX */++.launch-focus {++ margin-top: 10px;++ border: 1px solid rgba(0, 216, 255, 0.55);++ background: rgba(0, 216, 255, 0.14);++ color: #eaf8ff;++ border-radius: 10px;++ padding: 9px 16px;++ font-weight: 700;++ cursor: pointer;++}++.launch-focus:hover {++ background: rgba(0, 216, 255, 0.22);++}++.launch-sub {++ margin-top: 10px;++ max-width: 560px;++ color: rgba(230, 236, 255, 0.76);++ font-size: 13px;++ line-height: 1.35;++ text-align: center;++}++CSS++++# -----------------------------------------------------------------------------++# 4) Diagnostic.++# -----------------------------------------------------------------------------++cat > tools/check-openvibe-client-mode.sh <<'SH'++#!/usr/bin/env bash++set -euo pipefail++ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"++cd "$ROOT"++++echo "[openvibe] client mode diagnostic"++echo "Windows/Proton launcher: $ROOT/tools/run-client-proton.sh"++echo "Linux client module: $ROOT/game/openvibe.games/bin/linux64/client.so"++echo "Fallback aliases: $ROOT/game/openvibe.games/cfg/openvibe_proton_client.cfg"++echo++[[ -f game/openvibe.games/bin/linux64/client.so ]] && echo "[ok] Linux client.so exists" || echo "[missing] Linux client.so missing"++[[ -f game/openvibe.games/cfg/openvibe_proton_client.cfg ]] && echo "[ok] Proton fallback aliases exist" || echo "[missing] Proton fallback aliases missing"++++echo++cat <<'TXT'++Important:++ Proton Windows hl2.exe loads Windows client.dll, not Linux bin/linux64/client.so.++ If the console says unknown command ov_join/ov_menu, the OpenVibe client DLL is not loaded.++++Current reliable custom UI:++ Electron launcher + local Chromium UI.++++Current in-game fallback:++ autoexec.cfg loads openvibe_proton_client.cfg aliases.++ Try: ov_help, ov_join_hub, ov_join_prophunt, ov_join_deathrun.++++Full in-game HTML/CSS/JS menu requires:++ 1) native Linux Source client loading bin/linux64/client.so, or++ 2) a Windows client.dll build for Proton hl2.exe.++++Full loading image replacement:++ Convert materialsrc/console/openvibe-loading.svg to:++ materials/console/background01.vtf++ materials/console/background01_widescreen.vtf++TXT++SH++chmod +x tools/check-openvibe-client-mode.sh++++# Keep setup links current, if build exists.++if [[ -x tools/setup-openvibe-bin.sh ]]; then++ tools/setup-openvibe-bin.sh || true++fi++++echo++echo "[openvibe] done. Re-test:"++echo " tools/check-openvibe-client-mode.sh"++echo " cd launcher && npm run dev"++echo++echo "In Proton client console, try:"++echo " exec openvibe_proton_client.cfg"++echo " ov_help"++echo " ov_join_hub"++echo " ov_menu_servers"@@@ -40,6 +40,7 @@ export STEAM_COMPAT_CLIENT_INSTALL_PATH="$STEAM_PATH"export STEAM_COMPAT_DATA_PATH="$STEAM_COMPAT_DATA"export SteamAppId=243750export PROTON_LOG=0++export OPENVIBE_PROTON_FALLBACK=1export DXVK_ASYNC=1exec "$GE_PROTON/proton" waitforexitandrun \
@@ -1,8 +1,2 @@
@@ -1,8 +1,2 @@
-// OpenVibe: Source client defaults
-con_enable 1
-developer 1
-con_timestamp 1
-cl_showfps 0
-
-// These are server-side commands unless a matching client DLL is loaded.
-echo "[OpenVibe] autoexec loaded. If ov_join/ov_menu are unknown, the custom client DLL is not loaded."
+// OpenVibe autoexec. Safe in both native and Proton modes.
+exec openvibe_proton_client.cfg
new file mode 100644
new file mode 100644
@@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
+// OpenVibe Proton client fallback aliases.
+// This file is executed by tools/run-client-proton.sh.
+// Proton Windows hl2.exe does not load Linux bin/linux64/client.so, so the
+// native OpenVibe client DLL commands are not available in this mode.
+
+echo "[OpenVibe] Proton fallback cfg loaded. Electron is the custom menu in Proton mode."
+
+alias ov_help "echo OpenVibe Proton fallback: use ov_join_hub / ov_join_prophunt / ov_join_deathrun / ov_join_fortwars / ov_join_traitortown. Electron remains the full HTML/CSS/JS menu."
+alias ov_menu "echo OpenVibe HTML menu is hosted by Electron in Proton mode. Alt-tab to the launcher or run npm run dev."
+alias ov_ui "ov_menu"
+alias ov_main_menu "ov_menu"
+alias ov_menu_main "ov_menu"
+alias ov_menu_servers "echo Server browser is in Electron. Quick connects: ov_join_hub, ov_join_prophunt, ov_join_deathrun, ov_join_fortwars, ov_join_traitortown."
+alias ov_menu_leaderboard "echo Leaderboard UI is in Electron in Proton mode."
+alias ov_menu_inventory "echo Inventory UI is in Electron in Proton mode."
+alias ov_menu_shop "echo Shop UI is in Electron in Proton mode."
+alias ov_menu_settings "echo Settings UI is in Electron in Proton mode."
+alias ov_menu_reload "echo Reload the Electron window with Ctrl+R or restart npm run dev."
+alias ov_menu_close "echo Close/minimize the Electron launcher window."
+
+alias ov_join "echo Usage in Proton fallback: ov_join_hub / ov_join_prophunt / ov_join_deathrun / ov_join_fortwars / ov_join_traitortown. The argument-taking ov_join command needs OpenVibe client.dll/client.so."
+alias ov_join_hub "connect 127.0.0.1:27015"
+alias ov_join_prophunt "connect 127.0.0.1:27016"
+alias ov_join_deathrun "connect 127.0.0.1:27017"
+alias ov_join_fortwars "connect 127.0.0.1:27018"
+alias ov_join_traitortown "connect 127.0.0.1:27019"
+
+alias ov_js_status "cmd ov_js_status"
+alias ov_js_reload "cmd ov_js_reload"
+alias ov_js_cmd "cmd ov_js_cmd"
+alias ov_js_fire "cmd ov_js_fire"
+
+alias ov_prophunt_disguise "cmd ov_prophunt_disguise"
+alias ov_prophunt_reset_disguise "cmd ov_prophunt_reset_disguise"
+alias ov_fortwars_spawn "cmd ov_fortwars_spawn"
new file mode 100644
new file mode 100644
@@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
+# OpenVibe loading background
+
+Source's full-screen loading background is a VTF material, normally:
+
+- `materials/console/background01.vtf`
+- `materials/console/background01_widescreen.vtf`
+
+This repo includes source art at:
+
+- `materialsrc/console/openvibe-loading.svg`
+
+To fully replace the HL2 loading image, convert it to the two VTF filenames above using VTFEdit/VTFCmd or Valve's `vtex`, then place the VTFs in this folder.
+
+Until those VTF files exist, `resource/LoadingDialog.res` can restyle text/layout, but the old HL2 image may still appear.
new file mode 100644
new file mode 100644
@@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080">
+ <defs>
+ <radialGradient id="g" cx="50%" cy="45%" r="75%">
+ <stop offset="0" stop-color="#16334a"/>
+ <stop offset="0.45" stop-color="#0b1020"/>
+ <stop offset="1" stop-color="#05060b"/>
+ </radialGradient>
+ <linearGradient id="line" x1="0" x2="1">
+ <stop offset="0" stop-color="#00d8ff" stop-opacity="0"/>
+ <stop offset="0.5" stop-color="#00d8ff" stop-opacity="1"/>
+ <stop offset="1" stop-color="#a855f7" stop-opacity="0"/>
+ </linearGradient>
+ </defs>
+ <rect width="1920" height="1080" fill="url(#g)"/>
+ <g opacity="0.18" stroke="#00d8ff" fill="none">
+ <path d="M0 770 C300 630 520 890 850 730 C1190 560 1390 810 1920 610"/>
+ <path d="M0 830 C330 700 560 960 910 780 C1240 610 1480 860 1920 680"/>
+ </g>
+ <circle cx="960" cy="450" r="155" fill="none" stroke="#00d8ff" stroke-width="8" opacity="0.8"/>
+ <circle cx="960" cy="450" r="112" fill="none" stroke="#a855f7" stroke-width="4" opacity="0.7"/>
+ <text x="960" y="465" text-anchor="middle" font-family="Orbitron, Rajdhani, sans-serif" font-size="84" fill="#eaf8ff" font-weight="900">OPENVIBE</text>
+ <text x="960" y="530" text-anchor="middle" font-family="Rajdhani, sans-serif" font-size="38" fill="#00d8ff" font-weight="700">SOURCE</text>
+ <rect x="660" y="620" width="600" height="6" rx="3" fill="url(#line)"/>
+ <text x="960" y="680" text-anchor="middle" font-family="Inter, sans-serif" font-size="28" fill="#b9c7e8">Loading connected game session...</text>
+</svg>
@@ -8,45 +8,41 @@
@@ -8,45 +8,41 @@
"ypos" "0"
"ypos" "0"
"wide" "f0"
"wide" "f0"
"tall" "f0"
"tall" "f0"
- "autoResize" "0"
- "pinCorner" "0"
"visible" "1"
"visible" "1"
"enabled" "1"
"enabled" "1"
- "tabPosition" "0"
- "settitlebarvisible" "0"
+ "title" "OpenVibe: Source"
+ "proportionaltoparent" "1"
"PaintBackgroundType" "2"
"PaintBackgroundType" "2"
- "bgcolor_override" "7 8 14 255"
+ "bgcolor_override" "7 8 14 245"
}
}
-
- "OpenVibeTitle"
+ "LoadingLabel"
{
{
"ControlName" "Label"
"ControlName" "Label"
- "fieldName" "OpenVibeTitle"
- "xpos" "c-260"
- "ypos" "c-72"
+ "fieldName" "LoadingLabel"
+ "xpos" "cs-260"
+ "ypos" "rs1-92"
"wide" "520"
"wide" "520"
- "tall" "42"
+ "tall" "28"
"visible" "1"
"visible" "1"
"enabled" "1"
"enabled" "1"
"labelText" "OPENVIBE: SOURCE"
"labelText" "OPENVIBE: SOURCE"
"textAlignment" "center"
"textAlignment" "center"
- "font" "DefaultVeryLarge"
- "fgcolor_override" "0 220 255 255"
+ "font" "DefaultLarge"
+ "fgcolor_override" "0 216 255 255"
}
}
-
- "OpenVibeStatus"
+ "ProgressLabel"
{
{
"ControlName" "Label"
"ControlName" "Label"
- "fieldName" "OpenVibeStatus"
- "xpos" "c-300"
- "ypos" "c-20"
- "wide" "600"
- "tall" "36"
+ "fieldName" "ProgressLabel"
+ "xpos" "cs-260"
+ "ypos" "rs1-58"
+ "wide" "520"
+ "tall" "24"
"visible" "1"
"visible" "1"
"enabled" "1"
"enabled" "1"
- "labelText" "Loading OpenVibe shell, servers, and scripts..."
+ "labelText" "Loading OpenVibe session..."
"textAlignment" "center"
"textAlignment" "center"
- "font" "DefaultLarge"
- "fgcolor_override" "220 235 255 255"
+ "font" "Default"
+ "fgcolor_override" "230 236 255 255"
}
}
}
}
@@ -18,6 +18,63 @@ let gameProcess = null;
@@ -18,6 +18,63 @@ let gameProcess = null;
let uiServerProcess = null;
let uiServerProcess = null;
let appIsQuitting = false;
let appIsQuitting = false;
+let lastLaunchState = { phase: 'idle', message: 'Ready', pid: null };
+
+function broadcastLaunchState(state) {
+ lastLaunchState = { ...lastLaunchState, ...state, at: Date.now() };
+ if (mainWindow && !mainWindow.isDestroyed()) {
+ mainWindow.webContents.send('launch-state', lastLaunchState);
+ }
+}
+
+function sourceWindowVisible() {
+ try {
+ const out = execSync('wmctrl -lx 2>/dev/null || true', { encoding: 'utf8' });
+ return /OpenVibe: Source|Source SDK Base 2013|hl2\.exe|hl2/i.test(out);
+ } catch {
+ return false;
+ }
+}
+
+function focusSourceWindow() {
+ const commands = [
+ "wmctrl -a 'OpenVibe: Source'",
+ "wmctrl -a 'Source SDK Base 2013'",
+ "wmctrl -a 'hl2.exe'",
+ ];
+ for (const cmd of commands) {
+ try {
+ execSync(cmd, { stdio: 'ignore' });
+ return true;
+ } catch {}
+ }
+ return false;
+}
+
+function waitForStableSourceWindow(timeoutMs = 60000) {
+ const started = Date.now();
+ let stableTicks = 0;
+ return new Promise((resolve) => {
+ const tick = () => {
+ if (sourceWindowVisible()) stableTicks += 1;
+ else stableTicks = 0;
+
+ broadcastLaunchState({
+ phase: stableTicks > 0 ? 'window-detected' : 'starting',
+ message: stableTicks > 0
+ ? `Source window detected (${stableTicks}/5 stable checks)...`
+ : 'Starting Source through Proton...',
+ });
+
+ if (stableTicks >= 5) return resolve(true);
+ if (Date.now() - started >= timeoutMs) return resolve(false);
+ setTimeout(tick, 1000);
+ };
+ tick();
+ });
+}
+
+
const GAME_WINDOW_TITLE = process.env.OPENVIBE_GAME_WINDOW_TITLE || 'OpenVibe: Source';
const GAME_WINDOW_TITLE = process.env.OPENVIBE_GAME_WINDOW_TITLE || 'OpenVibe: Source';
const GAME_READY_TIMEOUT_MS = Number(process.env.OPENVIBE_GAME_WINDOW_READY_TIMEOUT_MS || 45000);
const GAME_READY_TIMEOUT_MS = Number(process.env.OPENVIBE_GAME_WINDOW_READY_TIMEOUT_MS || 45000);
@@ -25,6 +25,7 @@ contextBridge.exposeInMainWorld('OV', {
@@ -25,6 +25,7 @@ contextBridge.exposeInMainWorld('OV', {
// Events from main → renderer
// Events from main → renderer
onGameStart: (cb) => ipcRenderer.on('game-started', (_e, pid) => cb(pid)),
onGameStart: (cb) => ipcRenderer.on('game-started', (_e, pid) => cb(pid)),
+ onLaunchState: (cb) => ipcRenderer.on('launch-state', (_e, state) => cb(state)),
onLaunchPhase: (cb) => ipcRenderer.on('game-launch-phase', (_e, info) => cb(info)),
onLaunchPhase: (cb) => ipcRenderer.on('game-launch-phase', (_e, info) => cb(info)),
onGameExit: (cb) => ipcRenderer.on('game-exited', (_e, code) => cb(code)),
onGameExit: (cb) => ipcRenderer.on('game-exited', (_e, code) => cb(code)),
onRoute: (cb) => ipcRenderer.on('ui:set-route', (_e, route) => cb(route)),
onRoute: (cb) => ipcRenderer.on('ui:set-route', (_e, route) => cb(route)),
@@ -359,3 +359,20 @@ async function init() {
@@ -359,3 +359,20 @@ async function init() {
}
}
init();
init();
+
+
+// Launcher-aware Source startup status. This keeps Electron useful while Proton
+// shows the default Source loading window and before a native client DLL exists.
+if (isElectron && window.OV.onLaunchState) {
+ window.OV.onLaunchState((state) => {
+ const label = document.getElementById('launch-label');
+ const sub = document.getElementById('launch-sub');
+ if (label && state?.message) label.textContent = state.message;
+ if (sub) {
+ sub.textContent = state?.phase === 'ready'
+ ? 'Game window is ready. Use Focus Game Window, or keep Electron open as the custom menu.'
+ : 'Electron remains open so you are not left staring at a frozen/default Source loading screen.';
+ }
+ if (state?.phase && state.phase !== 'idle') launchOverlay?.classList.add('show');
+ });
+}
@@ -771,3 +771,26 @@ html.embedded .hero-sub {
@@ -771,3 +771,26 @@ html.embedded .hero-sub {
flex-wrap: wrap;
flex-wrap: wrap;
margin-top: 18px;
margin-top: 18px;
}
}
+
+/* OpenVibe launcher-aware Source loading UX */
+.launch-focus {
+ margin-top: 10px;
+ border: 1px solid rgba(0, 216, 255, 0.55);
+ background: rgba(0, 216, 255, 0.14);
+ color: #eaf8ff;
+ border-radius: 10px;
+ padding: 9px 16px;
+ font-weight: 700;
+ cursor: pointer;
+}
+.launch-focus:hover {
+ background: rgba(0, 216, 255, 0.22);
+}
+.launch-sub {
+ margin-top: 10px;
+ max-width: 560px;
+ color: rgba(230, 236, 255, 0.76);
+ font-size: 13px;
+ line-height: 1.35;
+ text-align: center;
+}
@@ -1,27 +1,35 @@
@@ -1,27 +1,35 @@
#!/usr/bin/env bash
#!/usr/bin/env bash
set -euo pipefail
set -euo pipefail
ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
+cd "$ROOT"
echo "[openvibe] client mode diagnostic"
echo "[openvibe] client mode diagnostic"
echo "Windows/Proton launcher: $ROOT/tools/run-client-proton.sh"
echo "Windows/Proton launcher: $ROOT/tools/run-client-proton.sh"
-echo "Linux client module expected at: $ROOT/game/openvibe.games/bin/linux64/client.so"
+echo "Linux client module: $ROOT/game/openvibe.games/bin/linux64/client.so"
+echo "Fallback aliases: $ROOT/game/openvibe.games/cfg/openvibe_proton_client.cfg"
+echo
+[[ -f game/openvibe.games/bin/linux64/client.so ]] && echo "[ok] Linux client.so exists" || echo "[missing] Linux client.so missing"
+[[ -f game/openvibe.games/cfg/openvibe_proton_client.cfg ]] && echo "[ok] Proton fallback aliases exist" || echo "[missing] Proton fallback aliases missing"
-if [[ -f "$ROOT/game/openvibe.games/bin/linux64/client.so" ]]; then
- echo "[ok] Linux client.so exists"
-else
- echo "[warn] Linux client.so missing; run tools/build-sdk-linux.sh and tools/setup-openvibe-bin.sh"
-fi
+echo
+cat <<'TXT'
+Important:
+ Proton Windows hl2.exe loads Windows client.dll, not Linux bin/linux64/client.so.
+ If the console says unknown command ov_join/ov_menu, the OpenVibe client DLL is not loaded.
-cat <<'MSG'
+Current reliable custom UI:
+ Electron launcher + local Chromium UI.
-If the in-game console says unknown command "ov_join" or "ov_menu", the OpenVibe client DLL is not loaded.
-That is expected when launching Windows hl2.exe through Proton while only Linux client.so exists.
+Current in-game fallback:
+ autoexec.cfg loads openvibe_proton_client.cfg aliases.
+ Try: ov_help, ov_join_hub, ov_join_prophunt, ov_join_deathrun.
-Current reliable UI path:
- - Electron launcher / local Chromium UI
- - server-side GameDLL commands
+Full in-game HTML/CSS/JS menu requires:
+ 1) native Linux Source client loading bin/linux64/client.so, or
+ 2) a Windows client.dll build for Proton hl2.exe.
-Full in-game HTML menu requires one of:
- 1. Run a native Linux Source client that loads bin/linux64/client.so, or
- 2. Build a Windows client.dll/server.dll and place them in game/openvibe.games/bin/ for Proton hl2.exe.
-MSG
+Full loading image replacement:
+ Convert materialsrc/console/openvibe-loading.svg to:
+ materials/console/background01.vtf
+ materials/console/background01_widescreen.vtf
+TXT
new file mode 100755
new file mode 100755
@@ -0,0 +1,396 @@
@@ -0,0 +1,396 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
+cd "$ROOT"
+
+echo "[openvibe] next phase: Proton-aware loading UX + fallback menu aliases"
+echo "[openvibe] root=$ROOT"
+
+mkdir -p game/openvibe.games/cfg game/openvibe.games/resource game/openvibe.games/materialsrc/console game/openvibe.games/materials/console launcher
+
+# -----------------------------------------------------------------------------
+# 1) Proton/client fallback aliases.
+# Windows hl2.exe cannot load the Linux client.so, so these aliases prevent
+# confusing Unknown command spam and keep raw connect flows usable.
+# -----------------------------------------------------------------------------
+cat > game/openvibe.games/cfg/openvibe_proton_client.cfg <<'CFG'
+// OpenVibe Proton client fallback aliases.
+// This file is executed by tools/run-client-proton.sh.
+// Proton Windows hl2.exe does not load Linux bin/linux64/client.so, so the
+// native OpenVibe client DLL commands are not available in this mode.
+
+echo "[OpenVibe] Proton fallback cfg loaded. Electron is the custom menu in Proton mode."
+
+alias ov_help "echo OpenVibe Proton fallback: use ov_join_hub / ov_join_prophunt / ov_join_deathrun / ov_join_fortwars / ov_join_traitortown. Electron remains the full HTML/CSS/JS menu."
+alias ov_menu "echo OpenVibe HTML menu is hosted by Electron in Proton mode. Alt-tab to the launcher or run npm run dev."
+alias ov_ui "ov_menu"
+alias ov_main_menu "ov_menu"
+alias ov_menu_main "ov_menu"
+alias ov_menu_servers "echo Server browser is in Electron. Quick connects: ov_join_hub, ov_join_prophunt, ov_join_deathrun, ov_join_fortwars, ov_join_traitortown."
+alias ov_menu_leaderboard "echo Leaderboard UI is in Electron in Proton mode."
+alias ov_menu_inventory "echo Inventory UI is in Electron in Proton mode."
+alias ov_menu_shop "echo Shop UI is in Electron in Proton mode."
+alias ov_menu_settings "echo Settings UI is in Electron in Proton mode."
+alias ov_menu_reload "echo Reload the Electron window with Ctrl+R or restart npm run dev."
+alias ov_menu_close "echo Close/minimize the Electron launcher window."
+
+alias ov_join "echo Usage in Proton fallback: ov_join_hub / ov_join_prophunt / ov_join_deathrun / ov_join_fortwars / ov_join_traitortown. The argument-taking ov_join command needs OpenVibe client.dll/client.so."
+alias ov_join_hub "connect 127.0.0.1:27015"
+alias ov_join_prophunt "connect 127.0.0.1:27016"
+alias ov_join_deathrun "connect 127.0.0.1:27017"
+alias ov_join_fortwars "connect 127.0.0.1:27018"
+alias ov_join_traitortown "connect 127.0.0.1:27019"
+
+alias ov_js_status "cmd ov_js_status"
+alias ov_js_reload "cmd ov_js_reload"
+alias ov_js_cmd "cmd ov_js_cmd"
+alias ov_js_fire "cmd ov_js_fire"
+
+alias ov_prophunt_disguise "cmd ov_prophunt_disguise"
+alias ov_prophunt_reset_disguise "cmd ov_prophunt_reset_disguise"
+alias ov_fortwars_spawn "cmd ov_fortwars_spawn"
+CFG
+
+cat > game/openvibe.games/cfg/autoexec.cfg <<'CFG'
+// OpenVibe autoexec. Safe in both native and Proton modes.
+exec openvibe_proton_client.cfg
+CFG
+
+# Patch Proton launcher to exec aliases early.
+python3 <<'PY'
+from pathlib import Path
+p = Path('tools/run-client-proton.sh')
+s = p.read_text()
+if '+exec openvibe_proton_client.cfg' not in s:
+ s = s.replace(
+ '-console -dev -novid -sw -w 1280 -h 720 \\\n',
+ '-console -dev -novid -sw -w 1280 -h 720 +exec openvibe_proton_client.cfg \\\n'
+ )
+if 'OPENVIBE_PROTON_FALLBACK' not in s:
+ s = s.replace('export PROTON_LOG=0\n', 'export PROTON_LOG=0\nexport OPENVIBE_PROTON_FALLBACK=1\n')
+p.write_text(s)
+PY
+chmod +x tools/run-client-proton.sh
+
+# -----------------------------------------------------------------------------
+# 2) Best-effort loading dialog override resources.
+# This won't replace the HL2 background material until VTF files are supplied,
+# but it customizes text/layout and adds docs + source art placeholders.
+# -----------------------------------------------------------------------------
+cat > game/openvibe.games/resource/LoadingDialog.res <<'RES'
+"Resource/LoadingDialog.res"
+{
+ "LoadingDialog"
+ {
+ "ControlName" "Frame"
+ "fieldName" "LoadingDialog"
+ "xpos" "0"
+ "ypos" "0"
+ "wide" "f0"
+ "tall" "f0"
+ "visible" "1"
+ "enabled" "1"
+ "title" "OpenVibe: Source"
+ "proportionaltoparent" "1"
+ "PaintBackgroundType" "2"
+ "bgcolor_override" "7 8 14 245"
+ }
+ "LoadingLabel"
+ {
+ "ControlName" "Label"
+ "fieldName" "LoadingLabel"
+ "xpos" "cs-260"
+ "ypos" "rs1-92"
+ "wide" "520"
+ "tall" "28"
+ "visible" "1"
+ "enabled" "1"
+ "labelText" "OPENVIBE: SOURCE"
+ "textAlignment" "center"
+ "font" "DefaultLarge"
+ "fgcolor_override" "0 216 255 255"
+ }
+ "ProgressLabel"
+ {
+ "ControlName" "Label"
+ "fieldName" "ProgressLabel"
+ "xpos" "cs-260"
+ "ypos" "rs1-58"
+ "wide" "520"
+ "tall" "24"
+ "visible" "1"
+ "enabled" "1"
+ "labelText" "Loading OpenVibe session..."
+ "textAlignment" "center"
+ "font" "Default"
+ "fgcolor_override" "230 236 255 255"
+ }
+}
+RES
+
+cat > game/openvibe.games/materialsrc/console/openvibe-loading.svg <<'SVG'
+<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080">
+ <defs>
+ <radialGradient id="g" cx="50%" cy="45%" r="75%">
+ <stop offset="0" stop-color="#16334a"/>
+ <stop offset="0.45" stop-color="#0b1020"/>
+ <stop offset="1" stop-color="#05060b"/>
+ </radialGradient>
+ <linearGradient id="line" x1="0" x2="1">
+ <stop offset="0" stop-color="#00d8ff" stop-opacity="0"/>
+ <stop offset="0.5" stop-color="#00d8ff" stop-opacity="1"/>
+ <stop offset="1" stop-color="#a855f7" stop-opacity="0"/>
+ </linearGradient>
+ </defs>
+ <rect width="1920" height="1080" fill="url(#g)"/>
+ <g opacity="0.18" stroke="#00d8ff" fill="none">
+ <path d="M0 770 C300 630 520 890 850 730 C1190 560 1390 810 1920 610"/>
+ <path d="M0 830 C330 700 560 960 910 780 C1240 610 1480 860 1920 680"/>
+ </g>
+ <circle cx="960" cy="450" r="155" fill="none" stroke="#00d8ff" stroke-width="8" opacity="0.8"/>
+ <circle cx="960" cy="450" r="112" fill="none" stroke="#a855f7" stroke-width="4" opacity="0.7"/>
+ <text x="960" y="465" text-anchor="middle" font-family="Orbitron, Rajdhani, sans-serif" font-size="84" fill="#eaf8ff" font-weight="900">OPENVIBE</text>
+ <text x="960" y="530" text-anchor="middle" font-family="Rajdhani, sans-serif" font-size="38" fill="#00d8ff" font-weight="700">SOURCE</text>
+ <rect x="660" y="620" width="600" height="6" rx="3" fill="url(#line)"/>
+ <text x="960" y="680" text-anchor="middle" font-family="Inter, sans-serif" font-size="28" fill="#b9c7e8">Loading connected game session...</text>
+</svg>
+SVG
+
+cat > game/openvibe.games/materials/console/README.md <<'MD'
+# OpenVibe loading background
+
+Source's full-screen loading background is a VTF material, normally:
+
+- `materials/console/background01.vtf`
+- `materials/console/background01_widescreen.vtf`
+
+This repo includes source art at:
+
+- `materialsrc/console/openvibe-loading.svg`
+
+To fully replace the HL2 loading image, convert it to the two VTF filenames above using VTFEdit/VTFCmd or Valve's `vtex`, then place the VTFs in this folder.
+
+Until those VTF files exist, `resource/LoadingDialog.res` can restyle text/layout, but the old HL2 image may still appear.
+MD
+
+# -----------------------------------------------------------------------------
+# 3) Electron launch UX: keep launcher visible, expose focus button, and only
+# hide when Source window is stable if env var opts in.
+# -----------------------------------------------------------------------------
+python3 <<'PY'
+from pathlib import Path
+p = Path('launcher/main.js')
+s = p.read_text()
+
+if 'function sourceWindowVisible' not in s:
+ insert_after = "let appIsQuitting = false;\n"
+ block = r'''
+let lastLaunchState = { phase: 'idle', message: 'Ready', pid: null };
+
+function broadcastLaunchState(state) {
+ lastLaunchState = { ...lastLaunchState, ...state, at: Date.now() };
+ if (mainWindow && !mainWindow.isDestroyed()) {
+ mainWindow.webContents.send('launch-state', lastLaunchState);
+ }
+}
+
+function sourceWindowVisible() {
+ try {
+ const out = execSync('wmctrl -lx 2>/dev/null || true', { encoding: 'utf8' });
+ return /OpenVibe: Source|Source SDK Base 2013|hl2\.exe|hl2/i.test(out);
+ } catch {
+ return false;
+ }
+}
+
+function focusSourceWindow() {
+ const commands = [
+ "wmctrl -a 'OpenVibe: Source'",
+ "wmctrl -a 'Source SDK Base 2013'",
+ "wmctrl -a 'hl2.exe'",
+ ];
+ for (const cmd of commands) {
+ try {
+ execSync(cmd, { stdio: 'ignore' });
+ return true;
+ } catch {}
+ }
+ return false;
+}
+
+function waitForStableSourceWindow(timeoutMs = 60000) {
+ const started = Date.now();
+ let stableTicks = 0;
+ return new Promise((resolve) => {
+ const tick = () => {
+ if (sourceWindowVisible()) stableTicks += 1;
+ else stableTicks = 0;
+
+ broadcastLaunchState({
+ phase: stableTicks > 0 ? 'window-detected' : 'starting',
+ message: stableTicks > 0
+ ? `Source window detected (${stableTicks}/5 stable checks)...`
+ : 'Starting Source through Proton...',
+ });
+
+ if (stableTicks >= 5) return resolve(true);
+ if (Date.now() - started >= timeoutMs) return resolve(false);
+ setTimeout(tick, 1000);
+ };
+ tick();
+ });
+}
+'''
+ s = s.replace(insert_after, insert_after + block + "\n")
+
+old = """ mainWindow?.webContents.send('game-started', gameProcess.pid);\n setTimeout(() => {\n if (gameProcess && mainWindow && !mainWindow.isDestroyed()) mainWindow.hide();\n }, 750);\n"""
+new = """ mainWindow?.webContents.send('game-started', gameProcess.pid);\n broadcastLaunchState({\n phase: 'spawned',\n pid: gameProcess.pid,\n message: 'Source spawned. Keeping launcher visible until the game window is stable.',\n });\n\n waitForStableSourceWindow().then((ready) => {\n broadcastLaunchState({\n phase: ready ? 'ready' : 'timeout',\n pid: gameProcess ? gameProcess.pid : null,\n message: ready\n ? 'Source window appears stable. You can focus the game now.'\n : 'Source is still not confirmed stable. Launcher stayed visible to avoid confusion.',\n });\n\n const shouldAutoHide = process.env.OPENVIBE_HIDE_LAUNCHER_ON_GAME_READY === '1';\n if (ready && shouldAutoHide && gameProcess && mainWindow && !mainWindow.isDestroyed()) {\n mainWindow.hide();\n focusSourceWindow();\n }\n });\n"""
+if old in s:
+ s = s.replace(old, new)
+
+if "ipcMain.handle('game:focus'" not in s:
+ s = s.replace("ipcMain.handle('game:status', () => ({\n running: gameProcess !== null,\n pid: gameProcess?.pid ?? null,\n}));\n",
+ "ipcMain.handle('game:status', () => ({\n running: gameProcess !== null,\n pid: gameProcess?.pid ?? null,\n launchState: lastLaunchState,\n}));\n\nipcMain.handle('game:focus', async () => focusSourceWindow());\n")
+
+p.write_text(s)
+PY
+
+python3 <<'PY'
+from pathlib import Path
+p = Path('launcher/preload.js')
+s = p.read_text()
+if "focusGame" not in s:
+ s = s.replace("gameStatus: () => ipcRenderer.invoke('game:status'),",
+ "gameStatus: () => ipcRenderer.invoke('game:status'),\n focusGame: () => ipcRenderer.invoke('game:focus'),")
+if "onLaunchState" not in s:
+ s = s.replace("onGameStart: (cb) => ipcRenderer.on('game-started', (_e, pid) => cb(pid)),",
+ "onGameStart: (cb) => ipcRenderer.on('game-started', (_e, pid) => cb(pid)),\n onLaunchState: (cb) => ipcRenderer.on('launch-state', (_e, state) => cb(state)),")
+p.write_text(s)
+PY
+
+python3 <<'PY'
+from pathlib import Path
+p = Path('launcher/renderer.js')
+s = p.read_text()
+if 'focusGame:' not in s:
+ s = s.replace("gameStatus: () => isElectron ? window.OV.gameStatus() : Promise.resolve({ running: true, pid: null }),",
+ "gameStatus: () => isElectron ? window.OV.gameStatus() : Promise.resolve({ running: true, pid: null }),\n focusGame: () => isElectron ? window.OV.focusGame() : Promise.resolve(false),")
+if 'launch-focus' not in s:
+ s = s.replace("document.getElementById('launch-cancel')?.addEventListener('click', () => {\n launchOverlay.classList.remove('show');\n});",
+ "document.getElementById('launch-cancel')?.addEventListener('click', () => {\n launchOverlay.classList.remove('show');\n});\n\ndocument.getElementById('launch-focus')?.addEventListener('click', async () => {\n const ok = await Bridge.focusGame();\n toast(ok ? 'Focused Source window.' : 'Could not find Source window yet.', !ok);\n});")
+if 'onLaunchState' not in s:
+ s += r'''
+
+// Launcher-aware Source startup status. This keeps Electron useful while Proton
+// shows the default Source loading window and before a native client DLL exists.
+if (isElectron && window.OV.onLaunchState) {
+ window.OV.onLaunchState((state) => {
+ const label = document.getElementById('launch-label');
+ const sub = document.getElementById('launch-sub');
+ if (label && state?.message) label.textContent = state.message;
+ if (sub) {
+ sub.textContent = state?.phase === 'ready'
+ ? 'Game window is ready. Use Focus Game Window, or keep Electron open as the custom menu.'
+ : 'Electron remains open so you are not left staring at a frozen/default Source loading screen.';
+ }
+ if (state?.phase && state.phase !== 'idle') launchOverlay?.classList.add('show');
+ });
+}
+'''
+p.write_text(s)
+PY
+
+python3 <<'PY'
+from pathlib import Path
+p = Path('launcher/index.html')
+s = p.read_text()
+if 'id="launch-focus"' not in s:
+ s = s.replace(' <button class="launch-cancel" id="launch-cancel">Cancel</button>\n',
+ ' <button class="launch-cancel" id="launch-cancel">Cancel</button>\n <button class="launch-focus" id="launch-focus">Focus Game Window</button>\n <div class="launch-sub" id="launch-sub">Electron stays visible until Source is stable.</div>\n')
+p.write_text(s)
+PY
+
+cat >> launcher/styles.css <<'CSS'
+
+/* OpenVibe launcher-aware Source loading UX */
+.launch-focus {
+ margin-top: 10px;
+ border: 1px solid rgba(0, 216, 255, 0.55);
+ background: rgba(0, 216, 255, 0.14);
+ color: #eaf8ff;
+ border-radius: 10px;
+ padding: 9px 16px;
+ font-weight: 700;
+ cursor: pointer;
+}
+.launch-focus:hover {
+ background: rgba(0, 216, 255, 0.22);
+}
+.launch-sub {
+ margin-top: 10px;
+ max-width: 560px;
+ color: rgba(230, 236, 255, 0.76);
+ font-size: 13px;
+ line-height: 1.35;
+ text-align: center;
+}
+CSS
+
+# -----------------------------------------------------------------------------
+# 4) Diagnostic.
+# -----------------------------------------------------------------------------
+cat > tools/check-openvibe-client-mode.sh <<'SH'
+#!/usr/bin/env bash
+set -euo pipefail
+ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
+cd "$ROOT"
+
+echo "[openvibe] client mode diagnostic"
+echo "Windows/Proton launcher: $ROOT/tools/run-client-proton.sh"
+echo "Linux client module: $ROOT/game/openvibe.games/bin/linux64/client.so"
+echo "Fallback aliases: $ROOT/game/openvibe.games/cfg/openvibe_proton_client.cfg"
+echo
+[[ -f game/openvibe.games/bin/linux64/client.so ]] && echo "[ok] Linux client.so exists" || echo "[missing] Linux client.so missing"
+[[ -f game/openvibe.games/cfg/openvibe_proton_client.cfg ]] && echo "[ok] Proton fallback aliases exist" || echo "[missing] Proton fallback aliases missing"
+
+echo
+cat <<'TXT'
+Important:
+ Proton Windows hl2.exe loads Windows client.dll, not Linux bin/linux64/client.so.
+ If the console says unknown command ov_join/ov_menu, the OpenVibe client DLL is not loaded.
+
+Current reliable custom UI:
+ Electron launcher + local Chromium UI.
+
+Current in-game fallback:
+ autoexec.cfg loads openvibe_proton_client.cfg aliases.
+ Try: ov_help, ov_join_hub, ov_join_prophunt, ov_join_deathrun.
+
+Full in-game HTML/CSS/JS menu requires:
+ 1) native Linux Source client loading bin/linux64/client.so, or
+ 2) a Windows client.dll build for Proton hl2.exe.
+
+Full loading image replacement:
+ Convert materialsrc/console/openvibe-loading.svg to:
+ materials/console/background01.vtf
+ materials/console/background01_widescreen.vtf
+TXT
+SH
+chmod +x tools/check-openvibe-client-mode.sh
+
+# Keep setup links current, if build exists.
+if [[ -x tools/setup-openvibe-bin.sh ]]; then
+ tools/setup-openvibe-bin.sh || true
+fi
+
+echo
+echo "[openvibe] done. Re-test:"
+echo " tools/check-openvibe-client-mode.sh"
+echo " cd launcher && npm run dev"
+echo
+echo "In Proton client console, try:"
+echo " exec openvibe_proton_client.cfg"
+echo " ov_help"
+echo " ov_join_hub"
+echo " ov_menu_servers"
@@ -40,6 +40,7 @@ export STEAM_COMPAT_CLIENT_INSTALL_PATH="$STEAM_PATH"
@@ -40,6 +40,7 @@ export STEAM_COMPAT_CLIENT_INSTALL_PATH="$STEAM_PATH"
export STEAM_COMPAT_DATA_PATH="$STEAM_COMPAT_DATA"
export STEAM_COMPAT_DATA_PATH="$STEAM_COMPAT_DATA"
export SteamAppId=243750
export SteamAppId=243750
export PROTON_LOG=0
export PROTON_LOG=0
+export OPENVIBE_PROTON_FALLBACK=1
export DXVK_ASYNC=1
export DXVK_ASYNC=1
exec "$GE_PROTON/proton" waitforexitandrun \
exec "$GE_PROTON/proton" waitforexitandrun \