openvibers/source.openvibe.games Commits
Fix embedded JS console command duplication
Changes
@@@ -6,6 +6,10 @@ const GM = {OV.log("Base Initialize fired");},++ MapInitialize(mapName) {++ OV.log(`Base MapInitialize: ${mapName}`);++ },++PlayerInitialSpawn(ply) {ply.chat("Welcome to OpenVibe: Source.");},@@@ -20,7 +24,16 @@ const GM = {return undefined;},-- Think() {}++ ConsoleCommand(text) {++ OV.log(`ConsoleCommand: ${text}`);++ return undefined;++ },++++ Think() {},++++ConsoleCommand(text) {++ OV.log(`Base ConsoleCommand: ${text}`);++ }};gamemode.set(GM);@@@ -38,6 +38,17 @@ const GM = {return undefined;},++ ConsoleCommand(text) {++ OV.log(`Hub ConsoleCommand: ${text}`);++++ if (text === "smoke") {++ OV.broadcast("OpenVibe embedded JS smoke test passed.");++ return false;++ }++++ return undefined;++ },++Think() {}};@@@ -28,34 +28,53 @@ static bool OpenVibeJS_IsRunning()static void OpenVibeJS_EnsureStarted(){-- if (g_OVServerJSStarted)++ if ( g_OVServerJSStarted )return;g_OVServerJSStarted = true;-- if (!ov_js_enabled.GetBool())++ if ( !ov_js_enabled.GetBool() ){-- Msg("[OV JS] disabled by ov_js_enabled=0\n");++ Msg( "[OV JS] disabled by ov_js_enabled=0\n" );return;}-- if (g_OVServerJS.Init(true, ov_mode.GetString()))++ if ( g_OVServerJS.Init( true, ov_mode.GetString() ) ){-- Msg("[OV JS] server runtime initialized for mode '%s'\n", ov_mode.GetString());-- g_OVServerJS.CallHookVoid("Initialize");++ Msg( "[OV JS] server runtime initialized for mode '%s'\n", ov_mode.GetString() );++ g_OVServerJS.CallHookVoid( "Initialize" );JSContext *ctx = g_OVServerJS.Context();-- JSValue mapName = JS_NewString(ctx, gpGlobals ? STRING(gpGlobals->mapname) : "");++ JSValue mapName = JS_NewString( ctx, gpGlobals ? STRING( gpGlobals->mapname ) : "" );JSValueConst argv[] = { mapName };-- g_OVServerJS.CallHookVoid("MapInitialize", 1, argv);-- JS_FreeValue(ctx, mapName);++ g_OVServerJS.CallHookVoid( "MapInitialize", 1, argv );++ JS_FreeValue( ctx, mapName );}else{-- Warning("[OV JS] server runtime failed to initialize\n");++ Warning( "[OV JS] server runtime failed to initialize\n" );}}++static void OpenVibeJS_CallConsoleCommand( const char *pszCommand )++{++ OpenVibeJS_EnsureStarted();++++ if ( !OpenVibeJS_IsRunning() )++ {++ Warning( "[OV JS] runtime is not running; command ignored: %s\n", pszCommand ? pszCommand : "" );++ return;++ }++++ JSContext *ctx = g_OVServerJS.Context();++ JSValue command = JS_NewString( ctx, pszCommand ? pszCommand : "" );++ JSValueConst argv[] = { command };++++ g_OVServerJS.CallHookVoid( "ConsoleCommand", 1, argv );++++ JS_FreeValue( ctx, command );++}++void OpenVibeJS_ServerInit(){OpenVibeJS_EnsureStarted();@@@ -63,8 +82,8 @@ void OpenVibeJS_ServerInit()void OpenVibeJS_ServerShutdown(){-- if (OpenVibeJS_IsRunning())-- g_OVServerJS.CallHookVoid("Shutdown");++ if ( OpenVibeJS_IsRunning() )++ g_OVServerJS.CallHookVoid( "Shutdown" );g_OVServerJS.Shutdown();g_OVServerJSStarted = false;@@@ -74,101 +93,101 @@ void OpenVibeJS_ServerThink(){OpenVibeJS_EnsureStarted();-- if (!OpenVibeJS_IsRunning())++ if ( !OpenVibeJS_IsRunning() )return;-- g_OVServerJS.CallHookVoid("Think");++ g_OVServerJS.CallHookVoid( "Think" );}--void OpenVibeJS_Server_PlayerInitialSpawn(CHL2MP_Player *player)++void OpenVibeJS_Server_PlayerInitialSpawn( CHL2MP_Player *player ){OpenVibeJS_EnsureStarted();-- if (!OpenVibeJS_IsRunning() || !player)++ if ( !OpenVibeJS_IsRunning() || !player )return;JSContext *ctx = g_OVServerJS.Context();-- JSValue ply = OVJS_NewPlayer(ctx, player);++ JSValue ply = OVJS_NewPlayer( ctx, player );JSValueConst argv[] = { ply };-- g_OVServerJS.CallHookVoid("PlayerInitialSpawn", 1, argv);++ g_OVServerJS.CallHookVoid( "PlayerInitialSpawn", 1, argv );-- JS_FreeValue(ctx, ply);++ JS_FreeValue( ctx, ply );}--void OpenVibeJS_Server_PlayerSpawn(CHL2MP_Player *player)++void OpenVibeJS_Server_PlayerSpawn( CHL2MP_Player *player ){OpenVibeJS_EnsureStarted();-- if (!OpenVibeJS_IsRunning() || !player)++ if ( !OpenVibeJS_IsRunning() || !player )return;JSContext *ctx = g_OVServerJS.Context();-- JSValue ply = OVJS_NewPlayer(ctx, player);++ JSValue ply = OVJS_NewPlayer( ctx, player );JSValueConst argv[] = { ply };-- g_OVServerJS.CallHookVoid("PlayerSpawn", 1, argv);++ g_OVServerJS.CallHookVoid( "PlayerSpawn", 1, argv );-- JS_FreeValue(ctx, ply);++ JS_FreeValue( ctx, ply );}--void OpenVibeJS_Server_PlayerDeath(CHL2MP_Player *victim, CBaseEntity *attacker, CBaseEntity *inflictor)++void OpenVibeJS_Server_PlayerDeath( CHL2MP_Player *victim, CBaseEntity *attacker, CBaseEntity *inflictor ){OpenVibeJS_EnsureStarted();-- if (!OpenVibeJS_IsRunning() || !victim)++ if ( !OpenVibeJS_IsRunning() || !victim )return;JSContext *ctx = g_OVServerJS.Context();-- JSValue jsVictim = OVJS_NewPlayer(ctx, victim);++ JSValue jsVictim = OVJS_NewPlayer( ctx, victim );JSValue jsAttacker = JS_NULL;-- if (attacker && attacker->IsPlayer())-- jsAttacker = OVJS_NewPlayer(ctx, ToHL2MPPlayer(static_cast<CBasePlayer *>(attacker)));++ if ( attacker && attacker->IsPlayer() )++ jsAttacker = OVJS_NewPlayer( ctx, ToHL2MPPlayer( static_cast<CBasePlayer *>( attacker ) ) );JSValueConst argv[] = { jsVictim, jsAttacker };-- g_OVServerJS.CallHookVoid("PlayerDeath", 2, argv);++ g_OVServerJS.CallHookVoid( "PlayerDeath", 2, argv );-- JS_FreeValue(ctx, jsVictim);-- JS_FreeValue(ctx, jsAttacker);++ JS_FreeValue( ctx, jsVictim );++ JS_FreeValue( ctx, jsAttacker );}--void OpenVibeJS_Server_PlayerDisconnected(CHL2MP_Player *player)++void OpenVibeJS_Server_PlayerDisconnected( CHL2MP_Player *player ){OpenVibeJS_EnsureStarted();-- if (!OpenVibeJS_IsRunning() || !player)++ if ( !OpenVibeJS_IsRunning() || !player )return;JSContext *ctx = g_OVServerJS.Context();-- JSValue ply = OVJS_NewPlayer(ctx, player);++ JSValue ply = OVJS_NewPlayer( ctx, player );JSValueConst argv[] = { ply };-- g_OVServerJS.CallHookVoid("PlayerDisconnected", 1, argv);++ g_OVServerJS.CallHookVoid( "PlayerDisconnected", 1, argv );-- JS_FreeValue(ctx, ply);++ JS_FreeValue( ctx, ply );}--bool OpenVibeJS_Server_PlayerSay(CHL2MP_Player *player, const char *text)++bool OpenVibeJS_Server_PlayerSay( CHL2MP_Player *player, const char *text ){OpenVibeJS_EnsureStarted();-- if (!OpenVibeJS_IsRunning() || !player || !text)++ if ( !OpenVibeJS_IsRunning() || !player || !text )return false;JSContext *ctx = g_OVServerJS.Context();-- JSValue ply = OVJS_NewPlayer(ctx, player);-- JSValue msg = JS_NewString(ctx, text);++ JSValue ply = OVJS_NewPlayer( ctx, player );++ JSValue msg = JS_NewString( ctx, text );JSValueConst argv[] = { ply, msg };bool value = false;-- bool returned = g_OVServerJS.CallHookBool("PlayerSay", &value, 2, argv);++ bool returned = g_OVServerJS.CallHookBool( "PlayerSay", &value, 2, argv );-- JS_FreeValue(ctx, ply);-- JS_FreeValue(ctx, msg);++ JS_FreeValue( ctx, ply );++ JS_FreeValue( ctx, msg );return returned && value == false;}@@@ -177,7 +196,7 @@ static void OV_JSReload_f(){OpenVibeJS_ServerShutdown();OpenVibeJS_ServerInit();-- Msg("[OV JS] reloaded\n");++ Msg( "[OV JS] reloaded\n" );}static ConCommand ov_js_reload(@@@ -186,3 +205,63 @@ static ConCommand ov_js_reload("Reload OpenVibe JavaScript runtime.",FCVAR_GAMEDLL);++++static void OV_JSStatus_f()++{++ Msg( "[OV JS] enabled=%d started=%d running=%d mode=%s\n",++ ov_js_enabled.GetBool() ? 1 : 0,++ g_OVServerJSStarted ? 1 : 0,++ OpenVibeJS_IsRunning() ? 1 : 0,++ ov_mode.GetString() );++}++++static ConCommand ov_js_status(++ "ov_js_status",++ OV_JSStatus_f,++ "Print OpenVibe JavaScript runtime status.",++ FCVAR_GAMEDLL++);++++static void OV_JSFire_f( const CCommand &args )++{++ if ( args.ArgC() < 2 )++ {++ Msg( "Usage: ov_js_fire <HookName>\n" );++ return;++ }++++ OpenVibeJS_EnsureStarted();++++ if ( !OpenVibeJS_IsRunning() )++ {++ Warning( "[OV JS] runtime is not running\n" );++ return;++ }++++ g_OVServerJS.CallHookVoid( args[1] );++}++++static ConCommand ov_js_fire(++ "ov_js_fire",++ OV_JSFire_f,++ "Fire an OpenVibe JavaScript hook with no arguments.",++ FCVAR_GAMEDLL++);++++static void OV_JSCmd_f( const CCommand &args )++{++ if ( args.ArgC() < 2 )++ {++ Msg( "Usage: ov_js_cmd <text>\n" );++ return;++ }++++ OpenVibeJS_CallConsoleCommand( args.ArgS() );++}++++static ConCommand ov_js_cmd(++ "ov_js_cmd",++ OV_JSCmd_f,++ "Send a ConsoleCommand event into the embedded OpenVibe JavaScript runtime.",++ FCVAR_GAMEDLL++);@@@ -13,3 +13,6 @@ void OpenVibeJS_Server_PlayerSpawn(CHL2MP_Player *player);void OpenVibeJS_Server_PlayerDeath(CHL2MP_Player *victim, CBaseEntity *attacker, CBaseEntity *inflictor);void OpenVibeJS_Server_PlayerDisconnected(CHL2MP_Player *player);bool OpenVibeJS_Server_PlayerSay(CHL2MP_Player *player, const char *text);++++void OpenVibeJS_Server_ConsoleCommand(const char *text);++void OpenVibeJS_Server_FireHook(const char *hookName);@@@ -17,18 +17,6 @@#include "steam/isteamhttp.h"// Standard library and socket includes for JS bridge--#include <thread>--#include <mutex>--#include <queue>--#include <string>--#include <condition_variable>--#include <sys/socket.h>--#include <sys/un.h>--#include <unistd.h>--#include <fcntl.h>--#include <sstream>--#include <sys/select.h>--#include <sys/time.h>// memdbgon must be the last include file in a .cpp file.#include "tier0/memdbgon.h"@@@ -382,413 +370,9 @@ static ConCommand ov_fortwars_spawn_cmd(// =================================══════════════════════════════════════════// OpenVibe.JS - Scripting Engine C++ Bridge--// =================================══════════════════════════════════════════----class COpenVibeJsBridge--{--private:-- std::thread m_workerThread;-- std::mutex m_outgoingMutex;-- std::queue<std::string> m_outgoingQueue;-- std::mutex m_incomingMutex;-- std::queue<std::string> m_incomingQueue;-- bool m_bRunning;-- bool m_bConnected;-- int m_socketFd;-- std::string m_socketPath;-- std::string m_incomingBuffer;---- void ThreadFunc()-- {-- while ( m_bRunning )-- {-- if ( !m_bConnected )-- {-- m_socketFd = socket( AF_UNIX, SOCK_STREAM, 0 );-- if ( m_socketFd >= 0 )-- {-- struct sockaddr_un addr;-- memset( &addr, 0, sizeof( addr ) );-- addr.sun_family = AF_UNIX;-- Q_strncpy( addr.sun_path, m_socketPath.c_str(), sizeof( addr.sun_path ) );---- if ( connect( m_socketFd, (struct sockaddr*)&addr, sizeof( addr ) ) == 0 )-- {-- m_bConnected = true;-- Msg( "[OV JS] Connected to Node.js scripting bridge at %s\n", m_socketPath.c_str() );-- }-- else-- {-- close( m_socketFd );-- m_socketFd = -1;-- SpawnNodeJs();-- std::this_thread::sleep_for( std::chrono::milliseconds( 2000 ) );-- continue;-- }-- }-- }---- if ( m_bConnected )-- {-- fd_set readFds;-- fd_set writeFds;-- FD_ZERO( &readFds );-- FD_ZERO( &writeFds );-- FD_SET( m_socketFd, &readFds );---- bool bHasWrite = false;-- {-- std::lock_guard<std::mutex> lock( m_outgoingMutex );-- bHasWrite = !m_outgoingQueue.empty();-- }-- if ( bHasWrite )-- {-- FD_SET( m_socketFd, &writeFds );-- }---- struct timeval tv;-- tv.tv_sec = 0;-- tv.tv_usec = 50000;---- int activity = select( m_socketFd + 1, &readFds, &writeFds, NULL, &tv );-- if ( activity < 0 )-- {-- Disconnect();-- continue;-- }---- if ( FD_ISSET( m_socketFd, &readFds ) )-- {-- char buffer[4096];-- int bytesRead = recv( m_socketFd, buffer, sizeof( buffer ) - 1, 0 );-- if ( bytesRead <= 0 )-- {-- Disconnect();-- continue;-- }-- buffer[bytesRead] = '\0';-- ProcessIncomingBuffer( buffer );-- }---- if ( bHasWrite && FD_ISSET( m_socketFd, &writeFds ) )-- {-- std::string msg;-- {-- std::lock_guard<std::mutex> lock( m_outgoingMutex );-- if ( !m_outgoingQueue.empty() )-- {-- msg = m_outgoingQueue.front();-- m_outgoingQueue.pop();-- }-- }-- if ( !msg.empty() )-- {-- int sent = send( m_socketFd, msg.c_str(), msg.length(), 0 );-- if ( sent < 0 )-- {-- Disconnect();-- continue;-- }-- }-- }-- }-- }-- }---- void SpawnNodeJs()-- {-- static float lastSpawnTime = 0.0f;-- float curTime = gpGlobals ? gpGlobals->realtime : 0.0f;-- if ( curTime > 0.0f && curTime - lastSpawnTime < 10.0f )-- {-- return;-- }-- lastSpawnTime = curTime;---- Msg( "[OV JS] Starting Node.js OpenVibe.JS runtime...\n" );-- char cmd[1024];-- const char *root = getenv( "OPENVIBE_ROOT" );-- if ( !root )-- {-- root = "/home/workstation/src/openvibe-source";-- }-- Q_snprintf( cmd, sizeof( cmd ), "node %s/engine/openvibe-js-runtime/bridge.js %s > /tmp/openvibe-js-%s.log 2>&1 &",-- root, m_socketPath.c_str(), ov_server_id.GetString() );-- int ret = system( cmd );-- (void)ret;-- }---- void Disconnect()-- {-- if ( m_socketFd >= 0 )-- {-- close( m_socketFd );-- m_socketFd = -1;-- }-- m_bConnected = false;-- Msg( "[OV JS] Disconnected from Node.js scripting bridge.\n" );-- }---- void ProcessIncomingBuffer( const char *data )-- {-- m_incomingBuffer += data;-- size_t pos;-- while ( ( pos = m_incomingBuffer.find( '\n' ) ) != std::string::npos )-- {-- std::string line = m_incomingBuffer.substr( 0, pos );-- m_incomingBuffer.erase( 0, pos + 1 );-- if ( !line.empty() )-- {-- std::lock_guard<std::mutex> lock( m_incomingMutex );-- m_incomingQueue.push( line );-- }-- }-- }----public:-- COpenVibeJsBridge() :-- m_bRunning( false ),-- m_bConnected( false ),-- m_socketFd( -1 )-- {-- }---- ~COpenVibeJsBridge()-- {-- Stop();-- }---- void Start()-- {-- if ( m_bRunning ) return;---- char szSocket[256];-- Q_snprintf( szSocket, sizeof( szSocket ), "/tmp/openvibe-js-%s.sock", ov_server_id.GetString() );-- m_socketPath = szSocket;---- m_bRunning = true;-- m_workerThread = std::thread( &COpenVibeJsBridge::ThreadFunc, this );-- Msg( "[OV JS] Scripting bridge service thread started. socket=%s\n", m_socketPath.c_str() );-- }---- void Stop()-- {-- m_bRunning = false;-- if ( m_workerThread.joinable() )-- {-- m_workerThread.join();-- }-- Disconnect();-- }---- void SendMessage( const std::string &msg )-- {-- if ( !m_bConnected ) return;-- std::lock_guard<std::mutex> lock( m_outgoingMutex );-- m_outgoingQueue.push( msg + "\n" );-- }---- void OnPlayerJoined( CBasePlayer *pPlayer )-- {-- if ( !pPlayer ) return;---- char szSteamID[32];-- CSteamID steamID;-- if ( pPlayer->GetSteamID( &steamID ) )-- {-- Q_snprintf( szSteamID, sizeof( szSteamID ), "%llu", steamID.ConvertToUint64() );-- }-- else-- {-- Q_strncpy( szSteamID, "0", sizeof( szSteamID ) );-- }---- std::stringstream ss;-- ss << "{\"type\":\"player_join\",\"data\":{"-- << "\"steamId\":\"" << szSteamID << "\","-- << "\"name\":\"" << pPlayer->GetPlayerName() << "\""-- << "}}";-- SendMessage( ss.str() );-- }---- void OnPlayerLeft( CBasePlayer *pPlayer )-- {-- if ( !pPlayer ) return;---- char szSteamID[32];-- CSteamID steamID;-- if ( pPlayer->GetSteamID( &steamID ) )-- {-- Q_snprintf( szSteamID, sizeof( szSteamID ), "%llu", steamID.ConvertToUint64() );-- }-- else-- {-- Q_strncpy( szSteamID, "0", sizeof( szSteamID ) );-- }-- std::stringstream ss;-- ss << "{\"type\":\"player_leave\",\"data\":{"-- << "\"steamId\":\"" << szSteamID << "\""-- << "}}";-- SendMessage( ss.str() );-- }-- void OnPlayerDeath( CBasePlayer *pPlayer, CBaseEntity *pKiller )-- {-- if ( !pPlayer ) return;---- char szDeadSteamID[32];-- CSteamID deadSteamID;-- if ( pPlayer->GetSteamID( &deadSteamID ) )-- {-- Q_snprintf( szDeadSteamID, sizeof( szDeadSteamID ), "%llu", deadSteamID.ConvertToUint64() );-- }-- else-- {-- Q_strncpy( szDeadSteamID, "0", sizeof( szDeadSteamID ) );-- }---- char szKillerSteamID[32] = "0";-- if ( pKiller && pKiller->IsPlayer() )-- {-- CBasePlayer *pKillerPlayer = (CBasePlayer*)pKiller;-- CSteamID killerSteamID;-- if ( pKillerPlayer->GetSteamID( &killerSteamID ) )-- {-- Q_snprintf( szKillerSteamID, sizeof( szKillerSteamID ), "%llu", killerSteamID.ConvertToUint64() );-- }-- }---- std::stringstream ss;-- ss << "{\"type\":\"player_death\",\"data\":{"-- << "\"deadSteamId\":\"" << szDeadSteamID << "\","-- << "\"killerSteamId\":\"" << szKillerSteamID << "\""-- << "}}";-- SendMessage( ss.str() );-- }---- void OnCommand( const char *cmd )-- {-- std::stringstream ss;-- ss << "{\"type\":\"command\",\"data\":{"-- << "\"cmd\":\"" << cmd << "\""-- << "}}";-- SendMessage( ss.str() );-- }---- void Update()-- {-- if ( !m_bRunning )-- {-- Start();-- }---- static float lastTickTime = 0.0f;-- float curTime = gpGlobals->curtime;-- if ( curTime - lastTickTime >= 1.0f )-- {-- lastTickTime = curTime;-- SendMessage( "{\"type\":\"tick\",\"data\":{}}" );-- }---- std::queue<std::string> localQueue;-- {-- std::lock_guard<std::mutex> lock( m_incomingMutex );-- while ( !m_incomingQueue.empty() )-- {-- localQueue.push( m_incomingQueue.front() );-- m_incomingQueue.pop();-- }-- }---- while ( !localQueue.empty() )-- {-- std::string line = localQueue.front();-- localQueue.pop();-- ProcessMessage( line );-- }-- }----private:-- void ProcessMessage( const std::string &line )-- {-- if ( line.find( "\"type\":\"broadcast\"" ) != std::string::npos )-- {-- size_t msgPos = line.find( "\"message\":\"" );-- if ( msgPos != std::string::npos )-- {-- msgPos += 11;-- size_t endPos = line.find( "\"", msgPos );-- if ( endPos != std::string::npos )-- {-- std::string msg = line.substr( msgPos, endPos - msgPos );-- UTIL_ClientPrintAll( HUD_PRINTTALK, "[OV JS] %s", msg.c_str() );-- }-- }-- }-- else if ( line.find( "\"type\":\"entity_spawn\"" ) != std::string::npos )-- {-- std::string type = "prop_physics_override";-- std::string model = "";-- Vector pos( 0, 0, 0 );---- size_t tPos = line.find( "\"type\":\"" );-- if ( tPos != std::string::npos )-- {-- size_t dataPos = line.find( "\"data\":{" );-- if ( dataPos != std::string::npos )-- {-- size_t innerType = line.find( "\"type\":\"", dataPos );-- if ( innerType != std::string::npos )-- {-- innerType += 8;-- size_t end = line.find( "\"", innerType );-- if ( end != std::string::npos )-- {-- type = line.substr( innerType, end - innerType );-- }-- }-- size_t innerModel = line.find( "\"model\":\"", dataPos );-- if ( innerModel != std::string::npos )-- {-- innerModel += 9;-- size_t end = line.find( "\"", innerModel );-- if ( end != std::string::npos )-- {-- model = line.substr( innerModel, end - innerModel );-- }-- }-- size_t xPos = line.find( "\"x\":", dataPos );-- size_t yPos = line.find( "\"y\":", dataPos );-- size_t zPos = line.find( "\"z\":", dataPos );-- if ( xPos != std::string::npos && yPos != std::string::npos && zPos != std::string::npos )-- {-- pos.x = atof( line.c_str() + xPos + 4 );-- pos.y = atof( line.c_str() + yPos + 4 );-- pos.z = atof( line.c_str() + zPos + 4 );-- }-- }-- }---- if ( !model.empty() )-- {-- CBaseEntity::PrecacheModel( model.c_str() );-- CBaseEntity *pEnt = CreateEntityByName( type.c_str() );-- if ( pEnt )-- {-- pEnt->KeyValue( "model", model.c_str() );-- pEnt->SetAbsOrigin( pos );-- DispatchSpawn( pEnt );-- pEnt->Activate();-- Msg( "[OV JS] Spawned entity %s with model %s at (%f, %f, %f)\n",-- type.c_str(), model.c_str(), pos.x, pos.y, pos.z );-- }-- }-- }-- }--};----static COpenVibeJsBridge g_OpenVibeJsBridge;++// ======================================\n// OpenVibe embedded JavaScript hook bridge\n// ======================================void OpenVibe_OnClientDisconnect( CBasePlayer *pPlayer ){@@@ -810,18 +394,3 @@ void OpenVibe_OnFrame()OpenVibeJS_ServerThink();}--static void OV_JsCmd_f( const CCommand &args )--{-- if ( args.ArgC() < 2 )-- {-- Msg( "Usage: ov_js_cmd <command_or_json>\n" );-- return;-- }-- g_OpenVibeJsBridge.OnCommand( args.ArgS() );--}----static ConCommand ov_js_cmd(-- "ov_js_cmd",-- OV_JsCmd_f,-- "Send a command or JSON message to the OpenVibe.JS runtime.",-- FCVAR_GAMEDLL );@@@ -55,25 +55,6 @@ copy_tree "$ROOT/sdk/openvibe/third_party/quickjs" \"$ROOT/tools/build-quickjs-lib.sh"--QJS_C="$SDK/src/game/shared/openvibe/third_party/quickjs/quickjs.c"--if [[ -f "$QJS_C" ]] && ! grep -q 'OPENVIBE_QUICKJS_CONFIG' "$QJS_C"; then-- tmp="$(mktemp)"-- cat > "$tmp" <<'QCFG'--#ifndef OPENVIBE_QUICKJS_CONFIG--#define OPENVIBE_QUICKJS_CONFIG--#ifndef _GNU_SOURCE--#define _GNU_SOURCE--#endif--#ifndef CONFIG_VERSION--#define CONFIG_VERSION "openvibe"--#endif--#endif----QCFG-- cat "$QJS_C" >> "$tmp"-- mv "$tmp" "$QJS_C"--fi--CLIENT_VPC="$SDK/src/game/client/client_hl2mp.vpc"SERVER_VPC="$SDK/src/game/server/server_hl2mp.vpc"HL2MP_CLIENT="$SDK/src/game/server/hl2mp/hl2mp_client.cpp"@@@ -81,7 +62,11 @@ HL2MP_CLIENTMODE="$SDK/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp"GAMEINTERFACE="$SDK/src/game/server/gameinterface.cpp"HL2MP_PLAYER="$SDK/src/game/server/hl2mp/hl2mp_player.cpp"--perl -0pi -e 's/^.*hl2mp\\openvibe_client\.cpp.*\n//mg; s/^.*hl2mp\\vgui_openvibe_menu\.cpp.*\n//mg; s/(\$File\s+"hl2mp\\clientmode_hl2mpnormal\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_client.cpp"\n\t\t\t\$File\t"hl2mp\\vgui_openvibe_menu.cpp"\n/s' "$CLIENT_VPC"++perl -0pi -e '++ s/^.*hl2mp\\openvibe_client\.cpp.*\n//mg;++ s/^.*hl2mp\\vgui_openvibe_menu\.cpp.*\n//mg;++ s/(\$File\s+"hl2mp\\clientmode_hl2mpnormal\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_client.cpp"\n\t\t\t\$File\t"hl2mp\\vgui_openvibe_menu.cpp"\n/s;++' "$CLIENT_VPC"echo "[openvibe-sdk] patched client_hl2mp.vpc"perl -0pi -e '@@@ -90,7 +75,14 @@ perl -0pi -e 's/^.*openvibe\\ov_js_runtime\.cpp.*\n//mg;s/^.*openvibe\\ov_js_bindings\.cpp.*\n//mg;s/^.*openvibe\\ov_js_player\.cpp.*\n//mg;-- s/(\$File\s+"hl2mp\\hl2mp_player\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_server.cpp"\n\t\t\t\$File\t"hl2mp\\openvibe_js_server.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_runtime.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_bindings.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_player.cpp"\n/s;++ s/^.*quickjs\\quickjs\.c.*\n//mg;++ s/^.*quickjs\\libregexp\.c.*\n//mg;++ s/^.*quickjs\\libunicode\.c.*\n//mg;++ s/^.*quickjs\\cutils\.c.*\n//mg;++ s/^.*quickjs\\dtoa\.c.*\n//mg;++ s/^.*quickjs\\libbf\.c.*\n//mg;++ s/^.*libquickjs_openvibe(?:\.a)?".*\n//mg;++ s/(\$File\s+"hl2mp\\hl2mp_player\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_server.cpp"\n\t\t\t\$File\t"hl2mp\\openvibe_js_server.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_runtime.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_bindings.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_player.cpp"\n\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s;' "$SERVER_VPC"echo "[openvibe-sdk] patched server_hl2mp.vpc"@@@ -147,9 +139,6 @@ import re, sysp = Path(sys.argv[1])s = p.read_text()--# Best-effort patch for common Source SDK 2013 HL2MP Host_Say shape:--# CHL2MP_Player *client = ...--# const char *p = args.ArgS();if "OpenVibeJS_Server_PlayerSay( client," not in s:s2 = re.sub(r'(const char \*p\s*=\s*args\.ArgS\(\);\s*)',@@@ -166,75 +155,4 @@ p.write_text(s)PYfi----# OPENVIBE_CONTINUE_QUICKJS_POSTPATCH--patch_openvibe_quickjs_after_apply() {-- local qjs_dir="$SDK/src/game/shared/openvibe/third_party/quickjs"-- local qjs_c="$qjs_dir/quickjs.c"---- if [[ -f "$qjs_c" ]] && ! grep -q 'OPENVIBE_QUICKJS_STRICT_C_SHIM' "$qjs_c"; then-- local tmp-- tmp="$(mktemp)"-- cat > "$tmp" <<'QJS_SHIM'--#ifndef OPENVIBE_QUICKJS_STRICT_C_SHIM--#define OPENVIBE_QUICKJS_STRICT_C_SHIM----#ifndef _GNU_SOURCE--#define _GNU_SOURCE--#endif----#ifndef CONFIG_VERSION--#define CONFIG_VERSION "openvibe"--#endif----#if defined(__GNUC__) && !defined(__cplusplus) && defined(__STRICT_ANSI__) && !defined(asm)--#define asm __asm__--#endif----#endif----QJS_SHIM-- cat "$qjs_c" >> "$tmp"-- mv "$tmp" "$qjs_c"-- echo "[openvibe-sdk] patched QuickJS strict-C shim"-- fi---- if [[ ! -f "$qjs_dir/libbf.c" ]]; then-- perl -0pi -e 's/^.*openvibe\\\\third_party\\\\quickjs\\\\libbf\.c.*\n//mg; s/^.*openvibe\\third_party\\quickjs\\libbf\.c.*\n//mg' "$SERVER_VPC" || true-- echo "[openvibe-sdk] libbf.c missing; removed libbf.c from server_hl2mp.vpc"-- fi--}----patch_openvibe_quickjs_after_apply------# OPENVIBE_FIX_QUICKJS_LINK_FINAL_V2--# QuickJS is C, so do not compile its .c files through Source SDK/VPC C++.--# We build libquickjs_openvibe.a with cc and link it as a library.--if [[ -f "$SERVER_VPC" ]]; then-- perl -0pi -e '-- s/^.*quickjs\\quickjs\.c.*\n//mg;-- s/^.*quickjs\\libregexp\.c.*\n//mg;-- s/^.*quickjs\\libunicode\.c.*\n//mg;-- s/^.*quickjs\\cutils\.c.*\n//mg;-- s/^.*quickjs\\dtoa\.c.*\n//mg;-- s/^.*quickjs\\libbf\.c.*\n//mg;-- ' "$SERVER_VPC"---- perl -0pi -e 's/^.*libquickjs_openvibe(?:\.a)?".*\n//mg' "$SERVER_VPC"---- perl -0pi -e 's/(\$File\s+"hl2mp\\openvibe_js_server\.cpp"\n)/$1\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s' "$SERVER_VPC"---- echo "[openvibe-sdk] linked QuickJS static library"--fi--echo "[openvibe-sdk] Source SDK OpenVibe patch applied"------# Link prebuilt QuickJS C static library. QuickJS must be compiled as C, not C++.--if ! grep -q 'libquickjs_openvibe' "$SERVER_VPC"; then-- perl -0pi -e 's/(\$File\s+"hl2mp\\openvibe_js_server\.cpp"\n)/$1\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s' "$SERVER_VPC"-- echo "[openvibe-sdk] linked QuickJS static library"--fi----@@@ -16,7 +16,7 @@ fimkdir -p "$SDK_QJS"--# Exclude build/ so rsync does not delete our .o/.a output dir.++# Exclude build/ so rsync does not delete object/library output.rsync -a --delete --exclude='build/' "$SRC_QJS/" "$SDK_QJS/"mkdir -p "$OUT"nnew file mode 100755@@@ -0,0 +1,588 @@++#!/usr/bin/env bash++set -euo pipefail++++ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"++SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"++STAMP="$(date +%Y%m%d-%H%M%S)"++RUN_BUILD="${RUN_BUILD:-1}"++RUN_SETUP="${RUN_SETUP:-1}"++++cd "$ROOT"++++backup_file() {++ local file="$1"++ [[ -f "$file" ]] && cp "$file" "$file.bak.$STAMP"++}++++echo "[openvibe] continue after QuickJS build pass"++echo "[openvibe] root=$ROOT"++echo "[openvibe] sdk=$SDK"++++mkdir -p tools sdk/openvibe/shared sdk/openvibe/server/hl2mp game/openvibe.games/js/gamemodes/base game/openvibe.games/js/gamemodes/hub++++echo "[openvibe] clean local generated junk from git tracking if present"++git rm -f .tmp/quickjs-smoke/smoke-quickjs 2>/dev/null || true++git rm -f tools/apply-openvibe-sdk.sh.bak.* 2>/dev/null || true++++touch .gitignore++grep -qxF '.tmp/' .gitignore || cat >> .gitignore <<'GITIGNORE_ADD'++++# OpenVibe local/generated build junk++.tmp/++*.bak.*++tools/*.bak.*++engine/source-sdk-2013/src/game/shared/openvibe/third_party/quickjs/build/++GITIGNORE_ADD++++echo "[openvibe] write canonical tools/build-quickjs-lib.sh"++++cat > tools/build-quickjs-lib.sh <<'BUILDQJS'++#!/usr/bin/env bash++set -euo pipefail++++ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"++SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"++++SRC_QJS="$ROOT/sdk/openvibe/third_party/quickjs"++SDK_QJS="$SDK/src/game/shared/openvibe/third_party/quickjs"++OUT="$SDK_QJS/build"++++if [[ ! -f "$SRC_QJS/quickjs.c" ]]; then++ echo "Missing $SRC_QJS/quickjs.c" >&2++ echo "Run: tools/vendor-quickjs.sh" >&2++ exit 1++fi++++mkdir -p "$SDK_QJS"++++# Exclude build/ so rsync does not delete object/library output.++rsync -a --delete --exclude='build/' "$SRC_QJS/" "$SDK_QJS/"++++mkdir -p "$OUT"++++sources=(++ quickjs.c++ libregexp.c++ libunicode.c++ cutils.c++)++++[[ -f "$SDK_QJS/dtoa.c" ]] && sources+=(dtoa.c)++[[ -f "$SDK_QJS/libbf.c" ]] && sources+=(libbf.c)++++rm -f "$OUT"/*.o "$OUT"/libquickjs_openvibe.a++++for src in "${sources[@]}"; do++ obj="$OUT/${src%.c}.o"++ echo "[openvibe-qjs] cc $src -> $obj"++++ cc \++ -std=gnu11 \++ -O2 \++ -fPIC \++ -D_GNU_SOURCE \++ -DCONFIG_VERSION=\"openvibe\" \++ -I"$SDK_QJS" \++ -c "$SDK_QJS/$src" \++ -o "$obj"++done++++ar rcs "$OUT/libquickjs_openvibe.a" "$OUT"/*.o++++echo "[openvibe-qjs] built $OUT/libquickjs_openvibe.a"++ls -lh "$OUT/libquickjs_openvibe.a"++BUILDQJS++++chmod +x tools/build-quickjs-lib.sh++++echo "[openvibe] write clean canonical tools/apply-openvibe-sdk.sh"++++backup_file tools/apply-openvibe-sdk.sh++++cat > tools/apply-openvibe-sdk.sh <<'APPLY'++#!/usr/bin/env bash++set -euo pipefail++++ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"++SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"++++if [[ ! -d "$SDK/src/game/client/hl2mp" || ! -d "$SDK/src/game/server/hl2mp" ]]; then++ echo "Source SDK 2013 checkout not found at $SDK" >&2++ echo "Set OPENVIBE_SDK=/path/to/source-sdk-2013 if it lives elsewhere." >&2++ exit 1++fi++++copy_file() {++ local src="$1"++ local dst="$2"++ install -D -m 0644 "$src" "$dst"++ echo "[openvibe-sdk] copied ${dst#$SDK/}"++}++++copy_tree() {++ local src="$1"++ local dst="$2"++ mkdir -p "$dst"++ rsync -a --delete "$src/" "$dst/"++ echo "[openvibe-sdk] copied tree ${dst#$SDK/}"++}++++copy_file "$ROOT/sdk/openvibe/client/hl2mp/openvibe_client.cpp" \++ "$SDK/src/game/client/hl2mp/openvibe_client.cpp"++copy_file "$ROOT/sdk/openvibe/client/hl2mp/vgui_openvibe_menu.cpp" \++ "$SDK/src/game/client/hl2mp/vgui_openvibe_menu.cpp"++copy_file "$ROOT/sdk/openvibe/server/hl2mp/openvibe_server.cpp" \++ "$SDK/src/game/server/hl2mp/openvibe_server.cpp"++++copy_file "$ROOT/sdk/openvibe/shared/ov_js_runtime.h" \++ "$SDK/src/game/shared/openvibe/ov_js_runtime.h"++copy_file "$ROOT/sdk/openvibe/shared/ov_js_runtime.cpp" \++ "$SDK/src/game/shared/openvibe/ov_js_runtime.cpp"++copy_file "$ROOT/sdk/openvibe/shared/ov_js_bindings.h" \++ "$SDK/src/game/shared/openvibe/ov_js_bindings.h"++copy_file "$ROOT/sdk/openvibe/shared/ov_js_bindings.cpp" \++ "$SDK/src/game/shared/openvibe/ov_js_bindings.cpp"++copy_file "$ROOT/sdk/openvibe/shared/ov_js_player.h" \++ "$SDK/src/game/shared/openvibe/ov_js_player.h"++copy_file "$ROOT/sdk/openvibe/shared/ov_js_player.cpp" \++ "$SDK/src/game/shared/openvibe/ov_js_player.cpp"++++copy_file "$ROOT/sdk/openvibe/server/hl2mp/openvibe_js_server.h" \++ "$SDK/src/game/server/hl2mp/openvibe_js_server.h"++copy_file "$ROOT/sdk/openvibe/server/hl2mp/openvibe_js_server.cpp" \++ "$SDK/src/game/server/hl2mp/openvibe_js_server.cpp"++++copy_tree "$ROOT/sdk/openvibe/third_party/quickjs" \++ "$SDK/src/game/shared/openvibe/third_party/quickjs"++++"$ROOT/tools/build-quickjs-lib.sh"++++CLIENT_VPC="$SDK/src/game/client/client_hl2mp.vpc"++SERVER_VPC="$SDK/src/game/server/server_hl2mp.vpc"++HL2MP_CLIENT="$SDK/src/game/server/hl2mp/hl2mp_client.cpp"++HL2MP_CLIENTMODE="$SDK/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp"++GAMEINTERFACE="$SDK/src/game/server/gameinterface.cpp"++HL2MP_PLAYER="$SDK/src/game/server/hl2mp/hl2mp_player.cpp"++++perl -0pi -e '++ s/^.*hl2mp\\openvibe_client\.cpp.*\n//mg;++ s/^.*hl2mp\\vgui_openvibe_menu\.cpp.*\n//mg;++ s/(\$File\s+"hl2mp\\clientmode_hl2mpnormal\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_client.cpp"\n\t\t\t\$File\t"hl2mp\\vgui_openvibe_menu.cpp"\n/s;++' "$CLIENT_VPC"++echo "[openvibe-sdk] patched client_hl2mp.vpc"++++perl -0pi -e '++ s/^.*hl2mp\\openvibe_server\.cpp.*\n//mg;++ s/^.*hl2mp\\openvibe_js_server\.cpp.*\n//mg;++ s/^.*openvibe\\ov_js_runtime\.cpp.*\n//mg;++ s/^.*openvibe\\ov_js_bindings\.cpp.*\n//mg;++ s/^.*openvibe\\ov_js_player\.cpp.*\n//mg;++ s/^.*quickjs\\quickjs\.c.*\n//mg;++ s/^.*quickjs\\libregexp\.c.*\n//mg;++ s/^.*quickjs\\libunicode\.c.*\n//mg;++ s/^.*quickjs\\cutils\.c.*\n//mg;++ s/^.*quickjs\\dtoa\.c.*\n//mg;++ s/^.*quickjs\\libbf\.c.*\n//mg;++ s/^.*libquickjs_openvibe(?:\.a)?".*\n//mg;++ s/(\$File\s+"hl2mp\\hl2mp_player\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_server.cpp"\n\t\t\t\$File\t"hl2mp\\openvibe_js_server.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_runtime.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_bindings.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_player.cpp"\n\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s;++' "$SERVER_VPC"++echo "[openvibe-sdk] patched server_hl2mp.vpc"++++if ! grep -q 'OpenVibe_OnClientActive' "$HL2MP_CLIENT"; then++ sed -i '/void Host_Say/a void OpenVibe_OnClientActive( CHL2MP_Player *pPlayer );' "$HL2MP_CLIENT"++ sed -i '/FinishClientPutInServer( pPlayer );/a \ OpenVibe_OnClientActive( pPlayer );' "$HL2MP_CLIENT"++ echo "[openvibe-sdk] patched hl2mp_client.cpp arrival hook"++fi++++perl -0pi -e 's/[^\S\r\n]*\x0boid OpenVibe_OnClientActive/void OpenVibe_OnClientActive/g; s/\\tOpenVibe_OnClientActive/\tOpenVibe_OnClientActive/g; s/^[ \t]*void OpenVibe_OnClientActive/void OpenVibe_OnClientActive/m' "$HL2MP_CLIENT"++++if ! grep -q 'OpenVibe_OnClientModeInit' "$HL2MP_CLIENTMODE"; then++ sed -i '/#include "ienginevgui.h"/a void OpenVibe_OnClientModeInit();' "$HL2MP_CLIENTMODE"++ sed -i '/BaseClass::Init();/a \ OpenVibe_OnClientModeInit();' "$HL2MP_CLIENTMODE"++ echo "[openvibe-sdk] patched clientmode_hl2mpnormal.cpp OpenVibe menu hook"++fi++++perl -0pi -e 's/^[ \t]*tOpenVibe_OnClientModeInit\(\);/ OpenVibe_OnClientModeInit();/m' "$HL2MP_CLIENTMODE"++++if [[ -f "$GAMEINTERFACE" ]]; then++ if ! grep -q 'OpenVibe_OnFrame' "$GAMEINTERFACE"; then++ sed -i '/CServerGameDLL::GameFrame/i #ifdef HL2MP\nvoid OpenVibe_OnFrame();\n#endif' "$GAMEINTERFACE"++ sed -i '/VPROF( "CServerGameDLL::GameFrame" );/a #ifdef HL2MP\n\tOpenVibe_OnFrame();\n#endif' "$GAMEINTERFACE"++ echo "[openvibe-sdk] patched gameinterface.cpp frame hook"++ fi++++ if ! grep -q 'OpenVibe_OnClientDisconnect' "$GAMEINTERFACE"; then++ perl -pi -e 's/void CServerGameClients::ClientDisconnect/#ifdef HL2MP\nvoid OpenVibe_OnClientDisconnect( CBasePlayer *pPlayer );\n#endif\nvoid CServerGameClients::ClientDisconnect/' "$GAMEINTERFACE"++ perl -0777 -pi -e 's/(void CServerGameClients::ClientDisconnect\( edict_t \*pEdict \)\s*\{\s*extern bool\s+g_fGameOver;\s*CBasePlayer \*player = \( CBasePlayer \* \)CBaseEntity::Instance\( pEdict \);)/$1\n#ifdef HL2MP\n\tif ( player ) { OpenVibe_OnClientDisconnect( player ); }\n#endif/g' "$GAMEINTERFACE"++ echo "[openvibe-sdk] patched gameinterface.cpp disconnect hook"++ fi++fi++++if [[ -f "$HL2MP_PLAYER" ]]; then++ if ! grep -q 'OpenVibe_OnPlayerDeath' "$HL2MP_PLAYER"; then++ sed -i '/CHL2MP_Player::Event_Killed/i void OpenVibe_OnPlayerDeath( CHL2MP_Player *pPlayer, CBaseEntity *pKiller );' "$HL2MP_PLAYER"++ sed -i '/CTakeDamageInfo subinfo = info;/i \ OpenVibe_OnPlayerDeath( this, info.GetAttacker() );' "$HL2MP_PLAYER"++ echo "[openvibe-sdk] patched hl2mp_player.cpp death hook"++ fi++++ if ! grep -q 'OpenVibeJS_Server_PlayerSpawn' "$HL2MP_PLAYER"; then++ sed -i '/CHL2MP_Player::Spawn/i void OpenVibeJS_Server_PlayerSpawn( CHL2MP_Player *pPlayer );' "$HL2MP_PLAYER"++ sed -i '0,/BaseClass::Spawn();/s//BaseClass::Spawn();\n\tOpenVibeJS_Server_PlayerSpawn( this );/' "$HL2MP_PLAYER"++ echo "[openvibe-sdk] patched hl2mp_player.cpp spawn hook"++ fi++fi++++if [[ -f "$HL2MP_CLIENT" ]] && ! grep -q 'OpenVibeJS_Server_PlayerSay' "$HL2MP_CLIENT"; then++ sed -i '/void Host_Say/a bool OpenVibeJS_Server_PlayerSay( CHL2MP_Player *pPlayer, const char *pszText );' "$HL2MP_CLIENT"++ python3 - "$HL2MP_CLIENT" <<'PY'++from pathlib import Path++import re, sys++++p = Path(sys.argv[1])++s = p.read_text()++++if "OpenVibeJS_Server_PlayerSay( client," not in s:++ s2 = re.sub(++ r'(const char \*p\s*=\s*args\.ArgS\(\);\s*)',++ r'\1\n\tif ( client && OpenVibeJS_Server_PlayerSay( client, p ) )\n\t\treturn;\n',++ s,++ count=1,++ )++ if s2 != s:++ s = s2++ print("[openvibe-sdk] patched hl2mp_client.cpp PlayerSay hook")++ else:++ print("[openvibe-sdk] WARNING: could not auto-patch Host_Say PlayerSay body")++p.write_text(s)++PY++fi++++echo "[openvibe-sdk] Source SDK OpenVibe patch applied"++APPLY++++chmod +x tools/apply-openvibe-sdk.sh++++echo "[openvibe] remove legacy Node bridge from sdk/openvibe/server/hl2mp/openvibe_server.cpp"++++backup_file sdk/openvibe/server/hl2mp/openvibe_server.cpp++++python3 <<'PY'++from pathlib import Path++import re++++p = Path("sdk/openvibe/server/hl2mp/openvibe_server.cpp")++s = p.read_text()++++if '#include "openvibe_js_server.h"' not in s:++ s = re.sub(++ r'(#include\s+"hl2mp_player\.h"\s*\n)',++ r'\1#include "openvibe_js_server.h"\n',++ s,++ count=1,++ )++++start = s.find("// =========================================")++bridge = s.find("OpenVibe.JS - Scripting Engine C++ Bridge")++if start != -1 and bridge != -1 and start < bridge:++ end = s.find("void OpenVibe_OnClientDisconnect", bridge)++ if end != -1:++ s = s[:start].rstrip() + "\n\n" + s[end:]++ print("[openvibe] removed legacy OpenVibe.JS Node bridge block")++++s = re.sub(r'\n\s*static\s+COpenVibeJsBridge\s+g_OpenVibeJsBridge\s*;\s*\n', '\n', s)++++legacy_cmd = re.compile(++ r'\nstatic\s+void\s+OV_JsCmd_f\s*\([^)]*\)\s*\{.*?\nstatic\s+ConCommand\s+ov_js_cmd\s*\(.*?\);\s*',++ re.S,++)++s2 = legacy_cmd.sub('\n', s)++if s2 != s:++ s = s2++ print("[openvibe] removed legacy ov_js_cmd command")++++def replace_func(src, name, args, body):++ m = re.search(rf'void\s+{name}\s*\({args}\)\s*\{{', src)++ if not m:++ return src++ brace = src.find("{", m.end() - 1)++ depth = 0++ end = None++ for i in range(brace, len(src)):++ if src[i] == "{":++ depth += 1++ elif src[i] == "}":++ depth -= 1++ if depth == 0:++ end = i++ break++ if end is None:++ return src++ return src[:brace + 1] + "\n" + body.rstrip() + "\n" + src[end:]++++s = replace_func(++ s,++ "OpenVibe_OnClientDisconnect",++ r'\s*CBasePlayer\s*\*\s*pPlayer\s*',++ '''++ CHL2MP_Player *pHL2MP = ToHL2MPPlayer( pPlayer );++ if ( pHL2MP )++ OpenVibeJS_Server_PlayerDisconnected( pHL2MP );++'''++)++++s = replace_func(++ s,++ "OpenVibe_OnPlayerDeath",++ r'\s*CHL2MP_Player\s*\*\s*pPlayer\s*,\s*CBaseEntity\s*\*\s*pKiller\s*',++ '''++ OpenVibeJS_Server_PlayerDeath( pPlayer, pKiller, NULL );++'''++)++++s = replace_func(++ s,++ "OpenVibe_OnFrame",++ r'\s*',++ '''++ OpenVibeJS_ServerThink();++'''++)++++if "OpenVibeJS_Server_PlayerInitialSpawn( pPlayer );" not in s:++ m = re.search(r'void\s+OpenVibe_OnClientActive\s*\(\s*CHL2MP_Player\s*\*\s*pPlayer\s*\)\s*\{', s)++ if m:++ brace = s.find("{", m.end() - 1)++ depth = 0++ end = None++ for i in range(brace, len(s)):++ if s[i] == "{":++ depth += 1++ elif s[i] == "}":++ depth -= 1++ if depth == 0:++ end = i++ break++ if end is not None:++ s = s[:end] + "\n OpenVibeJS_Server_PlayerInitialSpawn( pPlayer );\n" + s[end:]++++p.write_text(s)++PY++++echo "[openvibe] add embedded JS console commands to openvibe_js_server.cpp"++++backup_file sdk/openvibe/server/hl2mp/openvibe_js_server.cpp++++python3 <<'PY'++from pathlib import Path++import re++++p = Path("sdk/openvibe/server/hl2mp/openvibe_js_server.cpp")++s = p.read_text()++++s = re.sub(r'\nstatic void OV_JSStatus_f\(\).*?static ConCommand ov_js_status\s*\(.*?\);\s*', '\n', s, flags=re.S)++s = re.sub(r'\nstatic void OV_JSFire_f\(.*?static ConCommand ov_js_fire\s*\(.*?\);\s*', '\n', s, flags=re.S)++s = re.sub(r'\nstatic void OV_JSCmd_f\(.*?static ConCommand ov_js_cmd\s*\(.*?\);\s*', '\n', s, flags=re.S)++++block = r'''++static void OV_JSStatus_f()++{++ Msg("[OV JS] enabled=%d running=%d mode=%s\n",++ ov_js_enabled.GetBool() ? 1 : 0,++ OpenVibeJS_IsRunning() ? 1 : 0,++ ov_mode.GetString());++}++++static ConCommand ov_js_status(++ "ov_js_status",++ OV_JSStatus_f,++ "Print OpenVibe JavaScript runtime status.",++ FCVAR_GAMEDLL++);++++static void OV_JSFire_f(const CCommand &args)++{++ if (args.ArgC() < 2)++ {++ Msg("Usage: ov_js_fire <HookName>\n");++ return;++ }++++ OpenVibeJS_EnsureStarted();++++ if (!OpenVibeJS_IsRunning())++ {++ Warning("[OV JS] runtime is not running.\n");++ return;++ }++++ g_OVServerJS.CallHookVoid(args[1]);++}++++static ConCommand ov_js_fire(++ "ov_js_fire",++ OV_JSFire_f,++ "Fire an OpenVibe JavaScript hook with no arguments.",++ FCVAR_GAMEDLL++);++++static void OV_JSCmd_f(const CCommand &args)++{++ OpenVibeJS_EnsureStarted();++++ if (!OpenVibeJS_IsRunning())++ {++ Warning("[OV JS] runtime is not running.\n");++ return;++ }++++ JSContext *ctx = g_OVServerJS.Context();++ JSValue payload = JS_NewString(ctx, args.ArgS() ? args.ArgS() : "");++++ JSValueConst argv[] = { payload };++ g_OVServerJS.CallHookVoid("ConsoleCommand", 1, argv);++++ JS_FreeValue(ctx, payload);++}++++static ConCommand ov_js_cmd(++ "ov_js_cmd",++ OV_JSCmd_f,++ "Send text to the OpenVibe embedded JavaScript ConsoleCommand hook.",++ FCVAR_GAMEDLL++);++'''++++s = s.rstrip() + "\n\n" + block.lstrip()++p.write_text(s)++PY++++echo "[openvibe] normalize base/hub JS ConsoleCommand handlers"++++cat > game/openvibe.games/js/gamemodes/base/server.js <<'JS_BASE'++const GM = {++ mode: "base",++ name: "OpenVibe Base",++++ Initialize() {++ OV.log("Base Initialize fired");++ },++++ MapInitialize(mapName) {++ OV.log(`Base MapInitialize: ${mapName}`);++ },++++ PlayerInitialSpawn(ply) {++ ply.chat("Welcome to OpenVibe: Source.");++ },++++ PlayerSpawn(_ply) {},++++ PlayerDeath(_victim, _attacker) {},++++ PlayerDisconnected(_ply) {},++++ PlayerSay(_ply, _text) {++ return undefined;++ },++++ ConsoleCommand(text) {++ OV.log(`ConsoleCommand: ${text}`);++ return undefined;++ },++++ Think() {}++};++++gamemode.set(GM);++JS_BASE++++cat > game/openvibe.games/js/gamemodes/hub/server.js <<'JS_HUB'++const GM = {++ mode: "hub",++ name: "OpenVibe Hub",++++ Initialize() {++ OV.log("Hub Initialize fired");++ },++++ MapInitialize(mapName) {++ OV.log(`Map initialized: ${mapName}`);++ },++++ PlayerInitialSpawn(ply) {++ ply.chat("Welcome to OpenVibe: Source JS runtime.");++ OV.broadcast(`${ply.name()} joined the hub.`);++ },++++ PlayerSpawn(ply) {++ ply.chat("PlayerSpawn hook fired.");++ },++++ PlayerSay(ply, text) {++ if (text === "!js") {++ ply.chat("JavaScript hooks are working.");++ return false;++ }++++ if (text === "!hp") {++ ply.chat(`Health: ${ply.health()}`);++ return false;++ }++++ if (text === "!players") {++ ply.chat(`Players online: ${OV.players().length}`);++ return false;++ }++++ return undefined;++ },++++ ConsoleCommand(text) {++ OV.log(`Hub ConsoleCommand: ${text}`);++++ if (text === "smoke") {++ OV.broadcast("OpenVibe embedded JS smoke test passed.");++ return false;++ }++++ return undefined;++ },++++ Think() {}++};++++gamemode.set(GM);++JS_HUB++++echo "[openvibe] apply SDK patch"++tools/apply-openvibe-sdk.sh++++if [[ "$RUN_BUILD" = "1" ]]; then++ echo "[openvibe] run SDK build"++ tools/build-sdk-linux.sh 2>&1 | tee "$HOME/ov-build.log"++ tail -80 "$HOME/ov-build.log"++else++ echo "[openvibe] skipping build because RUN_BUILD=$RUN_BUILD"++fi++++if [[ "$RUN_SETUP" = "1" && -x tools/setup-openvibe-bin.sh ]]; then++ echo "[openvibe] setup OpenVibe binaries"++ tools/setup-openvibe-bin.sh++fi++++echo++echo "[openvibe] done."++echo++echo "Next manual test:"++echo " OPENVIBE_SRCDS_MAP_DELAY=3 tools/dev-up.sh"++echo++echo "In server console:"++echo " ov_js_status"++echo " ov_js_fire Initialize"++echo " ov_js_cmd smoke"++echo++echo "In client:"++echo " connect 127.0.0.1:27015"++echo " say !js"++echo " say !hp"++echo " say !players"nnew file mode 100755@@@ -0,0 +1,441 @@++#!/usr/bin/env bash++set -euo pipefail++++ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"++SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"++STAMP="$(date +%Y%m%d-%H%M%S)"++++cd "$ROOT"++++backup_file() {++ local file="$1"++ [[ -f "$file" ]] && cp "$file" "$file.bak.$STAMP"++}++++echo "[openvibe] fixing duplicate ov_js_cmd and canonicalizing embedded JS server bridge"++++mkdir -p sdk/openvibe/server/hl2mp++++backup_file sdk/openvibe/server/hl2mp/openvibe_js_server.cpp++backup_file sdk/openvibe/server/hl2mp/openvibe_server.cpp++++cat > sdk/openvibe/server/hl2mp/openvibe_js_server.cpp <<'CPP'++#include "cbase.h"++#include "hl2mp_player.h"++#include "openvibe_js_server.h"++#include "openvibe/ov_js_runtime.h"++#include "openvibe/ov_js_player.h"++++#include "tier0/memdbgon.h"++++static ConVar ov_mode(++ "ov_mode",++ "hub",++ FCVAR_GAMEDLL,++ "OpenVibe mode: hub, prophunt, deathrun, fortwars, traitortown." );++++static ConVar ov_js_enabled(++ "ov_js_enabled",++ "1",++ FCVAR_GAMEDLL,++ "Enable OpenVibe JavaScript runtime." );++++static COpenVibeJSRuntime g_OVServerJS;++static bool g_OVServerJSStarted = false;++++static bool OpenVibeJS_IsRunning()++{++ return ov_js_enabled.GetBool() && g_OVServerJS.Context() != nullptr;++}++++static void OpenVibeJS_EnsureStarted()++{++ if ( g_OVServerJSStarted )++ return;++++ g_OVServerJSStarted = true;++++ if ( !ov_js_enabled.GetBool() )++ {++ Msg( "[OV JS] disabled by ov_js_enabled=0\n" );++ return;++ }++++ if ( g_OVServerJS.Init( true, ov_mode.GetString() ) )++ {++ Msg( "[OV JS] server runtime initialized for mode '%s'\n", ov_mode.GetString() );++ g_OVServerJS.CallHookVoid( "Initialize" );++++ JSContext *ctx = g_OVServerJS.Context();++ JSValue mapName = JS_NewString( ctx, gpGlobals ? STRING( gpGlobals->mapname ) : "" );++ JSValueConst argv[] = { mapName };++ g_OVServerJS.CallHookVoid( "MapInitialize", 1, argv );++ JS_FreeValue( ctx, mapName );++ }++ else++ {++ Warning( "[OV JS] server runtime failed to initialize\n" );++ }++}++++static void OpenVibeJS_CallConsoleCommand( const char *pszCommand )++{++ OpenVibeJS_EnsureStarted();++++ if ( !OpenVibeJS_IsRunning() )++ {++ Warning( "[OV JS] runtime is not running; command ignored: %s\n", pszCommand ? pszCommand : "" );++ return;++ }++++ JSContext *ctx = g_OVServerJS.Context();++ JSValue command = JS_NewString( ctx, pszCommand ? pszCommand : "" );++ JSValueConst argv[] = { command };++++ g_OVServerJS.CallHookVoid( "ConsoleCommand", 1, argv );++++ JS_FreeValue( ctx, command );++}++++void OpenVibeJS_ServerInit()++{++ OpenVibeJS_EnsureStarted();++}++++void OpenVibeJS_ServerShutdown()++{++ if ( OpenVibeJS_IsRunning() )++ g_OVServerJS.CallHookVoid( "Shutdown" );++++ g_OVServerJS.Shutdown();++ g_OVServerJSStarted = false;++}++++void OpenVibeJS_ServerThink()++{++ OpenVibeJS_EnsureStarted();++++ if ( !OpenVibeJS_IsRunning() )++ return;++++ g_OVServerJS.CallHookVoid( "Think" );++}++++void OpenVibeJS_Server_PlayerInitialSpawn( CHL2MP_Player *player )++{++ OpenVibeJS_EnsureStarted();++++ if ( !OpenVibeJS_IsRunning() || !player )++ return;++++ JSContext *ctx = g_OVServerJS.Context();++ JSValue ply = OVJS_NewPlayer( ctx, player );++++ JSValueConst argv[] = { ply };++ g_OVServerJS.CallHookVoid( "PlayerInitialSpawn", 1, argv );++++ JS_FreeValue( ctx, ply );++}++++void OpenVibeJS_Server_PlayerSpawn( CHL2MP_Player *player )++{++ OpenVibeJS_EnsureStarted();++++ if ( !OpenVibeJS_IsRunning() || !player )++ return;++++ JSContext *ctx = g_OVServerJS.Context();++ JSValue ply = OVJS_NewPlayer( ctx, player );++++ JSValueConst argv[] = { ply };++ g_OVServerJS.CallHookVoid( "PlayerSpawn", 1, argv );++++ JS_FreeValue( ctx, ply );++}++++void OpenVibeJS_Server_PlayerDeath( CHL2MP_Player *victim, CBaseEntity *attacker, CBaseEntity *inflictor )++{++ OpenVibeJS_EnsureStarted();++++ if ( !OpenVibeJS_IsRunning() || !victim )++ return;++++ JSContext *ctx = g_OVServerJS.Context();++++ JSValue jsVictim = OVJS_NewPlayer( ctx, victim );++ JSValue jsAttacker = JS_NULL;++++ if ( attacker && attacker->IsPlayer() )++ jsAttacker = OVJS_NewPlayer( ctx, ToHL2MPPlayer( static_cast<CBasePlayer *>( attacker ) ) );++++ JSValueConst argv[] = { jsVictim, jsAttacker };++ g_OVServerJS.CallHookVoid( "PlayerDeath", 2, argv );++++ JS_FreeValue( ctx, jsVictim );++ JS_FreeValue( ctx, jsAttacker );++}++++void OpenVibeJS_Server_PlayerDisconnected( CHL2MP_Player *player )++{++ OpenVibeJS_EnsureStarted();++++ if ( !OpenVibeJS_IsRunning() || !player )++ return;++++ JSContext *ctx = g_OVServerJS.Context();++ JSValue ply = OVJS_NewPlayer( ctx, player );++++ JSValueConst argv[] = { ply };++ g_OVServerJS.CallHookVoid( "PlayerDisconnected", 1, argv );++++ JS_FreeValue( ctx, ply );++}++++bool OpenVibeJS_Server_PlayerSay( CHL2MP_Player *player, const char *text )++{++ OpenVibeJS_EnsureStarted();++++ if ( !OpenVibeJS_IsRunning() || !player || !text )++ return false;++++ JSContext *ctx = g_OVServerJS.Context();++++ JSValue ply = OVJS_NewPlayer( ctx, player );++ JSValue msg = JS_NewString( ctx, text );++++ JSValueConst argv[] = { ply, msg };++++ bool value = false;++ bool returned = g_OVServerJS.CallHookBool( "PlayerSay", &value, 2, argv );++++ JS_FreeValue( ctx, ply );++ JS_FreeValue( ctx, msg );++++ return returned && value == false;++}++++static void OV_JSReload_f()++{++ OpenVibeJS_ServerShutdown();++ OpenVibeJS_ServerInit();++ Msg( "[OV JS] reloaded\n" );++}++++static ConCommand ov_js_reload(++ "ov_js_reload",++ OV_JSReload_f,++ "Reload OpenVibe JavaScript runtime.",++ FCVAR_GAMEDLL++);++++static void OV_JSStatus_f()++{++ Msg( "[OV JS] enabled=%d started=%d running=%d mode=%s\n",++ ov_js_enabled.GetBool() ? 1 : 0,++ g_OVServerJSStarted ? 1 : 0,++ OpenVibeJS_IsRunning() ? 1 : 0,++ ov_mode.GetString() );++}++++static ConCommand ov_js_status(++ "ov_js_status",++ OV_JSStatus_f,++ "Print OpenVibe JavaScript runtime status.",++ FCVAR_GAMEDLL++);++++static void OV_JSFire_f( const CCommand &args )++{++ if ( args.ArgC() < 2 )++ {++ Msg( "Usage: ov_js_fire <HookName>\n" );++ return;++ }++++ OpenVibeJS_EnsureStarted();++++ if ( !OpenVibeJS_IsRunning() )++ {++ Warning( "[OV JS] runtime is not running\n" );++ return;++ }++++ g_OVServerJS.CallHookVoid( args[1] );++}++++static ConCommand ov_js_fire(++ "ov_js_fire",++ OV_JSFire_f,++ "Fire an OpenVibe JavaScript hook with no arguments.",++ FCVAR_GAMEDLL++);++++static void OV_JSCmd_f( const CCommand &args )++{++ if ( args.ArgC() < 2 )++ {++ Msg( "Usage: ov_js_cmd <text>\n" );++ return;++ }++++ OpenVibeJS_CallConsoleCommand( args.ArgS() );++}++++static ConCommand ov_js_cmd(++ "ov_js_cmd",++ OV_JSCmd_f,++ "Send a ConsoleCommand event into the embedded OpenVibe JavaScript runtime.",++ FCVAR_GAMEDLL++);++CPP++++echo "[openvibe] canonical openvibe_js_server.cpp written"++++echo "[openvibe] removing stale legacy Node bridge remnants from openvibe_server.cpp"++++python3 <<'PY'++from pathlib import Path++import re++++p = Path("sdk/openvibe/server/hl2mp/openvibe_server.cpp")++s = p.read_text()++++# Remove legacy COpenVibeJsBridge class, if present.++s = re.sub(++ r'\n// ={10,}\n// OpenVibe\.JS - Scripting Engine C\+\+ Bridge\n// ={10,}\n\nclass COpenVibeJsBridge\b.*?\nstatic COpenVibeJsBridge g_OpenVibeJsBridge;\n',++ '\n',++ s,++ flags=re.S,++)++++# More tolerant fallback if banner text differs.++s = re.sub(++ r'\nclass COpenVibeJsBridge\b.*?\nstatic COpenVibeJsBridge g_OpenVibeJsBridge;\n',++ '\n',++ s,++ flags=re.S,++)++++# Remove old Node-bridge ov_js_cmd command if it survived.++s = re.sub(++ r'\nstatic void OV_JsCmd_f\s*\([^)]*\)\s*\{.*?\n\}\n\nstatic ConCommand ov_js_cmd\s*\(.*?\);\n',++ '\n',++ s,++ flags=re.S,++)++++# Remove obvious unused Node/socket includes from our tracked patch file.++for inc in [++ '#include <thread>\n',++ '#include <mutex>\n',++ '#include <queue>\n',++ '#include <string>\n',++ '#include <sstream>\n',++ '#include <sys/socket.h>\n',++ '#include <sys/un.h>\n',++ '#include <unistd.h>\n',++]:++ s = s.replace(inc, '')++++# Ensure embedded JS include exists.++if '#include "openvibe_js_server.h"' not in s:++ s = re.sub(++ r'(#include\s+"hl2mp_player\.h"\s*\n)',++ r'\1#include "openvibe_js_server.h"\n',++ s,++ count=1,++ )++++p.write_text(s)++PY++++echo "[openvibe] removing duplicate ov_js_cmd from already-applied SDK file if present"++++SDK_SERVER_JS="$SDK/src/game/server/hl2mp/openvibe_js_server.cpp"++if [[ -f "$SDK_SERVER_JS" ]]; then++ cp "$SDK_SERVER_JS" "$SDK_SERVER_JS.bak.$STAMP"++ cp sdk/openvibe/server/hl2mp/openvibe_js_server.cpp "$SDK_SERVER_JS"++fi++++echo "[openvibe] ensure JS ConsoleCommand handlers are present"++++mkdir -p game/openvibe.games/js/gamemodes/base game/openvibe.games/js/gamemodes/hub++++python3 <<'PY'++from pathlib import Path++++def ensure_console_handler(path: str, marker: str, handler_src: str):++ p = Path(path)++ s = p.read_text() if p.exists() else "const GM = {\n};\n\ngamemode.set(GM);\n"++ if marker in s:++ return++ idx = s.rfind("};")++ if idx == -1:++ s += "\n" + handler_src + "\n"++ else:++ before = s[:idx].rstrip()++ after = s[idx:]++ if before.endswith("{"):++ before = before + "\n" + handler_src.strip()++ else:++ before = before + ",\n\n" + handler_src.strip()++ s = before + "\n" + after++ p.write_text(s)++++ensure_console_handler(++ "game/openvibe.games/js/gamemodes/base/server.js",++ "Base ConsoleCommand",++ '''ConsoleCommand(text) {++ OV.log(`Base ConsoleCommand: ${text}`);++ }'''++)++++ensure_console_handler(++ "game/openvibe.games/js/gamemodes/hub/server.js",++ "Hub ConsoleCommand",++ '''ConsoleCommand(text) {++ OV.log(`Hub ConsoleCommand: ${text}`);++ if (text === "smoke") {++ OV.broadcast("Embedded JS smoke command worked.");++ }++ }'''++)++PY++++echo "[openvibe] apply SDK patch"++tools/apply-openvibe-sdk.sh++++echo "[openvibe] verify only one ov_js_cmd exists in SDK copied source"++grep -n "ConCommand ov_js_cmd" "$SDK/src/game/server/hl2mp/openvibe_js_server.cpp" || true++count="$(grep -c "ConCommand ov_js_cmd" "$SDK/src/game/server/hl2mp/openvibe_js_server.cpp" || true)"++if [[ "$count" != "1" ]]; then++ echo "[openvibe] ERROR: expected exactly one ov_js_cmd, got $count" >&2++ exit 1++fi++++echo "[openvibe] build SDK"++tools/build-sdk-linux.sh 2>&1 | tee "$HOME/ov-build.log"++++echo "[openvibe] setup OpenVibe bin"++if [[ -x tools/setup-openvibe-bin.sh ]]; then++ tools/setup-openvibe-bin.sh++else++ echo "[openvibe] WARNING: tools/setup-openvibe-bin.sh not found/executable; skipping"++fi++++echo++echo "[openvibe] phase build complete."++echo++echo "Next runtime test:"++echo " OPENVIBE_SRCDS_MAP_DELAY=3 tools/dev-up.sh"++echo++echo "Server console tests:"++echo " ov_js_status"++echo " ov_js_fire Initialize"++echo " ov_js_cmd smoke"++echo++echo "Client tests:"++echo " connect 127.0.0.1:27015"++echo " say !js"++echo " say !hp"++echo " say !players"nnew file mode 100755@@@ -0,0 +1,383 @@++#!/usr/bin/env bash++set -euo pipefail++++ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"++SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"++STAMP="$(date +%Y%m%d-%H%M%S)"++RUN_BUILD="${RUN_BUILD:-1}"++++cd "$ROOT"++++backup_file() {++ local file="$1"++ [[ -f "$file" ]] && cp "$file" "$file.bak.$STAMP"++}++++need_file() {++ [[ -f "$1" ]] || { echo "[openvibe] missing required file: $1" >&2; exit 1; }++}++++need_file "sdk/openvibe/server/hl2mp/openvibe_server.cpp"++need_file "sdk/openvibe/server/hl2mp/openvibe_js_server.cpp"++need_file "sdk/openvibe/server/hl2mp/openvibe_js_server.h"++need_file "tools/apply-openvibe-sdk.sh"++need_file "tools/build-quickjs-lib.sh"++++echo "[openvibe] next phase: remove legacy Node bridge, add embedded JS console hooks, rebuild"++++mkdir -p tools++++backup_file "sdk/openvibe/server/hl2mp/openvibe_server.cpp"++backup_file "sdk/openvibe/server/hl2mp/openvibe_js_server.cpp"++backup_file "sdk/openvibe/server/hl2mp/openvibe_js_server.h"++backup_file "tools/apply-openvibe-sdk.sh"++++python3 <<'PY'++from pathlib import Path++import re++++p = Path("sdk/openvibe/server/hl2mp/openvibe_server.cpp")++s = p.read_text()++++# Make sure the embedded JS bridge header is included.++if '#include "openvibe_js_server.h"' not in s:++ s = re.sub(++ r'(#include\s+"hl2mp_player\.h"\s*\n)',++ r'\1#include "openvibe_js_server.h"\n',++ s,++ count=1,++ )++++# Remove legacy Node/Unix-socket bridge includes that are no longer needed.++for inc in [++ '#include <thread>',++ '#include <mutex>',++ '#include <queue>',++ '#include <string>',++ '#include <condition_variable>',++ '#include <sys/socket.h>',++ '#include <sys/un.h>',++ '#include <unistd.h>',++ '#include <fcntl.h>',++ '#include <sstream>',++ '#include <sys/select.h>',++ '#include <sys/time.h>',++]:++ s = s.replace(inc + "\n", "")++++bottom = r'''++// ======================================\n// OpenVibe embedded JavaScript hook bridge\n// ======================================++++void OpenVibe_OnClientDisconnect( CBasePlayer *pPlayer )++{++ CHL2MP_Player *pHL2MP = ToHL2MPPlayer( pPlayer );++ if ( pHL2MP )++ OpenVibeJS_Server_PlayerDisconnected( pHL2MP );++}++++void OpenVibe_OnPlayerDeath( CHL2MP_Player *pPlayer, CBaseEntity *pKiller )++{++ OpenVibeJS_Server_PlayerDeath( pPlayer, pKiller, NULL );++}++++void OpenVibe_OnFrame()++{++ OpenVibeJS_ServerThink();++}++'''++++# Remove the huge legacy COpenVibeJsBridge class and old ov_js_cmd that spawned Node.++marker = "// ==========================================\n// OpenVibe.JS - Scripting Engine C++ Bridge"++start = s.find(marker)++if start == -1:++ # The exact banner can vary because of long ===== lines. Fall back to class start.++ start = s.find("class COpenVibeJsBridge")++ if start != -1:++ # Include preceding banner if present.++ banner = s.rfind("// =================================", 0, start)++ if banner != -1:++ start = banner++++if start != -1:++ s = s[:start].rstrip() + "\n\n" + bottom + "\n"++else:++ # If already cleaned, normalize the bottom hook functions only.++ def replace_function(src, regex, body):++ m = re.search(regex, src)++ if not m:++ return src++ brace = src.find('{', m.end())++ if brace < 0:++ return src++ depth = 0++ end = None++ for i in range(brace, len(src)):++ if src[i] == '{':++ depth += 1++ elif src[i] == '}':++ depth -= 1++ if depth == 0:++ end = i++ break++ if end is None:++ return src++ return src[:brace+1] + "\n" + body.strip() + "\n" + src[end:]++++ s = replace_function(s, r'void\s+OpenVibe_OnClientDisconnect\s*\(\s*CBasePlayer\s*\*\s*pPlayer\s*\)',++ ' CHL2MP_Player *pHL2MP = ToHL2MPPlayer( pPlayer );\n if ( pHL2MP )\n OpenVibeJS_Server_PlayerDisconnected( pHL2MP );')++ s = replace_function(s, r'void\s+OpenVibe_OnPlayerDeath\s*\(\s*CHL2MP_Player\s*\*\s*pPlayer\s*,\s*CBaseEntity\s*\*\s*pKiller\s*\)',++ ' OpenVibeJS_Server_PlayerDeath( pPlayer, pKiller, NULL );')++ s = replace_function(s, r'void\s+OpenVibe_OnFrame\s*\(\s*\)', ' OpenVibeJS_ServerThink();')++++p.write_text(s)++print("[openvibe] cleaned legacy Node bridge from openvibe_server.cpp")++PY++++cat > sdk/openvibe/server/hl2mp/openvibe_js_server.h <<'CPP_H'++#pragma once++++class CHL2MP_Player;++class CBasePlayer;++class CBaseEntity;++++void OpenVibeJS_ServerInit();++void OpenVibeJS_ServerShutdown();++void OpenVibeJS_ServerThink();++++void OpenVibeJS_Server_PlayerInitialSpawn(CHL2MP_Player *player);++void OpenVibeJS_Server_PlayerSpawn(CHL2MP_Player *player);++void OpenVibeJS_Server_PlayerDeath(CHL2MP_Player *victim, CBaseEntity *attacker, CBaseEntity *inflictor);++void OpenVibeJS_Server_PlayerDisconnected(CHL2MP_Player *player);++bool OpenVibeJS_Server_PlayerSay(CHL2MP_Player *player, const char *text);++++void OpenVibeJS_Server_ConsoleCommand(const char *text);++void OpenVibeJS_Server_FireHook(const char *hookName);++CPP_H++++python3 <<'PY'++from pathlib import Path++++p = Path("sdk/openvibe/server/hl2mp/openvibe_js_server.cpp")++s = p.read_text()++++if "OpenVibeJS_Server_ConsoleCommand" not in s:++ insert = r'''++void OpenVibeJS_Server_ConsoleCommand(const char *text)++{++ OpenVibeJS_EnsureStarted();++++ if (!OpenVibeJS_IsRunning() || !text)++ return;++++ JSContext *ctx = g_OVServerJS.Context();++ JSValue arg = JS_NewString(ctx, text);++++ JSValueConst argv[] = { arg };++ g_OVServerJS.CallHookVoid("ConsoleCommand", 1, argv);++++ JS_FreeValue(ctx, arg);++}++++void OpenVibeJS_Server_FireHook(const char *hookName)++{++ OpenVibeJS_EnsureStarted();++++ if (!OpenVibeJS_IsRunning() || !hookName || !hookName[0])++ return;++++ g_OVServerJS.CallHookVoid(hookName);++}++++static void OV_JsCmd_f(const CCommand &args)++{++ if (args.ArgC() < 2)++ {++ Msg("Usage: ov_js_cmd <text>\n");++ return;++ }++++ OpenVibeJS_Server_ConsoleCommand(args.ArgS());++}++++static ConCommand ov_js_cmd(++ "ov_js_cmd",++ OV_JsCmd_f,++ "Send a string to the embedded OpenVibe JS ConsoleCommand hook.",++ FCVAR_GAMEDLL++);++++static void OV_JSFire_f(const CCommand &args)++{++ if (args.ArgC() < 2)++ {++ Msg("Usage: ov_js_fire <HookName>\n");++ return;++ }++++ OpenVibeJS_Server_FireHook(args[1]);++}++++static ConCommand ov_js_fire(++ "ov_js_fire",++ OV_JSFire_f,++ "Fire an embedded OpenVibe JS hook by name.",++ FCVAR_GAMEDLL++);++++static void OV_JSStatus_f()++{++ Msg("[OV JS] enabled=%d running=%d mode=%s map=%s\n",++ ov_js_enabled.GetBool() ? 1 : 0,++ OpenVibeJS_IsRunning() ? 1 : 0,++ ov_mode.GetString(),++ gpGlobals ? STRING(gpGlobals->mapname) : "" );++}++++static ConCommand ov_js_status(++ "ov_js_status",++ OV_JSStatus_f,++ "Print embedded OpenVibe JS runtime status.",++ FCVAR_GAMEDLL++);++'''++ # Put commands after ov_js_reload command block.++ s = s.rstrip() + "\n\n" + insert.lstrip() + "\n"++++p.write_text(s)++print("[openvibe] added embedded ov_js_cmd / ov_js_fire / ov_js_status commands")++PY++++python3 <<'PY'++from pathlib import Path++++for path in [++ Path("game/openvibe.games/js/gamemodes/base/server.js"),++ Path("game/openvibe.games/js/gamemodes/hub/server.js"),++]:++ if not path.exists():++ continue++ s = path.read_text()++ if "ConsoleCommand(" in s:++ continue++++ snippet = '''++++ ConsoleCommand(text) {++ OV.log(`ConsoleCommand: ${text}`);++++ if (text === "smoke" || text === "test") {++ OV.broadcast("Embedded JS ConsoleCommand hook is working.");++ return false;++ }++++ return undefined;++ },++'''++++ marker = "\n Think()"++ if marker in s:++ s = s.replace(marker, snippet + marker, 1)++ else:++ s = s.replace("\n};", snippet.rstrip(",\n") + "\n};", 1)++ path.write_text(s)++ print(f"[openvibe] added ConsoleCommand hook to {path}")++PY++++echo "[openvibe] cleaning apply-openvibe-sdk.sh duplicate stale link block"++python3 <<'PY'++from pathlib import Path++import re++++p = Path("tools/apply-openvibe-sdk.sh")++s = p.read_text()++++# Remove stale block after final echo, if present. It is harmless but confusing.++s = re.sub(++ r'\n\n# Link prebuilt QuickJS C static library\. QuickJS must be compiled as C, not C\+\+\.\nif ! grep -q '\''libquickjs_openvibe'\'' "\$SERVER_VPC"; then\n perl -0pi -e '\''s/\(\\\$File\\s\+"hl2mp\\\\openvibe_js_server\\\.cpp"\\n\)/\$1\\t\\t\\t\\\$Lib\\t"\.\.\\\\shared\\\\openvibe\\\\third_party\\\\quickjs\\\\build\\\\libquickjs_openvibe"\\n/s'\'' "\$SERVER_VPC"\n echo "\[openvibe-sdk\] linked QuickJS static library"\nfi\n*',++ '\n',++ s,++ flags=re.S,++)++++# Ensure final V2 block exists.++if "OPENVIBE_FIX_QUICKJS_LINK_FINAL_V2" not in s:++ final_block = r'''++++# OPENVIBE_FIX_QUICKJS_LINK_FINAL_V2++# QuickJS is C, so do not compile its .c files through Source SDK/VPC C++.++# We build libquickjs_openvibe.a with cc and link it as a library.++if [[ -f "$SERVER_VPC" ]]; then++ perl -0pi -e '++ s/^.*quickjs\\quickjs\.c.*\n//mg;++ s/^.*quickjs\\libregexp\.c.*\n//mg;++ s/^.*quickjs\\libunicode\.c.*\n//mg;++ s/^.*quickjs\\cutils\.c.*\n//mg;++ s/^.*quickjs\\dtoa\.c.*\n//mg;++ s/^.*quickjs\\libbf\.c.*\n//mg;++ ' "$SERVER_VPC"++++ perl -0pi -e 's/^.*libquickjs_openvibe(?:\.a)?".*\n//mg' "$SERVER_VPC"++++ perl -0pi -e 's/(\$File\s+"hl2mp\\openvibe_js_server\.cpp"\n)/$1\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s' "$SERVER_VPC"++++ echo "[openvibe-sdk] linked QuickJS static library"++fi++'''++ s = s.replace('echo "[openvibe-sdk] Source SDK OpenVibe patch applied"', final_block + '\n\necho "[openvibe-sdk] Source SDK OpenVibe patch applied"')++++p.write_text(s)++PY++++echo "[openvibe] writing embedded JS runtime smoke helper"++cat > tools/smoke-embedded-js-runtime.sh <<'SMOKE'++#!/usr/bin/env bash++set -euo pipefail++++ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"++cd "$ROOT"++++tools/apply-openvibe-sdk.sh++tools/build-sdk-linux.sh++tools/setup-openvibe-bin.sh++++echo++cat <<'MSG'++Embedded JS build/setup completed.++++Start the dev stack:++ OPENVIBE_SRCDS_MAP_DELAY=3 tools/dev-up.sh++++Then in server console or via rcon/tmux pane, test:++ ov_js_status++ ov_js_fire Initialize++ ov_js_cmd smoke++++Then in game:++ connect 127.0.0.1:27015++ say !js++ say !hp++ say !players++MSG++SMOKE++chmod +x tools/smoke-embedded-js-runtime.sh++++echo "[openvibe] applying SDK patch"++tools/apply-openvibe-sdk.sh++++if [[ "$RUN_BUILD" = "1" ]]; then++ echo "[openvibe] running SDK build"++ tools/build-sdk-linux.sh 2>&1 | tee "$HOME/ov-build.log"++ echo "[openvibe] running setup-openvibe-bin.sh"++ tools/setup-openvibe-bin.sh++fi++++echo++echo "[openvibe] next phase complete. Useful checks:"++echo " grep -R \"COpenVibeJsBridge\|SpawnNodeJs\|system( cmd\" sdk/openvibe/server/hl2mp/openvibe_server.cpp || true"++echo " grep -nE 'ov_js_cmd|ov_js_fire|ov_js_status' sdk/openvibe/server/hl2mp/openvibe_js_server.cpp"++echo " tail -80 ~/ov-build.log"++echo++echo "Commit after testing:"++echo " git add ."++echo " git commit -m \"Remove legacy Node JS bridge and add embedded JS console hooks\""++echo " git push"
@@ -6,6 +6,10 @@ const GM = {
@@ -6,6 +6,10 @@ const GM = {
OV.log("Base Initialize fired");
OV.log("Base Initialize fired");
},
},
+ MapInitialize(mapName) {
+ OV.log(`Base MapInitialize: ${mapName}`);
+ },
+
PlayerInitialSpawn(ply) {
PlayerInitialSpawn(ply) {
ply.chat("Welcome to OpenVibe: Source.");
ply.chat("Welcome to OpenVibe: Source.");
},
},
@@ -20,7 +24,16 @@ const GM = {
@@ -20,7 +24,16 @@ const GM = {
return undefined;
return undefined;
},
},
- Think() {}
+ ConsoleCommand(text) {
+ OV.log(`ConsoleCommand: ${text}`);
+ return undefined;
+ },
+
+ Think() {},
+
+ConsoleCommand(text) {
+ OV.log(`Base ConsoleCommand: ${text}`);
+ }
};
};
gamemode.set(GM);
gamemode.set(GM);
@@ -38,6 +38,17 @@ const GM = {
@@ -38,6 +38,17 @@ const GM = {
return undefined;
return undefined;
},
},
+ ConsoleCommand(text) {
+ OV.log(`Hub ConsoleCommand: ${text}`);
+
+ if (text === "smoke") {
+ OV.broadcast("OpenVibe embedded JS smoke test passed.");
+ return false;
+ }
+
+ return undefined;
+ },
+
Think() {}
Think() {}
};
};
@@ -28,34 +28,53 @@ static bool OpenVibeJS_IsRunning()
@@ -28,34 +28,53 @@ static bool OpenVibeJS_IsRunning()
static void OpenVibeJS_EnsureStarted()
static void OpenVibeJS_EnsureStarted()
{
{
- if (g_OVServerJSStarted)
+ if ( g_OVServerJSStarted )
return;
return;
g_OVServerJSStarted = true;
g_OVServerJSStarted = true;
- if (!ov_js_enabled.GetBool())
+ if ( !ov_js_enabled.GetBool() )
{
{
- Msg("[OV JS] disabled by ov_js_enabled=0\n");
+ Msg( "[OV JS] disabled by ov_js_enabled=0\n" );
return;
return;
}
}
- if (g_OVServerJS.Init(true, ov_mode.GetString()))
+ if ( g_OVServerJS.Init( true, ov_mode.GetString() ) )
{
{
- Msg("[OV JS] server runtime initialized for mode '%s'\n", ov_mode.GetString());
- g_OVServerJS.CallHookVoid("Initialize");
+ Msg( "[OV JS] server runtime initialized for mode '%s'\n", ov_mode.GetString() );
+ g_OVServerJS.CallHookVoid( "Initialize" );
JSContext *ctx = g_OVServerJS.Context();
JSContext *ctx = g_OVServerJS.Context();
- JSValue mapName = JS_NewString(ctx, gpGlobals ? STRING(gpGlobals->mapname) : "");
+ JSValue mapName = JS_NewString( ctx, gpGlobals ? STRING( gpGlobals->mapname ) : "" );
JSValueConst argv[] = { mapName };
JSValueConst argv[] = { mapName };
- g_OVServerJS.CallHookVoid("MapInitialize", 1, argv);
- JS_FreeValue(ctx, mapName);
+ g_OVServerJS.CallHookVoid( "MapInitialize", 1, argv );
+ JS_FreeValue( ctx, mapName );
}
}
else
else
{
{
- Warning("[OV JS] server runtime failed to initialize\n");
+ Warning( "[OV JS] server runtime failed to initialize\n" );
}
}
}
}
+static void OpenVibeJS_CallConsoleCommand( const char *pszCommand )
+{
+ OpenVibeJS_EnsureStarted();
+
+ if ( !OpenVibeJS_IsRunning() )
+ {
+ Warning( "[OV JS] runtime is not running; command ignored: %s\n", pszCommand ? pszCommand : "" );
+ return;
+ }
+
+ JSContext *ctx = g_OVServerJS.Context();
+ JSValue command = JS_NewString( ctx, pszCommand ? pszCommand : "" );
+ JSValueConst argv[] = { command };
+
+ g_OVServerJS.CallHookVoid( "ConsoleCommand", 1, argv );
+
+ JS_FreeValue( ctx, command );
+}
+
void OpenVibeJS_ServerInit()
void OpenVibeJS_ServerInit()
{
{
OpenVibeJS_EnsureStarted();
OpenVibeJS_EnsureStarted();
@@ -63,8 +82,8 @@ void OpenVibeJS_ServerInit()
@@ -63,8 +82,8 @@ void OpenVibeJS_ServerInit()
void OpenVibeJS_ServerShutdown()
void OpenVibeJS_ServerShutdown()
{
{
- if (OpenVibeJS_IsRunning())
- g_OVServerJS.CallHookVoid("Shutdown");
+ if ( OpenVibeJS_IsRunning() )
+ g_OVServerJS.CallHookVoid( "Shutdown" );
g_OVServerJS.Shutdown();
g_OVServerJS.Shutdown();
g_OVServerJSStarted = false;
g_OVServerJSStarted = false;
@@ -74,101 +93,101 @@ void OpenVibeJS_ServerThink()
@@ -74,101 +93,101 @@ void OpenVibeJS_ServerThink()
{
{
OpenVibeJS_EnsureStarted();
OpenVibeJS_EnsureStarted();
- if (!OpenVibeJS_IsRunning())
+ if ( !OpenVibeJS_IsRunning() )
return;
return;
- g_OVServerJS.CallHookVoid("Think");
+ g_OVServerJS.CallHookVoid( "Think" );
}
}
-void OpenVibeJS_Server_PlayerInitialSpawn(CHL2MP_Player *player)
+void OpenVibeJS_Server_PlayerInitialSpawn( CHL2MP_Player *player )
{
{
OpenVibeJS_EnsureStarted();
OpenVibeJS_EnsureStarted();
- if (!OpenVibeJS_IsRunning() || !player)
+ if ( !OpenVibeJS_IsRunning() || !player )
return;
return;
JSContext *ctx = g_OVServerJS.Context();
JSContext *ctx = g_OVServerJS.Context();
- JSValue ply = OVJS_NewPlayer(ctx, player);
+ JSValue ply = OVJS_NewPlayer( ctx, player );
JSValueConst argv[] = { ply };
JSValueConst argv[] = { ply };
- g_OVServerJS.CallHookVoid("PlayerInitialSpawn", 1, argv);
+ g_OVServerJS.CallHookVoid( "PlayerInitialSpawn", 1, argv );
- JS_FreeValue(ctx, ply);
+ JS_FreeValue( ctx, ply );
}
}
-void OpenVibeJS_Server_PlayerSpawn(CHL2MP_Player *player)
+void OpenVibeJS_Server_PlayerSpawn( CHL2MP_Player *player )
{
{
OpenVibeJS_EnsureStarted();
OpenVibeJS_EnsureStarted();
- if (!OpenVibeJS_IsRunning() || !player)
+ if ( !OpenVibeJS_IsRunning() || !player )
return;
return;
JSContext *ctx = g_OVServerJS.Context();
JSContext *ctx = g_OVServerJS.Context();
- JSValue ply = OVJS_NewPlayer(ctx, player);
+ JSValue ply = OVJS_NewPlayer( ctx, player );
JSValueConst argv[] = { ply };
JSValueConst argv[] = { ply };
- g_OVServerJS.CallHookVoid("PlayerSpawn", 1, argv);
+ g_OVServerJS.CallHookVoid( "PlayerSpawn", 1, argv );
- JS_FreeValue(ctx, ply);
+ JS_FreeValue( ctx, ply );
}
}
-void OpenVibeJS_Server_PlayerDeath(CHL2MP_Player *victim, CBaseEntity *attacker, CBaseEntity *inflictor)
+void OpenVibeJS_Server_PlayerDeath( CHL2MP_Player *victim, CBaseEntity *attacker, CBaseEntity *inflictor )
{
{
OpenVibeJS_EnsureStarted();
OpenVibeJS_EnsureStarted();
- if (!OpenVibeJS_IsRunning() || !victim)
+ if ( !OpenVibeJS_IsRunning() || !victim )
return;
return;
JSContext *ctx = g_OVServerJS.Context();
JSContext *ctx = g_OVServerJS.Context();
- JSValue jsVictim = OVJS_NewPlayer(ctx, victim);
+ JSValue jsVictim = OVJS_NewPlayer( ctx, victim );
JSValue jsAttacker = JS_NULL;
JSValue jsAttacker = JS_NULL;
- if (attacker && attacker->IsPlayer())
- jsAttacker = OVJS_NewPlayer(ctx, ToHL2MPPlayer(static_cast<CBasePlayer *>(attacker)));
+ if ( attacker && attacker->IsPlayer() )
+ jsAttacker = OVJS_NewPlayer( ctx, ToHL2MPPlayer( static_cast<CBasePlayer *>( attacker ) ) );
JSValueConst argv[] = { jsVictim, jsAttacker };
JSValueConst argv[] = { jsVictim, jsAttacker };
- g_OVServerJS.CallHookVoid("PlayerDeath", 2, argv);
+ g_OVServerJS.CallHookVoid( "PlayerDeath", 2, argv );
- JS_FreeValue(ctx, jsVictim);
- JS_FreeValue(ctx, jsAttacker);
+ JS_FreeValue( ctx, jsVictim );
+ JS_FreeValue( ctx, jsAttacker );
}
}
-void OpenVibeJS_Server_PlayerDisconnected(CHL2MP_Player *player)
+void OpenVibeJS_Server_PlayerDisconnected( CHL2MP_Player *player )
{
{
OpenVibeJS_EnsureStarted();
OpenVibeJS_EnsureStarted();
- if (!OpenVibeJS_IsRunning() || !player)
+ if ( !OpenVibeJS_IsRunning() || !player )
return;
return;
JSContext *ctx = g_OVServerJS.Context();
JSContext *ctx = g_OVServerJS.Context();
- JSValue ply = OVJS_NewPlayer(ctx, player);
+ JSValue ply = OVJS_NewPlayer( ctx, player );
JSValueConst argv[] = { ply };
JSValueConst argv[] = { ply };
- g_OVServerJS.CallHookVoid("PlayerDisconnected", 1, argv);
+ g_OVServerJS.CallHookVoid( "PlayerDisconnected", 1, argv );
- JS_FreeValue(ctx, ply);
+ JS_FreeValue( ctx, ply );
}
}
-bool OpenVibeJS_Server_PlayerSay(CHL2MP_Player *player, const char *text)
+bool OpenVibeJS_Server_PlayerSay( CHL2MP_Player *player, const char *text )
{
{
OpenVibeJS_EnsureStarted();
OpenVibeJS_EnsureStarted();
- if (!OpenVibeJS_IsRunning() || !player || !text)
+ if ( !OpenVibeJS_IsRunning() || !player || !text )
return false;
return false;
JSContext *ctx = g_OVServerJS.Context();
JSContext *ctx = g_OVServerJS.Context();
- JSValue ply = OVJS_NewPlayer(ctx, player);
- JSValue msg = JS_NewString(ctx, text);
+ JSValue ply = OVJS_NewPlayer( ctx, player );
+ JSValue msg = JS_NewString( ctx, text );
JSValueConst argv[] = { ply, msg };
JSValueConst argv[] = { ply, msg };
bool value = false;
bool value = false;
- bool returned = g_OVServerJS.CallHookBool("PlayerSay", &value, 2, argv);
+ bool returned = g_OVServerJS.CallHookBool( "PlayerSay", &value, 2, argv );
- JS_FreeValue(ctx, ply);
- JS_FreeValue(ctx, msg);
+ JS_FreeValue( ctx, ply );
+ JS_FreeValue( ctx, msg );
return returned && value == false;
return returned && value == false;
}
}
@@ -177,7 +196,7 @@ static void OV_JSReload_f()
@@ -177,7 +196,7 @@ static void OV_JSReload_f()
{
{
OpenVibeJS_ServerShutdown();
OpenVibeJS_ServerShutdown();
OpenVibeJS_ServerInit();
OpenVibeJS_ServerInit();
- Msg("[OV JS] reloaded\n");
+ Msg( "[OV JS] reloaded\n" );
}
}
static ConCommand ov_js_reload(
static ConCommand ov_js_reload(
@@ -186,3 +205,63 @@ static ConCommand ov_js_reload(
@@ -186,3 +205,63 @@ static ConCommand ov_js_reload(
"Reload OpenVibe JavaScript runtime.",
"Reload OpenVibe JavaScript runtime.",
FCVAR_GAMEDLL
FCVAR_GAMEDLL
);
);
+
+static void OV_JSStatus_f()
+{
+ Msg( "[OV JS] enabled=%d started=%d running=%d mode=%s\n",
+ ov_js_enabled.GetBool() ? 1 : 0,
+ g_OVServerJSStarted ? 1 : 0,
+ OpenVibeJS_IsRunning() ? 1 : 0,
+ ov_mode.GetString() );
+}
+
+static ConCommand ov_js_status(
+ "ov_js_status",
+ OV_JSStatus_f,
+ "Print OpenVibe JavaScript runtime status.",
+ FCVAR_GAMEDLL
+);
+
+static void OV_JSFire_f( const CCommand &args )
+{
+ if ( args.ArgC() < 2 )
+ {
+ Msg( "Usage: ov_js_fire <HookName>\n" );
+ return;
+ }
+
+ OpenVibeJS_EnsureStarted();
+
+ if ( !OpenVibeJS_IsRunning() )
+ {
+ Warning( "[OV JS] runtime is not running\n" );
+ return;
+ }
+
+ g_OVServerJS.CallHookVoid( args[1] );
+}
+
+static ConCommand ov_js_fire(
+ "ov_js_fire",
+ OV_JSFire_f,
+ "Fire an OpenVibe JavaScript hook with no arguments.",
+ FCVAR_GAMEDLL
+);
+
+static void OV_JSCmd_f( const CCommand &args )
+{
+ if ( args.ArgC() < 2 )
+ {
+ Msg( "Usage: ov_js_cmd <text>\n" );
+ return;
+ }
+
+ OpenVibeJS_CallConsoleCommand( args.ArgS() );
+}
+
+static ConCommand ov_js_cmd(
+ "ov_js_cmd",
+ OV_JSCmd_f,
+ "Send a ConsoleCommand event into the embedded OpenVibe JavaScript runtime.",
+ FCVAR_GAMEDLL
+);
@@ -13,3 +13,6 @@ void OpenVibeJS_Server_PlayerSpawn(CHL2MP_Player *player);
@@ -13,3 +13,6 @@ void OpenVibeJS_Server_PlayerSpawn(CHL2MP_Player *player);
void OpenVibeJS_Server_PlayerDeath(CHL2MP_Player *victim, CBaseEntity *attacker, CBaseEntity *inflictor);
void OpenVibeJS_Server_PlayerDeath(CHL2MP_Player *victim, CBaseEntity *attacker, CBaseEntity *inflictor);
void OpenVibeJS_Server_PlayerDisconnected(CHL2MP_Player *player);
void OpenVibeJS_Server_PlayerDisconnected(CHL2MP_Player *player);
bool OpenVibeJS_Server_PlayerSay(CHL2MP_Player *player, const char *text);
bool OpenVibeJS_Server_PlayerSay(CHL2MP_Player *player, const char *text);
+
+void OpenVibeJS_Server_ConsoleCommand(const char *text);
+void OpenVibeJS_Server_FireHook(const char *hookName);
@@ -17,18 +17,6 @@
@@ -17,18 +17,6 @@
#include "steam/isteamhttp.h"
#include "steam/isteamhttp.h"
// Standard library and socket includes for JS bridge
// Standard library and socket includes for JS bridge
-#include <thread>
-#include <mutex>
-#include <queue>
-#include <string>
-#include <condition_variable>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <sstream>
-#include <sys/select.h>
-#include <sys/time.h>
// memdbgon must be the last include file in a .cpp file.
// memdbgon must be the last include file in a .cpp file.
#include "tier0/memdbgon.h"
#include "tier0/memdbgon.h"
@@ -382,413 +370,9 @@ static ConCommand ov_fortwars_spawn_cmd(
@@ -382,413 +370,9 @@ static ConCommand ov_fortwars_spawn_cmd(
// =================================══════════════════════════════════════════
// =================================══════════════════════════════════════════
// OpenVibe.JS - Scripting Engine C++ Bridge
// OpenVibe.JS - Scripting Engine C++ Bridge
-// =================================══════════════════════════════════════════
-
-class COpenVibeJsBridge
-{
-private:
- std::thread m_workerThread;
- std::mutex m_outgoingMutex;
- std::queue<std::string> m_outgoingQueue;
- std::mutex m_incomingMutex;
- std::queue<std::string> m_incomingQueue;
- bool m_bRunning;
- bool m_bConnected;
- int m_socketFd;
- std::string m_socketPath;
- std::string m_incomingBuffer;
-
- void ThreadFunc()
- {
- while ( m_bRunning )
- {
- if ( !m_bConnected )
- {
- m_socketFd = socket( AF_UNIX, SOCK_STREAM, 0 );
- if ( m_socketFd >= 0 )
- {
- struct sockaddr_un addr;
- memset( &addr, 0, sizeof( addr ) );
- addr.sun_family = AF_UNIX;
- Q_strncpy( addr.sun_path, m_socketPath.c_str(), sizeof( addr.sun_path ) );
-
- if ( connect( m_socketFd, (struct sockaddr*)&addr, sizeof( addr ) ) == 0 )
- {
- m_bConnected = true;
- Msg( "[OV JS] Connected to Node.js scripting bridge at %s\n", m_socketPath.c_str() );
- }
- else
- {
- close( m_socketFd );
- m_socketFd = -1;
- SpawnNodeJs();
- std::this_thread::sleep_for( std::chrono::milliseconds( 2000 ) );
- continue;
- }
- }
- }
-
- if ( m_bConnected )
- {
- fd_set readFds;
- fd_set writeFds;
- FD_ZERO( &readFds );
- FD_ZERO( &writeFds );
- FD_SET( m_socketFd, &readFds );
-
- bool bHasWrite = false;
- {
- std::lock_guard<std::mutex> lock( m_outgoingMutex );
- bHasWrite = !m_outgoingQueue.empty();
- }
- if ( bHasWrite )
- {
- FD_SET( m_socketFd, &writeFds );
- }
-
- struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = 50000;
-
- int activity = select( m_socketFd + 1, &readFds, &writeFds, NULL, &tv );
- if ( activity < 0 )
- {
- Disconnect();
- continue;
- }
-
- if ( FD_ISSET( m_socketFd, &readFds ) )
- {
- char buffer[4096];
- int bytesRead = recv( m_socketFd, buffer, sizeof( buffer ) - 1, 0 );
- if ( bytesRead <= 0 )
- {
- Disconnect();
- continue;
- }
- buffer[bytesRead] = '\0';
- ProcessIncomingBuffer( buffer );
- }
-
- if ( bHasWrite && FD_ISSET( m_socketFd, &writeFds ) )
- {
- std::string msg;
- {
- std::lock_guard<std::mutex> lock( m_outgoingMutex );
- if ( !m_outgoingQueue.empty() )
- {
- msg = m_outgoingQueue.front();
- m_outgoingQueue.pop();
- }
- }
- if ( !msg.empty() )
- {
- int sent = send( m_socketFd, msg.c_str(), msg.length(), 0 );
- if ( sent < 0 )
- {
- Disconnect();
- continue;
- }
- }
- }
- }
- }
- }
-
- void SpawnNodeJs()
- {
- static float lastSpawnTime = 0.0f;
- float curTime = gpGlobals ? gpGlobals->realtime : 0.0f;
- if ( curTime > 0.0f && curTime - lastSpawnTime < 10.0f )
- {
- return;
- }
- lastSpawnTime = curTime;
-
- Msg( "[OV JS] Starting Node.js OpenVibe.JS runtime...\n" );
- char cmd[1024];
- const char *root = getenv( "OPENVIBE_ROOT" );
- if ( !root )
- {
- root = "/home/workstation/src/openvibe-source";
- }
- Q_snprintf( cmd, sizeof( cmd ), "node %s/engine/openvibe-js-runtime/bridge.js %s > /tmp/openvibe-js-%s.log 2>&1 &",
- root, m_socketPath.c_str(), ov_server_id.GetString() );
- int ret = system( cmd );
- (void)ret;
- }
-
- void Disconnect()
- {
- if ( m_socketFd >= 0 )
- {
- close( m_socketFd );
- m_socketFd = -1;
- }
- m_bConnected = false;
- Msg( "[OV JS] Disconnected from Node.js scripting bridge.\n" );
- }
-
- void ProcessIncomingBuffer( const char *data )
- {
- m_incomingBuffer += data;
- size_t pos;
- while ( ( pos = m_incomingBuffer.find( '\n' ) ) != std::string::npos )
- {
- std::string line = m_incomingBuffer.substr( 0, pos );
- m_incomingBuffer.erase( 0, pos + 1 );
- if ( !line.empty() )
- {
- std::lock_guard<std::mutex> lock( m_incomingMutex );
- m_incomingQueue.push( line );
- }
- }
- }
-
-public:
- COpenVibeJsBridge() :
- m_bRunning( false ),
- m_bConnected( false ),
- m_socketFd( -1 )
- {
- }
-
- ~COpenVibeJsBridge()
- {
- Stop();
- }
-
- void Start()
- {
- if ( m_bRunning ) return;
-
- char szSocket[256];
- Q_snprintf( szSocket, sizeof( szSocket ), "/tmp/openvibe-js-%s.sock", ov_server_id.GetString() );
- m_socketPath = szSocket;
-
- m_bRunning = true;
- m_workerThread = std::thread( &COpenVibeJsBridge::ThreadFunc, this );
- Msg( "[OV JS] Scripting bridge service thread started. socket=%s\n", m_socketPath.c_str() );
- }
-
- void Stop()
- {
- m_bRunning = false;
- if ( m_workerThread.joinable() )
- {
- m_workerThread.join();
- }
- Disconnect();
- }
-
- void SendMessage( const std::string &msg )
- {
- if ( !m_bConnected ) return;
- std::lock_guard<std::mutex> lock( m_outgoingMutex );
- m_outgoingQueue.push( msg + "\n" );
- }
-
- void OnPlayerJoined( CBasePlayer *pPlayer )
- {
- if ( !pPlayer ) return;
-
- char szSteamID[32];
- CSteamID steamID;
- if ( pPlayer->GetSteamID( &steamID ) )
- {
- Q_snprintf( szSteamID, sizeof( szSteamID ), "%llu", steamID.ConvertToUint64() );
- }
- else
- {
- Q_strncpy( szSteamID, "0", sizeof( szSteamID ) );
- }
-
- std::stringstream ss;
- ss << "{\"type\":\"player_join\",\"data\":{"
- << "\"steamId\":\"" << szSteamID << "\","
- << "\"name\":\"" << pPlayer->GetPlayerName() << "\""
- << "}}";
- SendMessage( ss.str() );
- }
-
- void OnPlayerLeft( CBasePlayer *pPlayer )
- {
- if ( !pPlayer ) return;
-
- char szSteamID[32];
- CSteamID steamID;
- if ( pPlayer->GetSteamID( &steamID ) )
- {
- Q_snprintf( szSteamID, sizeof( szSteamID ), "%llu", steamID.ConvertToUint64() );
- }
- else
- {
- Q_strncpy( szSteamID, "0", sizeof( szSteamID ) );
- }
- std::stringstream ss;
- ss << "{\"type\":\"player_leave\",\"data\":{"
- << "\"steamId\":\"" << szSteamID << "\""
- << "}}";
- SendMessage( ss.str() );
- }
- void OnPlayerDeath( CBasePlayer *pPlayer, CBaseEntity *pKiller )
- {
- if ( !pPlayer ) return;
-
- char szDeadSteamID[32];
- CSteamID deadSteamID;
- if ( pPlayer->GetSteamID( &deadSteamID ) )
- {
- Q_snprintf( szDeadSteamID, sizeof( szDeadSteamID ), "%llu", deadSteamID.ConvertToUint64() );
- }
- else
- {
- Q_strncpy( szDeadSteamID, "0", sizeof( szDeadSteamID ) );
- }
-
- char szKillerSteamID[32] = "0";
- if ( pKiller && pKiller->IsPlayer() )
- {
- CBasePlayer *pKillerPlayer = (CBasePlayer*)pKiller;
- CSteamID killerSteamID;
- if ( pKillerPlayer->GetSteamID( &killerSteamID ) )
- {
- Q_snprintf( szKillerSteamID, sizeof( szKillerSteamID ), "%llu", killerSteamID.ConvertToUint64() );
- }
- }
-
- std::stringstream ss;
- ss << "{\"type\":\"player_death\",\"data\":{"
- << "\"deadSteamId\":\"" << szDeadSteamID << "\","
- << "\"killerSteamId\":\"" << szKillerSteamID << "\""
- << "}}";
- SendMessage( ss.str() );
- }
-
- void OnCommand( const char *cmd )
- {
- std::stringstream ss;
- ss << "{\"type\":\"command\",\"data\":{"
- << "\"cmd\":\"" << cmd << "\""
- << "}}";
- SendMessage( ss.str() );
- }
-
- void Update()
- {
- if ( !m_bRunning )
- {
- Start();
- }
-
- static float lastTickTime = 0.0f;
- float curTime = gpGlobals->curtime;
- if ( curTime - lastTickTime >= 1.0f )
- {
- lastTickTime = curTime;
- SendMessage( "{\"type\":\"tick\",\"data\":{}}" );
- }
-
- std::queue<std::string> localQueue;
- {
- std::lock_guard<std::mutex> lock( m_incomingMutex );
- while ( !m_incomingQueue.empty() )
- {
- localQueue.push( m_incomingQueue.front() );
- m_incomingQueue.pop();
- }
- }
-
- while ( !localQueue.empty() )
- {
- std::string line = localQueue.front();
- localQueue.pop();
- ProcessMessage( line );
- }
- }
-
-private:
- void ProcessMessage( const std::string &line )
- {
- if ( line.find( "\"type\":\"broadcast\"" ) != std::string::npos )
- {
- size_t msgPos = line.find( "\"message\":\"" );
- if ( msgPos != std::string::npos )
- {
- msgPos += 11;
- size_t endPos = line.find( "\"", msgPos );
- if ( endPos != std::string::npos )
- {
- std::string msg = line.substr( msgPos, endPos - msgPos );
- UTIL_ClientPrintAll( HUD_PRINTTALK, "[OV JS] %s", msg.c_str() );
- }
- }
- }
- else if ( line.find( "\"type\":\"entity_spawn\"" ) != std::string::npos )
- {
- std::string type = "prop_physics_override";
- std::string model = "";
- Vector pos( 0, 0, 0 );
-
- size_t tPos = line.find( "\"type\":\"" );
- if ( tPos != std::string::npos )
- {
- size_t dataPos = line.find( "\"data\":{" );
- if ( dataPos != std::string::npos )
- {
- size_t innerType = line.find( "\"type\":\"", dataPos );
- if ( innerType != std::string::npos )
- {
- innerType += 8;
- size_t end = line.find( "\"", innerType );
- if ( end != std::string::npos )
- {
- type = line.substr( innerType, end - innerType );
- }
- }
- size_t innerModel = line.find( "\"model\":\"", dataPos );
- if ( innerModel != std::string::npos )
- {
- innerModel += 9;
- size_t end = line.find( "\"", innerModel );
- if ( end != std::string::npos )
- {
- model = line.substr( innerModel, end - innerModel );
- }
- }
- size_t xPos = line.find( "\"x\":", dataPos );
- size_t yPos = line.find( "\"y\":", dataPos );
- size_t zPos = line.find( "\"z\":", dataPos );
- if ( xPos != std::string::npos && yPos != std::string::npos && zPos != std::string::npos )
- {
- pos.x = atof( line.c_str() + xPos + 4 );
- pos.y = atof( line.c_str() + yPos + 4 );
- pos.z = atof( line.c_str() + zPos + 4 );
- }
- }
- }
-
- if ( !model.empty() )
- {
- CBaseEntity::PrecacheModel( model.c_str() );
- CBaseEntity *pEnt = CreateEntityByName( type.c_str() );
- if ( pEnt )
- {
- pEnt->KeyValue( "model", model.c_str() );
- pEnt->SetAbsOrigin( pos );
- DispatchSpawn( pEnt );
- pEnt->Activate();
- Msg( "[OV JS] Spawned entity %s with model %s at (%f, %f, %f)\n",
- type.c_str(), model.c_str(), pos.x, pos.y, pos.z );
- }
- }
- }
- }
-};
-
-static COpenVibeJsBridge g_OpenVibeJsBridge;
+// ======================================\n// OpenVibe embedded JavaScript hook bridge\n// ======================================
void OpenVibe_OnClientDisconnect( CBasePlayer *pPlayer )
void OpenVibe_OnClientDisconnect( CBasePlayer *pPlayer )
{
{
@@ -810,18 +394,3 @@ void OpenVibe_OnFrame()
@@ -810,18 +394,3 @@ void OpenVibe_OnFrame()
OpenVibeJS_ServerThink();
OpenVibeJS_ServerThink();
}
}
-static void OV_JsCmd_f( const CCommand &args )
-{
- if ( args.ArgC() < 2 )
- {
- Msg( "Usage: ov_js_cmd <command_or_json>\n" );
- return;
- }
- g_OpenVibeJsBridge.OnCommand( args.ArgS() );
-}
-
-static ConCommand ov_js_cmd(
- "ov_js_cmd",
- OV_JsCmd_f,
- "Send a command or JSON message to the OpenVibe.JS runtime.",
- FCVAR_GAMEDLL );
@@ -55,25 +55,6 @@ copy_tree "$ROOT/sdk/openvibe/third_party/quickjs" \
@@ -55,25 +55,6 @@ copy_tree "$ROOT/sdk/openvibe/third_party/quickjs" \
"$ROOT/tools/build-quickjs-lib.sh"
"$ROOT/tools/build-quickjs-lib.sh"
-QJS_C="$SDK/src/game/shared/openvibe/third_party/quickjs/quickjs.c"
-if [[ -f "$QJS_C" ]] && ! grep -q 'OPENVIBE_QUICKJS_CONFIG' "$QJS_C"; then
- tmp="$(mktemp)"
- cat > "$tmp" <<'QCFG'
-#ifndef OPENVIBE_QUICKJS_CONFIG
-#define OPENVIBE_QUICKJS_CONFIG
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-#ifndef CONFIG_VERSION
-#define CONFIG_VERSION "openvibe"
-#endif
-#endif
-
-QCFG
- cat "$QJS_C" >> "$tmp"
- mv "$tmp" "$QJS_C"
-fi
-
CLIENT_VPC="$SDK/src/game/client/client_hl2mp.vpc"
CLIENT_VPC="$SDK/src/game/client/client_hl2mp.vpc"
SERVER_VPC="$SDK/src/game/server/server_hl2mp.vpc"
SERVER_VPC="$SDK/src/game/server/server_hl2mp.vpc"
HL2MP_CLIENT="$SDK/src/game/server/hl2mp/hl2mp_client.cpp"
HL2MP_CLIENT="$SDK/src/game/server/hl2mp/hl2mp_client.cpp"
@@ -81,7 +62,11 @@ HL2MP_CLIENTMODE="$SDK/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp"
@@ -81,7 +62,11 @@ HL2MP_CLIENTMODE="$SDK/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp"
GAMEINTERFACE="$SDK/src/game/server/gameinterface.cpp"
GAMEINTERFACE="$SDK/src/game/server/gameinterface.cpp"
HL2MP_PLAYER="$SDK/src/game/server/hl2mp/hl2mp_player.cpp"
HL2MP_PLAYER="$SDK/src/game/server/hl2mp/hl2mp_player.cpp"
-perl -0pi -e 's/^.*hl2mp\\openvibe_client\.cpp.*\n//mg; s/^.*hl2mp\\vgui_openvibe_menu\.cpp.*\n//mg; s/(\$File\s+"hl2mp\\clientmode_hl2mpnormal\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_client.cpp"\n\t\t\t\$File\t"hl2mp\\vgui_openvibe_menu.cpp"\n/s' "$CLIENT_VPC"
+perl -0pi -e '
+ s/^.*hl2mp\\openvibe_client\.cpp.*\n//mg;
+ s/^.*hl2mp\\vgui_openvibe_menu\.cpp.*\n//mg;
+ s/(\$File\s+"hl2mp\\clientmode_hl2mpnormal\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_client.cpp"\n\t\t\t\$File\t"hl2mp\\vgui_openvibe_menu.cpp"\n/s;
+' "$CLIENT_VPC"
echo "[openvibe-sdk] patched client_hl2mp.vpc"
echo "[openvibe-sdk] patched client_hl2mp.vpc"
perl -0pi -e '
perl -0pi -e '
@@ -90,7 +75,14 @@ perl -0pi -e '
@@ -90,7 +75,14 @@ perl -0pi -e '
s/^.*openvibe\\ov_js_runtime\.cpp.*\n//mg;
s/^.*openvibe\\ov_js_runtime\.cpp.*\n//mg;
s/^.*openvibe\\ov_js_bindings\.cpp.*\n//mg;
s/^.*openvibe\\ov_js_bindings\.cpp.*\n//mg;
s/^.*openvibe\\ov_js_player\.cpp.*\n//mg;
s/^.*openvibe\\ov_js_player\.cpp.*\n//mg;
- s/(\$File\s+"hl2mp\\hl2mp_player\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_server.cpp"\n\t\t\t\$File\t"hl2mp\\openvibe_js_server.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_runtime.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_bindings.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_player.cpp"\n/s;
+ s/^.*quickjs\\quickjs\.c.*\n//mg;
+ s/^.*quickjs\\libregexp\.c.*\n//mg;
+ s/^.*quickjs\\libunicode\.c.*\n//mg;
+ s/^.*quickjs\\cutils\.c.*\n//mg;
+ s/^.*quickjs\\dtoa\.c.*\n//mg;
+ s/^.*quickjs\\libbf\.c.*\n//mg;
+ s/^.*libquickjs_openvibe(?:\.a)?".*\n//mg;
+ s/(\$File\s+"hl2mp\\hl2mp_player\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_server.cpp"\n\t\t\t\$File\t"hl2mp\\openvibe_js_server.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_runtime.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_bindings.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_player.cpp"\n\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s;
' "$SERVER_VPC"
' "$SERVER_VPC"
echo "[openvibe-sdk] patched server_hl2mp.vpc"
echo "[openvibe-sdk] patched server_hl2mp.vpc"
@@ -147,9 +139,6 @@ import re, sys
@@ -147,9 +139,6 @@ import re, sys
p = Path(sys.argv[1])
p = Path(sys.argv[1])
s = p.read_text()
s = p.read_text()
-# Best-effort patch for common Source SDK 2013 HL2MP Host_Say shape:
-# CHL2MP_Player *client = ...
-# const char *p = args.ArgS();
if "OpenVibeJS_Server_PlayerSay( client," not in s:
if "OpenVibeJS_Server_PlayerSay( client," not in s:
s2 = re.sub(
s2 = re.sub(
r'(const char \*p\s*=\s*args\.ArgS\(\);\s*)',
r'(const char \*p\s*=\s*args\.ArgS\(\);\s*)',
@@ -166,75 +155,4 @@ p.write_text(s)
@@ -166,75 +155,4 @@ p.write_text(s)
PY
PY
fi
fi
-
-# OPENVIBE_CONTINUE_QUICKJS_POSTPATCH
-patch_openvibe_quickjs_after_apply() {
- local qjs_dir="$SDK/src/game/shared/openvibe/third_party/quickjs"
- local qjs_c="$qjs_dir/quickjs.c"
-
- if [[ -f "$qjs_c" ]] && ! grep -q 'OPENVIBE_QUICKJS_STRICT_C_SHIM' "$qjs_c"; then
- local tmp
- tmp="$(mktemp)"
- cat > "$tmp" <<'QJS_SHIM'
-#ifndef OPENVIBE_QUICKJS_STRICT_C_SHIM
-#define OPENVIBE_QUICKJS_STRICT_C_SHIM
-
-#ifndef _GNU_SOURCE
-#define _GNU_SOURCE
-#endif
-
-#ifndef CONFIG_VERSION
-#define CONFIG_VERSION "openvibe"
-#endif
-
-#if defined(__GNUC__) && !defined(__cplusplus) && defined(__STRICT_ANSI__) && !defined(asm)
-#define asm __asm__
-#endif
-
-#endif
-
-QJS_SHIM
- cat "$qjs_c" >> "$tmp"
- mv "$tmp" "$qjs_c"
- echo "[openvibe-sdk] patched QuickJS strict-C shim"
- fi
-
- if [[ ! -f "$qjs_dir/libbf.c" ]]; then
- perl -0pi -e 's/^.*openvibe\\\\third_party\\\\quickjs\\\\libbf\.c.*\n//mg; s/^.*openvibe\\third_party\\quickjs\\libbf\.c.*\n//mg' "$SERVER_VPC" || true
- echo "[openvibe-sdk] libbf.c missing; removed libbf.c from server_hl2mp.vpc"
- fi
-}
-
-patch_openvibe_quickjs_after_apply
-
-
-# OPENVIBE_FIX_QUICKJS_LINK_FINAL_V2
-# QuickJS is C, so do not compile its .c files through Source SDK/VPC C++.
-# We build libquickjs_openvibe.a with cc and link it as a library.
-if [[ -f "$SERVER_VPC" ]]; then
- perl -0pi -e '
- s/^.*quickjs\\quickjs\.c.*\n//mg;
- s/^.*quickjs\\libregexp\.c.*\n//mg;
- s/^.*quickjs\\libunicode\.c.*\n//mg;
- s/^.*quickjs\\cutils\.c.*\n//mg;
- s/^.*quickjs\\dtoa\.c.*\n//mg;
- s/^.*quickjs\\libbf\.c.*\n//mg;
- ' "$SERVER_VPC"
-
- perl -0pi -e 's/^.*libquickjs_openvibe(?:\.a)?".*\n//mg' "$SERVER_VPC"
-
- perl -0pi -e 's/(\$File\s+"hl2mp\\openvibe_js_server\.cpp"\n)/$1\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s' "$SERVER_VPC"
-
- echo "[openvibe-sdk] linked QuickJS static library"
-fi
-
echo "[openvibe-sdk] Source SDK OpenVibe patch applied"
echo "[openvibe-sdk] Source SDK OpenVibe patch applied"
-
-
-# Link prebuilt QuickJS C static library. QuickJS must be compiled as C, not C++.
-if ! grep -q 'libquickjs_openvibe' "$SERVER_VPC"; then
- perl -0pi -e 's/(\$File\s+"hl2mp\\openvibe_js_server\.cpp"\n)/$1\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s' "$SERVER_VPC"
- echo "[openvibe-sdk] linked QuickJS static library"
-fi
-
-
@@ -16,7 +16,7 @@ fi
@@ -16,7 +16,7 @@ fi
mkdir -p "$SDK_QJS"
mkdir -p "$SDK_QJS"
-# Exclude build/ so rsync does not delete our .o/.a output dir.
+# Exclude build/ so rsync does not delete object/library output.
rsync -a --delete --exclude='build/' "$SRC_QJS/" "$SDK_QJS/"
rsync -a --delete --exclude='build/' "$SRC_QJS/" "$SDK_QJS/"
mkdir -p "$OUT"
mkdir -p "$OUT"
new file mode 100755
new file mode 100755
@@ -0,0 +1,588 @@
@@ -0,0 +1,588 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
+SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
+STAMP="$(date +%Y%m%d-%H%M%S)"
+RUN_BUILD="${RUN_BUILD:-1}"
+RUN_SETUP="${RUN_SETUP:-1}"
+
+cd "$ROOT"
+
+backup_file() {
+ local file="$1"
+ [[ -f "$file" ]] && cp "$file" "$file.bak.$STAMP"
+}
+
+echo "[openvibe] continue after QuickJS build pass"
+echo "[openvibe] root=$ROOT"
+echo "[openvibe] sdk=$SDK"
+
+mkdir -p tools sdk/openvibe/shared sdk/openvibe/server/hl2mp game/openvibe.games/js/gamemodes/base game/openvibe.games/js/gamemodes/hub
+
+echo "[openvibe] clean local generated junk from git tracking if present"
+git rm -f .tmp/quickjs-smoke/smoke-quickjs 2>/dev/null || true
+git rm -f tools/apply-openvibe-sdk.sh.bak.* 2>/dev/null || true
+
+touch .gitignore
+grep -qxF '.tmp/' .gitignore || cat >> .gitignore <<'GITIGNORE_ADD'
+
+# OpenVibe local/generated build junk
+.tmp/
+*.bak.*
+tools/*.bak.*
+engine/source-sdk-2013/src/game/shared/openvibe/third_party/quickjs/build/
+GITIGNORE_ADD
+
+echo "[openvibe] write canonical tools/build-quickjs-lib.sh"
+
+cat > tools/build-quickjs-lib.sh <<'BUILDQJS'
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
+SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
+
+SRC_QJS="$ROOT/sdk/openvibe/third_party/quickjs"
+SDK_QJS="$SDK/src/game/shared/openvibe/third_party/quickjs"
+OUT="$SDK_QJS/build"
+
+if [[ ! -f "$SRC_QJS/quickjs.c" ]]; then
+ echo "Missing $SRC_QJS/quickjs.c" >&2
+ echo "Run: tools/vendor-quickjs.sh" >&2
+ exit 1
+fi
+
+mkdir -p "$SDK_QJS"
+
+# Exclude build/ so rsync does not delete object/library output.
+rsync -a --delete --exclude='build/' "$SRC_QJS/" "$SDK_QJS/"
+
+mkdir -p "$OUT"
+
+sources=(
+ quickjs.c
+ libregexp.c
+ libunicode.c
+ cutils.c
+)
+
+[[ -f "$SDK_QJS/dtoa.c" ]] && sources+=(dtoa.c)
+[[ -f "$SDK_QJS/libbf.c" ]] && sources+=(libbf.c)
+
+rm -f "$OUT"/*.o "$OUT"/libquickjs_openvibe.a
+
+for src in "${sources[@]}"; do
+ obj="$OUT/${src%.c}.o"
+ echo "[openvibe-qjs] cc $src -> $obj"
+
+ cc \
+ -std=gnu11 \
+ -O2 \
+ -fPIC \
+ -D_GNU_SOURCE \
+ -DCONFIG_VERSION=\"openvibe\" \
+ -I"$SDK_QJS" \
+ -c "$SDK_QJS/$src" \
+ -o "$obj"
+done
+
+ar rcs "$OUT/libquickjs_openvibe.a" "$OUT"/*.o
+
+echo "[openvibe-qjs] built $OUT/libquickjs_openvibe.a"
+ls -lh "$OUT/libquickjs_openvibe.a"
+BUILDQJS
+
+chmod +x tools/build-quickjs-lib.sh
+
+echo "[openvibe] write clean canonical tools/apply-openvibe-sdk.sh"
+
+backup_file tools/apply-openvibe-sdk.sh
+
+cat > tools/apply-openvibe-sdk.sh <<'APPLY'
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
+SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
+
+if [[ ! -d "$SDK/src/game/client/hl2mp" || ! -d "$SDK/src/game/server/hl2mp" ]]; then
+ echo "Source SDK 2013 checkout not found at $SDK" >&2
+ echo "Set OPENVIBE_SDK=/path/to/source-sdk-2013 if it lives elsewhere." >&2
+ exit 1
+fi
+
+copy_file() {
+ local src="$1"
+ local dst="$2"
+ install -D -m 0644 "$src" "$dst"
+ echo "[openvibe-sdk] copied ${dst#$SDK/}"
+}
+
+copy_tree() {
+ local src="$1"
+ local dst="$2"
+ mkdir -p "$dst"
+ rsync -a --delete "$src/" "$dst/"
+ echo "[openvibe-sdk] copied tree ${dst#$SDK/}"
+}
+
+copy_file "$ROOT/sdk/openvibe/client/hl2mp/openvibe_client.cpp" \
+ "$SDK/src/game/client/hl2mp/openvibe_client.cpp"
+copy_file "$ROOT/sdk/openvibe/client/hl2mp/vgui_openvibe_menu.cpp" \
+ "$SDK/src/game/client/hl2mp/vgui_openvibe_menu.cpp"
+copy_file "$ROOT/sdk/openvibe/server/hl2mp/openvibe_server.cpp" \
+ "$SDK/src/game/server/hl2mp/openvibe_server.cpp"
+
+copy_file "$ROOT/sdk/openvibe/shared/ov_js_runtime.h" \
+ "$SDK/src/game/shared/openvibe/ov_js_runtime.h"
+copy_file "$ROOT/sdk/openvibe/shared/ov_js_runtime.cpp" \
+ "$SDK/src/game/shared/openvibe/ov_js_runtime.cpp"
+copy_file "$ROOT/sdk/openvibe/shared/ov_js_bindings.h" \
+ "$SDK/src/game/shared/openvibe/ov_js_bindings.h"
+copy_file "$ROOT/sdk/openvibe/shared/ov_js_bindings.cpp" \
+ "$SDK/src/game/shared/openvibe/ov_js_bindings.cpp"
+copy_file "$ROOT/sdk/openvibe/shared/ov_js_player.h" \
+ "$SDK/src/game/shared/openvibe/ov_js_player.h"
+copy_file "$ROOT/sdk/openvibe/shared/ov_js_player.cpp" \
+ "$SDK/src/game/shared/openvibe/ov_js_player.cpp"
+
+copy_file "$ROOT/sdk/openvibe/server/hl2mp/openvibe_js_server.h" \
+ "$SDK/src/game/server/hl2mp/openvibe_js_server.h"
+copy_file "$ROOT/sdk/openvibe/server/hl2mp/openvibe_js_server.cpp" \
+ "$SDK/src/game/server/hl2mp/openvibe_js_server.cpp"
+
+copy_tree "$ROOT/sdk/openvibe/third_party/quickjs" \
+ "$SDK/src/game/shared/openvibe/third_party/quickjs"
+
+"$ROOT/tools/build-quickjs-lib.sh"
+
+CLIENT_VPC="$SDK/src/game/client/client_hl2mp.vpc"
+SERVER_VPC="$SDK/src/game/server/server_hl2mp.vpc"
+HL2MP_CLIENT="$SDK/src/game/server/hl2mp/hl2mp_client.cpp"
+HL2MP_CLIENTMODE="$SDK/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp"
+GAMEINTERFACE="$SDK/src/game/server/gameinterface.cpp"
+HL2MP_PLAYER="$SDK/src/game/server/hl2mp/hl2mp_player.cpp"
+
+perl -0pi -e '
+ s/^.*hl2mp\\openvibe_client\.cpp.*\n//mg;
+ s/^.*hl2mp\\vgui_openvibe_menu\.cpp.*\n//mg;
+ s/(\$File\s+"hl2mp\\clientmode_hl2mpnormal\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_client.cpp"\n\t\t\t\$File\t"hl2mp\\vgui_openvibe_menu.cpp"\n/s;
+' "$CLIENT_VPC"
+echo "[openvibe-sdk] patched client_hl2mp.vpc"
+
+perl -0pi -e '
+ s/^.*hl2mp\\openvibe_server\.cpp.*\n//mg;
+ s/^.*hl2mp\\openvibe_js_server\.cpp.*\n//mg;
+ s/^.*openvibe\\ov_js_runtime\.cpp.*\n//mg;
+ s/^.*openvibe\\ov_js_bindings\.cpp.*\n//mg;
+ s/^.*openvibe\\ov_js_player\.cpp.*\n//mg;
+ s/^.*quickjs\\quickjs\.c.*\n//mg;
+ s/^.*quickjs\\libregexp\.c.*\n//mg;
+ s/^.*quickjs\\libunicode\.c.*\n//mg;
+ s/^.*quickjs\\cutils\.c.*\n//mg;
+ s/^.*quickjs\\dtoa\.c.*\n//mg;
+ s/^.*quickjs\\libbf\.c.*\n//mg;
+ s/^.*libquickjs_openvibe(?:\.a)?".*\n//mg;
+ s/(\$File\s+"hl2mp\\hl2mp_player\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_server.cpp"\n\t\t\t\$File\t"hl2mp\\openvibe_js_server.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_runtime.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_bindings.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_player.cpp"\n\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s;
+' "$SERVER_VPC"
+echo "[openvibe-sdk] patched server_hl2mp.vpc"
+
+if ! grep -q 'OpenVibe_OnClientActive' "$HL2MP_CLIENT"; then
+ sed -i '/void Host_Say/a void OpenVibe_OnClientActive( CHL2MP_Player *pPlayer );' "$HL2MP_CLIENT"
+ sed -i '/FinishClientPutInServer( pPlayer );/a \ OpenVibe_OnClientActive( pPlayer );' "$HL2MP_CLIENT"
+ echo "[openvibe-sdk] patched hl2mp_client.cpp arrival hook"
+fi
+
+perl -0pi -e 's/[^\S\r\n]*\x0boid OpenVibe_OnClientActive/void OpenVibe_OnClientActive/g; s/\\tOpenVibe_OnClientActive/\tOpenVibe_OnClientActive/g; s/^[ \t]*void OpenVibe_OnClientActive/void OpenVibe_OnClientActive/m' "$HL2MP_CLIENT"
+
+if ! grep -q 'OpenVibe_OnClientModeInit' "$HL2MP_CLIENTMODE"; then
+ sed -i '/#include "ienginevgui.h"/a void OpenVibe_OnClientModeInit();' "$HL2MP_CLIENTMODE"
+ sed -i '/BaseClass::Init();/a \ OpenVibe_OnClientModeInit();' "$HL2MP_CLIENTMODE"
+ echo "[openvibe-sdk] patched clientmode_hl2mpnormal.cpp OpenVibe menu hook"
+fi
+
+perl -0pi -e 's/^[ \t]*tOpenVibe_OnClientModeInit\(\);/ OpenVibe_OnClientModeInit();/m' "$HL2MP_CLIENTMODE"
+
+if [[ -f "$GAMEINTERFACE" ]]; then
+ if ! grep -q 'OpenVibe_OnFrame' "$GAMEINTERFACE"; then
+ sed -i '/CServerGameDLL::GameFrame/i #ifdef HL2MP\nvoid OpenVibe_OnFrame();\n#endif' "$GAMEINTERFACE"
+ sed -i '/VPROF( "CServerGameDLL::GameFrame" );/a #ifdef HL2MP\n\tOpenVibe_OnFrame();\n#endif' "$GAMEINTERFACE"
+ echo "[openvibe-sdk] patched gameinterface.cpp frame hook"
+ fi
+
+ if ! grep -q 'OpenVibe_OnClientDisconnect' "$GAMEINTERFACE"; then
+ perl -pi -e 's/void CServerGameClients::ClientDisconnect/#ifdef HL2MP\nvoid OpenVibe_OnClientDisconnect( CBasePlayer *pPlayer );\n#endif\nvoid CServerGameClients::ClientDisconnect/' "$GAMEINTERFACE"
+ perl -0777 -pi -e 's/(void CServerGameClients::ClientDisconnect\( edict_t \*pEdict \)\s*\{\s*extern bool\s+g_fGameOver;\s*CBasePlayer \*player = \( CBasePlayer \* \)CBaseEntity::Instance\( pEdict \);)/$1\n#ifdef HL2MP\n\tif ( player ) { OpenVibe_OnClientDisconnect( player ); }\n#endif/g' "$GAMEINTERFACE"
+ echo "[openvibe-sdk] patched gameinterface.cpp disconnect hook"
+ fi
+fi
+
+if [[ -f "$HL2MP_PLAYER" ]]; then
+ if ! grep -q 'OpenVibe_OnPlayerDeath' "$HL2MP_PLAYER"; then
+ sed -i '/CHL2MP_Player::Event_Killed/i void OpenVibe_OnPlayerDeath( CHL2MP_Player *pPlayer, CBaseEntity *pKiller );' "$HL2MP_PLAYER"
+ sed -i '/CTakeDamageInfo subinfo = info;/i \ OpenVibe_OnPlayerDeath( this, info.GetAttacker() );' "$HL2MP_PLAYER"
+ echo "[openvibe-sdk] patched hl2mp_player.cpp death hook"
+ fi
+
+ if ! grep -q 'OpenVibeJS_Server_PlayerSpawn' "$HL2MP_PLAYER"; then
+ sed -i '/CHL2MP_Player::Spawn/i void OpenVibeJS_Server_PlayerSpawn( CHL2MP_Player *pPlayer );' "$HL2MP_PLAYER"
+ sed -i '0,/BaseClass::Spawn();/s//BaseClass::Spawn();\n\tOpenVibeJS_Server_PlayerSpawn( this );/' "$HL2MP_PLAYER"
+ echo "[openvibe-sdk] patched hl2mp_player.cpp spawn hook"
+ fi
+fi
+
+if [[ -f "$HL2MP_CLIENT" ]] && ! grep -q 'OpenVibeJS_Server_PlayerSay' "$HL2MP_CLIENT"; then
+ sed -i '/void Host_Say/a bool OpenVibeJS_Server_PlayerSay( CHL2MP_Player *pPlayer, const char *pszText );' "$HL2MP_CLIENT"
+ python3 - "$HL2MP_CLIENT" <<'PY'
+from pathlib import Path
+import re, sys
+
+p = Path(sys.argv[1])
+s = p.read_text()
+
+if "OpenVibeJS_Server_PlayerSay( client," not in s:
+ s2 = re.sub(
+ r'(const char \*p\s*=\s*args\.ArgS\(\);\s*)',
+ r'\1\n\tif ( client && OpenVibeJS_Server_PlayerSay( client, p ) )\n\t\treturn;\n',
+ s,
+ count=1,
+ )
+ if s2 != s:
+ s = s2
+ print("[openvibe-sdk] patched hl2mp_client.cpp PlayerSay hook")
+ else:
+ print("[openvibe-sdk] WARNING: could not auto-patch Host_Say PlayerSay body")
+p.write_text(s)
+PY
+fi
+
+echo "[openvibe-sdk] Source SDK OpenVibe patch applied"
+APPLY
+
+chmod +x tools/apply-openvibe-sdk.sh
+
+echo "[openvibe] remove legacy Node bridge from sdk/openvibe/server/hl2mp/openvibe_server.cpp"
+
+backup_file sdk/openvibe/server/hl2mp/openvibe_server.cpp
+
+python3 <<'PY'
+from pathlib import Path
+import re
+
+p = Path("sdk/openvibe/server/hl2mp/openvibe_server.cpp")
+s = p.read_text()
+
+if '#include "openvibe_js_server.h"' not in s:
+ s = re.sub(
+ r'(#include\s+"hl2mp_player\.h"\s*\n)',
+ r'\1#include "openvibe_js_server.h"\n',
+ s,
+ count=1,
+ )
+
+start = s.find("// =========================================")
+bridge = s.find("OpenVibe.JS - Scripting Engine C++ Bridge")
+if start != -1 and bridge != -1 and start < bridge:
+ end = s.find("void OpenVibe_OnClientDisconnect", bridge)
+ if end != -1:
+ s = s[:start].rstrip() + "\n\n" + s[end:]
+ print("[openvibe] removed legacy OpenVibe.JS Node bridge block")
+
+s = re.sub(r'\n\s*static\s+COpenVibeJsBridge\s+g_OpenVibeJsBridge\s*;\s*\n', '\n', s)
+
+legacy_cmd = re.compile(
+ r'\nstatic\s+void\s+OV_JsCmd_f\s*\([^)]*\)\s*\{.*?\nstatic\s+ConCommand\s+ov_js_cmd\s*\(.*?\);\s*',
+ re.S,
+)
+s2 = legacy_cmd.sub('\n', s)
+if s2 != s:
+ s = s2
+ print("[openvibe] removed legacy ov_js_cmd command")
+
+def replace_func(src, name, args, body):
+ m = re.search(rf'void\s+{name}\s*\({args}\)\s*\{{', src)
+ if not m:
+ return src
+ brace = src.find("{", m.end() - 1)
+ depth = 0
+ end = None
+ for i in range(brace, len(src)):
+ if src[i] == "{":
+ depth += 1
+ elif src[i] == "}":
+ depth -= 1
+ if depth == 0:
+ end = i
+ break
+ if end is None:
+ return src
+ return src[:brace + 1] + "\n" + body.rstrip() + "\n" + src[end:]
+
+s = replace_func(
+ s,
+ "OpenVibe_OnClientDisconnect",
+ r'\s*CBasePlayer\s*\*\s*pPlayer\s*',
+ '''
+ CHL2MP_Player *pHL2MP = ToHL2MPPlayer( pPlayer );
+ if ( pHL2MP )
+ OpenVibeJS_Server_PlayerDisconnected( pHL2MP );
+'''
+)
+
+s = replace_func(
+ s,
+ "OpenVibe_OnPlayerDeath",
+ r'\s*CHL2MP_Player\s*\*\s*pPlayer\s*,\s*CBaseEntity\s*\*\s*pKiller\s*',
+ '''
+ OpenVibeJS_Server_PlayerDeath( pPlayer, pKiller, NULL );
+'''
+)
+
+s = replace_func(
+ s,
+ "OpenVibe_OnFrame",
+ r'\s*',
+ '''
+ OpenVibeJS_ServerThink();
+'''
+)
+
+if "OpenVibeJS_Server_PlayerInitialSpawn( pPlayer );" not in s:
+ m = re.search(r'void\s+OpenVibe_OnClientActive\s*\(\s*CHL2MP_Player\s*\*\s*pPlayer\s*\)\s*\{', s)
+ if m:
+ brace = s.find("{", m.end() - 1)
+ depth = 0
+ end = None
+ for i in range(brace, len(s)):
+ if s[i] == "{":
+ depth += 1
+ elif s[i] == "}":
+ depth -= 1
+ if depth == 0:
+ end = i
+ break
+ if end is not None:
+ s = s[:end] + "\n OpenVibeJS_Server_PlayerInitialSpawn( pPlayer );\n" + s[end:]
+
+p.write_text(s)
+PY
+
+echo "[openvibe] add embedded JS console commands to openvibe_js_server.cpp"
+
+backup_file sdk/openvibe/server/hl2mp/openvibe_js_server.cpp
+
+python3 <<'PY'
+from pathlib import Path
+import re
+
+p = Path("sdk/openvibe/server/hl2mp/openvibe_js_server.cpp")
+s = p.read_text()
+
+s = re.sub(r'\nstatic void OV_JSStatus_f\(\).*?static ConCommand ov_js_status\s*\(.*?\);\s*', '\n', s, flags=re.S)
+s = re.sub(r'\nstatic void OV_JSFire_f\(.*?static ConCommand ov_js_fire\s*\(.*?\);\s*', '\n', s, flags=re.S)
+s = re.sub(r'\nstatic void OV_JSCmd_f\(.*?static ConCommand ov_js_cmd\s*\(.*?\);\s*', '\n', s, flags=re.S)
+
+block = r'''
+static void OV_JSStatus_f()
+{
+ Msg("[OV JS] enabled=%d running=%d mode=%s\n",
+ ov_js_enabled.GetBool() ? 1 : 0,
+ OpenVibeJS_IsRunning() ? 1 : 0,
+ ov_mode.GetString());
+}
+
+static ConCommand ov_js_status(
+ "ov_js_status",
+ OV_JSStatus_f,
+ "Print OpenVibe JavaScript runtime status.",
+ FCVAR_GAMEDLL
+);
+
+static void OV_JSFire_f(const CCommand &args)
+{
+ if (args.ArgC() < 2)
+ {
+ Msg("Usage: ov_js_fire <HookName>\n");
+ return;
+ }
+
+ OpenVibeJS_EnsureStarted();
+
+ if (!OpenVibeJS_IsRunning())
+ {
+ Warning("[OV JS] runtime is not running.\n");
+ return;
+ }
+
+ g_OVServerJS.CallHookVoid(args[1]);
+}
+
+static ConCommand ov_js_fire(
+ "ov_js_fire",
+ OV_JSFire_f,
+ "Fire an OpenVibe JavaScript hook with no arguments.",
+ FCVAR_GAMEDLL
+);
+
+static void OV_JSCmd_f(const CCommand &args)
+{
+ OpenVibeJS_EnsureStarted();
+
+ if (!OpenVibeJS_IsRunning())
+ {
+ Warning("[OV JS] runtime is not running.\n");
+ return;
+ }
+
+ JSContext *ctx = g_OVServerJS.Context();
+ JSValue payload = JS_NewString(ctx, args.ArgS() ? args.ArgS() : "");
+
+ JSValueConst argv[] = { payload };
+ g_OVServerJS.CallHookVoid("ConsoleCommand", 1, argv);
+
+ JS_FreeValue(ctx, payload);
+}
+
+static ConCommand ov_js_cmd(
+ "ov_js_cmd",
+ OV_JSCmd_f,
+ "Send text to the OpenVibe embedded JavaScript ConsoleCommand hook.",
+ FCVAR_GAMEDLL
+);
+'''
+
+s = s.rstrip() + "\n\n" + block.lstrip()
+p.write_text(s)
+PY
+
+echo "[openvibe] normalize base/hub JS ConsoleCommand handlers"
+
+cat > game/openvibe.games/js/gamemodes/base/server.js <<'JS_BASE'
+const GM = {
+ mode: "base",
+ name: "OpenVibe Base",
+
+ Initialize() {
+ OV.log("Base Initialize fired");
+ },
+
+ MapInitialize(mapName) {
+ OV.log(`Base MapInitialize: ${mapName}`);
+ },
+
+ PlayerInitialSpawn(ply) {
+ ply.chat("Welcome to OpenVibe: Source.");
+ },
+
+ PlayerSpawn(_ply) {},
+
+ PlayerDeath(_victim, _attacker) {},
+
+ PlayerDisconnected(_ply) {},
+
+ PlayerSay(_ply, _text) {
+ return undefined;
+ },
+
+ ConsoleCommand(text) {
+ OV.log(`ConsoleCommand: ${text}`);
+ return undefined;
+ },
+
+ Think() {}
+};
+
+gamemode.set(GM);
+JS_BASE
+
+cat > game/openvibe.games/js/gamemodes/hub/server.js <<'JS_HUB'
+const GM = {
+ mode: "hub",
+ name: "OpenVibe Hub",
+
+ Initialize() {
+ OV.log("Hub Initialize fired");
+ },
+
+ MapInitialize(mapName) {
+ OV.log(`Map initialized: ${mapName}`);
+ },
+
+ PlayerInitialSpawn(ply) {
+ ply.chat("Welcome to OpenVibe: Source JS runtime.");
+ OV.broadcast(`${ply.name()} joined the hub.`);
+ },
+
+ PlayerSpawn(ply) {
+ ply.chat("PlayerSpawn hook fired.");
+ },
+
+ PlayerSay(ply, text) {
+ if (text === "!js") {
+ ply.chat("JavaScript hooks are working.");
+ return false;
+ }
+
+ if (text === "!hp") {
+ ply.chat(`Health: ${ply.health()}`);
+ return false;
+ }
+
+ if (text === "!players") {
+ ply.chat(`Players online: ${OV.players().length}`);
+ return false;
+ }
+
+ return undefined;
+ },
+
+ ConsoleCommand(text) {
+ OV.log(`Hub ConsoleCommand: ${text}`);
+
+ if (text === "smoke") {
+ OV.broadcast("OpenVibe embedded JS smoke test passed.");
+ return false;
+ }
+
+ return undefined;
+ },
+
+ Think() {}
+};
+
+gamemode.set(GM);
+JS_HUB
+
+echo "[openvibe] apply SDK patch"
+tools/apply-openvibe-sdk.sh
+
+if [[ "$RUN_BUILD" = "1" ]]; then
+ echo "[openvibe] run SDK build"
+ tools/build-sdk-linux.sh 2>&1 | tee "$HOME/ov-build.log"
+ tail -80 "$HOME/ov-build.log"
+else
+ echo "[openvibe] skipping build because RUN_BUILD=$RUN_BUILD"
+fi
+
+if [[ "$RUN_SETUP" = "1" && -x tools/setup-openvibe-bin.sh ]]; then
+ echo "[openvibe] setup OpenVibe binaries"
+ tools/setup-openvibe-bin.sh
+fi
+
+echo
+echo "[openvibe] done."
+echo
+echo "Next manual test:"
+echo " OPENVIBE_SRCDS_MAP_DELAY=3 tools/dev-up.sh"
+echo
+echo "In server console:"
+echo " ov_js_status"
+echo " ov_js_fire Initialize"
+echo " ov_js_cmd smoke"
+echo
+echo "In client:"
+echo " connect 127.0.0.1:27015"
+echo " say !js"
+echo " say !hp"
+echo " say !players"
new file mode 100755
new file mode 100755
@@ -0,0 +1,441 @@
@@ -0,0 +1,441 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
+SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
+STAMP="$(date +%Y%m%d-%H%M%S)"
+
+cd "$ROOT"
+
+backup_file() {
+ local file="$1"
+ [[ -f "$file" ]] && cp "$file" "$file.bak.$STAMP"
+}
+
+echo "[openvibe] fixing duplicate ov_js_cmd and canonicalizing embedded JS server bridge"
+
+mkdir -p sdk/openvibe/server/hl2mp
+
+backup_file sdk/openvibe/server/hl2mp/openvibe_js_server.cpp
+backup_file sdk/openvibe/server/hl2mp/openvibe_server.cpp
+
+cat > sdk/openvibe/server/hl2mp/openvibe_js_server.cpp <<'CPP'
+#include "cbase.h"
+#include "hl2mp_player.h"
+#include "openvibe_js_server.h"
+#include "openvibe/ov_js_runtime.h"
+#include "openvibe/ov_js_player.h"
+
+#include "tier0/memdbgon.h"
+
+static ConVar ov_mode(
+ "ov_mode",
+ "hub",
+ FCVAR_GAMEDLL,
+ "OpenVibe mode: hub, prophunt, deathrun, fortwars, traitortown." );
+
+static ConVar ov_js_enabled(
+ "ov_js_enabled",
+ "1",
+ FCVAR_GAMEDLL,
+ "Enable OpenVibe JavaScript runtime." );
+
+static COpenVibeJSRuntime g_OVServerJS;
+static bool g_OVServerJSStarted = false;
+
+static bool OpenVibeJS_IsRunning()
+{
+ return ov_js_enabled.GetBool() && g_OVServerJS.Context() != nullptr;
+}
+
+static void OpenVibeJS_EnsureStarted()
+{
+ if ( g_OVServerJSStarted )
+ return;
+
+ g_OVServerJSStarted = true;
+
+ if ( !ov_js_enabled.GetBool() )
+ {
+ Msg( "[OV JS] disabled by ov_js_enabled=0\n" );
+ return;
+ }
+
+ if ( g_OVServerJS.Init( true, ov_mode.GetString() ) )
+ {
+ Msg( "[OV JS] server runtime initialized for mode '%s'\n", ov_mode.GetString() );
+ g_OVServerJS.CallHookVoid( "Initialize" );
+
+ JSContext *ctx = g_OVServerJS.Context();
+ JSValue mapName = JS_NewString( ctx, gpGlobals ? STRING( gpGlobals->mapname ) : "" );
+ JSValueConst argv[] = { mapName };
+ g_OVServerJS.CallHookVoid( "MapInitialize", 1, argv );
+ JS_FreeValue( ctx, mapName );
+ }
+ else
+ {
+ Warning( "[OV JS] server runtime failed to initialize\n" );
+ }
+}
+
+static void OpenVibeJS_CallConsoleCommand( const char *pszCommand )
+{
+ OpenVibeJS_EnsureStarted();
+
+ if ( !OpenVibeJS_IsRunning() )
+ {
+ Warning( "[OV JS] runtime is not running; command ignored: %s\n", pszCommand ? pszCommand : "" );
+ return;
+ }
+
+ JSContext *ctx = g_OVServerJS.Context();
+ JSValue command = JS_NewString( ctx, pszCommand ? pszCommand : "" );
+ JSValueConst argv[] = { command };
+
+ g_OVServerJS.CallHookVoid( "ConsoleCommand", 1, argv );
+
+ JS_FreeValue( ctx, command );
+}
+
+void OpenVibeJS_ServerInit()
+{
+ OpenVibeJS_EnsureStarted();
+}
+
+void OpenVibeJS_ServerShutdown()
+{
+ if ( OpenVibeJS_IsRunning() )
+ g_OVServerJS.CallHookVoid( "Shutdown" );
+
+ g_OVServerJS.Shutdown();
+ g_OVServerJSStarted = false;
+}
+
+void OpenVibeJS_ServerThink()
+{
+ OpenVibeJS_EnsureStarted();
+
+ if ( !OpenVibeJS_IsRunning() )
+ return;
+
+ g_OVServerJS.CallHookVoid( "Think" );
+}
+
+void OpenVibeJS_Server_PlayerInitialSpawn( CHL2MP_Player *player )
+{
+ OpenVibeJS_EnsureStarted();
+
+ if ( !OpenVibeJS_IsRunning() || !player )
+ return;
+
+ JSContext *ctx = g_OVServerJS.Context();
+ JSValue ply = OVJS_NewPlayer( ctx, player );
+
+ JSValueConst argv[] = { ply };
+ g_OVServerJS.CallHookVoid( "PlayerInitialSpawn", 1, argv );
+
+ JS_FreeValue( ctx, ply );
+}
+
+void OpenVibeJS_Server_PlayerSpawn( CHL2MP_Player *player )
+{
+ OpenVibeJS_EnsureStarted();
+
+ if ( !OpenVibeJS_IsRunning() || !player )
+ return;
+
+ JSContext *ctx = g_OVServerJS.Context();
+ JSValue ply = OVJS_NewPlayer( ctx, player );
+
+ JSValueConst argv[] = { ply };
+ g_OVServerJS.CallHookVoid( "PlayerSpawn", 1, argv );
+
+ JS_FreeValue( ctx, ply );
+}
+
+void OpenVibeJS_Server_PlayerDeath( CHL2MP_Player *victim, CBaseEntity *attacker, CBaseEntity *inflictor )
+{
+ OpenVibeJS_EnsureStarted();
+
+ if ( !OpenVibeJS_IsRunning() || !victim )
+ return;
+
+ JSContext *ctx = g_OVServerJS.Context();
+
+ JSValue jsVictim = OVJS_NewPlayer( ctx, victim );
+ JSValue jsAttacker = JS_NULL;
+
+ if ( attacker && attacker->IsPlayer() )
+ jsAttacker = OVJS_NewPlayer( ctx, ToHL2MPPlayer( static_cast<CBasePlayer *>( attacker ) ) );
+
+ JSValueConst argv[] = { jsVictim, jsAttacker };
+ g_OVServerJS.CallHookVoid( "PlayerDeath", 2, argv );
+
+ JS_FreeValue( ctx, jsVictim );
+ JS_FreeValue( ctx, jsAttacker );
+}
+
+void OpenVibeJS_Server_PlayerDisconnected( CHL2MP_Player *player )
+{
+ OpenVibeJS_EnsureStarted();
+
+ if ( !OpenVibeJS_IsRunning() || !player )
+ return;
+
+ JSContext *ctx = g_OVServerJS.Context();
+ JSValue ply = OVJS_NewPlayer( ctx, player );
+
+ JSValueConst argv[] = { ply };
+ g_OVServerJS.CallHookVoid( "PlayerDisconnected", 1, argv );
+
+ JS_FreeValue( ctx, ply );
+}
+
+bool OpenVibeJS_Server_PlayerSay( CHL2MP_Player *player, const char *text )
+{
+ OpenVibeJS_EnsureStarted();
+
+ if ( !OpenVibeJS_IsRunning() || !player || !text )
+ return false;
+
+ JSContext *ctx = g_OVServerJS.Context();
+
+ JSValue ply = OVJS_NewPlayer( ctx, player );
+ JSValue msg = JS_NewString( ctx, text );
+
+ JSValueConst argv[] = { ply, msg };
+
+ bool value = false;
+ bool returned = g_OVServerJS.CallHookBool( "PlayerSay", &value, 2, argv );
+
+ JS_FreeValue( ctx, ply );
+ JS_FreeValue( ctx, msg );
+
+ return returned && value == false;
+}
+
+static void OV_JSReload_f()
+{
+ OpenVibeJS_ServerShutdown();
+ OpenVibeJS_ServerInit();
+ Msg( "[OV JS] reloaded\n" );
+}
+
+static ConCommand ov_js_reload(
+ "ov_js_reload",
+ OV_JSReload_f,
+ "Reload OpenVibe JavaScript runtime.",
+ FCVAR_GAMEDLL
+);
+
+static void OV_JSStatus_f()
+{
+ Msg( "[OV JS] enabled=%d started=%d running=%d mode=%s\n",
+ ov_js_enabled.GetBool() ? 1 : 0,
+ g_OVServerJSStarted ? 1 : 0,
+ OpenVibeJS_IsRunning() ? 1 : 0,
+ ov_mode.GetString() );
+}
+
+static ConCommand ov_js_status(
+ "ov_js_status",
+ OV_JSStatus_f,
+ "Print OpenVibe JavaScript runtime status.",
+ FCVAR_GAMEDLL
+);
+
+static void OV_JSFire_f( const CCommand &args )
+{
+ if ( args.ArgC() < 2 )
+ {
+ Msg( "Usage: ov_js_fire <HookName>\n" );
+ return;
+ }
+
+ OpenVibeJS_EnsureStarted();
+
+ if ( !OpenVibeJS_IsRunning() )
+ {
+ Warning( "[OV JS] runtime is not running\n" );
+ return;
+ }
+
+ g_OVServerJS.CallHookVoid( args[1] );
+}
+
+static ConCommand ov_js_fire(
+ "ov_js_fire",
+ OV_JSFire_f,
+ "Fire an OpenVibe JavaScript hook with no arguments.",
+ FCVAR_GAMEDLL
+);
+
+static void OV_JSCmd_f( const CCommand &args )
+{
+ if ( args.ArgC() < 2 )
+ {
+ Msg( "Usage: ov_js_cmd <text>\n" );
+ return;
+ }
+
+ OpenVibeJS_CallConsoleCommand( args.ArgS() );
+}
+
+static ConCommand ov_js_cmd(
+ "ov_js_cmd",
+ OV_JSCmd_f,
+ "Send a ConsoleCommand event into the embedded OpenVibe JavaScript runtime.",
+ FCVAR_GAMEDLL
+);
+CPP
+
+echo "[openvibe] canonical openvibe_js_server.cpp written"
+
+echo "[openvibe] removing stale legacy Node bridge remnants from openvibe_server.cpp"
+
+python3 <<'PY'
+from pathlib import Path
+import re
+
+p = Path("sdk/openvibe/server/hl2mp/openvibe_server.cpp")
+s = p.read_text()
+
+# Remove legacy COpenVibeJsBridge class, if present.
+s = re.sub(
+ r'\n// ={10,}\n// OpenVibe\.JS - Scripting Engine C\+\+ Bridge\n// ={10,}\n\nclass COpenVibeJsBridge\b.*?\nstatic COpenVibeJsBridge g_OpenVibeJsBridge;\n',
+ '\n',
+ s,
+ flags=re.S,
+)
+
+# More tolerant fallback if banner text differs.
+s = re.sub(
+ r'\nclass COpenVibeJsBridge\b.*?\nstatic COpenVibeJsBridge g_OpenVibeJsBridge;\n',
+ '\n',
+ s,
+ flags=re.S,
+)
+
+# Remove old Node-bridge ov_js_cmd command if it survived.
+s = re.sub(
+ r'\nstatic void OV_JsCmd_f\s*\([^)]*\)\s*\{.*?\n\}\n\nstatic ConCommand ov_js_cmd\s*\(.*?\);\n',
+ '\n',
+ s,
+ flags=re.S,
+)
+
+# Remove obvious unused Node/socket includes from our tracked patch file.
+for inc in [
+ '#include <thread>\n',
+ '#include <mutex>\n',
+ '#include <queue>\n',
+ '#include <string>\n',
+ '#include <sstream>\n',
+ '#include <sys/socket.h>\n',
+ '#include <sys/un.h>\n',
+ '#include <unistd.h>\n',
+]:
+ s = s.replace(inc, '')
+
+# Ensure embedded JS include exists.
+if '#include "openvibe_js_server.h"' not in s:
+ s = re.sub(
+ r'(#include\s+"hl2mp_player\.h"\s*\n)',
+ r'\1#include "openvibe_js_server.h"\n',
+ s,
+ count=1,
+ )
+
+p.write_text(s)
+PY
+
+echo "[openvibe] removing duplicate ov_js_cmd from already-applied SDK file if present"
+
+SDK_SERVER_JS="$SDK/src/game/server/hl2mp/openvibe_js_server.cpp"
+if [[ -f "$SDK_SERVER_JS" ]]; then
+ cp "$SDK_SERVER_JS" "$SDK_SERVER_JS.bak.$STAMP"
+ cp sdk/openvibe/server/hl2mp/openvibe_js_server.cpp "$SDK_SERVER_JS"
+fi
+
+echo "[openvibe] ensure JS ConsoleCommand handlers are present"
+
+mkdir -p game/openvibe.games/js/gamemodes/base game/openvibe.games/js/gamemodes/hub
+
+python3 <<'PY'
+from pathlib import Path
+
+def ensure_console_handler(path: str, marker: str, handler_src: str):
+ p = Path(path)
+ s = p.read_text() if p.exists() else "const GM = {\n};\n\ngamemode.set(GM);\n"
+ if marker in s:
+ return
+ idx = s.rfind("};")
+ if idx == -1:
+ s += "\n" + handler_src + "\n"
+ else:
+ before = s[:idx].rstrip()
+ after = s[idx:]
+ if before.endswith("{"):
+ before = before + "\n" + handler_src.strip()
+ else:
+ before = before + ",\n\n" + handler_src.strip()
+ s = before + "\n" + after
+ p.write_text(s)
+
+ensure_console_handler(
+ "game/openvibe.games/js/gamemodes/base/server.js",
+ "Base ConsoleCommand",
+ '''ConsoleCommand(text) {
+ OV.log(`Base ConsoleCommand: ${text}`);
+ }'''
+)
+
+ensure_console_handler(
+ "game/openvibe.games/js/gamemodes/hub/server.js",
+ "Hub ConsoleCommand",
+ '''ConsoleCommand(text) {
+ OV.log(`Hub ConsoleCommand: ${text}`);
+ if (text === "smoke") {
+ OV.broadcast("Embedded JS smoke command worked.");
+ }
+ }'''
+)
+PY
+
+echo "[openvibe] apply SDK patch"
+tools/apply-openvibe-sdk.sh
+
+echo "[openvibe] verify only one ov_js_cmd exists in SDK copied source"
+grep -n "ConCommand ov_js_cmd" "$SDK/src/game/server/hl2mp/openvibe_js_server.cpp" || true
+count="$(grep -c "ConCommand ov_js_cmd" "$SDK/src/game/server/hl2mp/openvibe_js_server.cpp" || true)"
+if [[ "$count" != "1" ]]; then
+ echo "[openvibe] ERROR: expected exactly one ov_js_cmd, got $count" >&2
+ exit 1
+fi
+
+echo "[openvibe] build SDK"
+tools/build-sdk-linux.sh 2>&1 | tee "$HOME/ov-build.log"
+
+echo "[openvibe] setup OpenVibe bin"
+if [[ -x tools/setup-openvibe-bin.sh ]]; then
+ tools/setup-openvibe-bin.sh
+else
+ echo "[openvibe] WARNING: tools/setup-openvibe-bin.sh not found/executable; skipping"
+fi
+
+echo
+echo "[openvibe] phase build complete."
+echo
+echo "Next runtime test:"
+echo " OPENVIBE_SRCDS_MAP_DELAY=3 tools/dev-up.sh"
+echo
+echo "Server console tests:"
+echo " ov_js_status"
+echo " ov_js_fire Initialize"
+echo " ov_js_cmd smoke"
+echo
+echo "Client tests:"
+echo " connect 127.0.0.1:27015"
+echo " say !js"
+echo " say !hp"
+echo " say !players"
new file mode 100755
new file mode 100755
@@ -0,0 +1,383 @@
@@ -0,0 +1,383 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
+SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
+STAMP="$(date +%Y%m%d-%H%M%S)"
+RUN_BUILD="${RUN_BUILD:-1}"
+
+cd "$ROOT"
+
+backup_file() {
+ local file="$1"
+ [[ -f "$file" ]] && cp "$file" "$file.bak.$STAMP"
+}
+
+need_file() {
+ [[ -f "$1" ]] || { echo "[openvibe] missing required file: $1" >&2; exit 1; }
+}
+
+need_file "sdk/openvibe/server/hl2mp/openvibe_server.cpp"
+need_file "sdk/openvibe/server/hl2mp/openvibe_js_server.cpp"
+need_file "sdk/openvibe/server/hl2mp/openvibe_js_server.h"
+need_file "tools/apply-openvibe-sdk.sh"
+need_file "tools/build-quickjs-lib.sh"
+
+echo "[openvibe] next phase: remove legacy Node bridge, add embedded JS console hooks, rebuild"
+
+mkdir -p tools
+
+backup_file "sdk/openvibe/server/hl2mp/openvibe_server.cpp"
+backup_file "sdk/openvibe/server/hl2mp/openvibe_js_server.cpp"
+backup_file "sdk/openvibe/server/hl2mp/openvibe_js_server.h"
+backup_file "tools/apply-openvibe-sdk.sh"
+
+python3 <<'PY'
+from pathlib import Path
+import re
+
+p = Path("sdk/openvibe/server/hl2mp/openvibe_server.cpp")
+s = p.read_text()
+
+# Make sure the embedded JS bridge header is included.
+if '#include "openvibe_js_server.h"' not in s:
+ s = re.sub(
+ r'(#include\s+"hl2mp_player\.h"\s*\n)',
+ r'\1#include "openvibe_js_server.h"\n',
+ s,
+ count=1,
+ )
+
+# Remove legacy Node/Unix-socket bridge includes that are no longer needed.
+for inc in [
+ '#include <thread>',
+ '#include <mutex>',
+ '#include <queue>',
+ '#include <string>',
+ '#include <condition_variable>',
+ '#include <sys/socket.h>',
+ '#include <sys/un.h>',
+ '#include <unistd.h>',
+ '#include <fcntl.h>',
+ '#include <sstream>',
+ '#include <sys/select.h>',
+ '#include <sys/time.h>',
+]:
+ s = s.replace(inc + "\n", "")
+
+bottom = r'''
+// ======================================\n// OpenVibe embedded JavaScript hook bridge\n// ======================================
+
+void OpenVibe_OnClientDisconnect( CBasePlayer *pPlayer )
+{
+ CHL2MP_Player *pHL2MP = ToHL2MPPlayer( pPlayer );
+ if ( pHL2MP )
+ OpenVibeJS_Server_PlayerDisconnected( pHL2MP );
+}
+
+void OpenVibe_OnPlayerDeath( CHL2MP_Player *pPlayer, CBaseEntity *pKiller )
+{
+ OpenVibeJS_Server_PlayerDeath( pPlayer, pKiller, NULL );
+}
+
+void OpenVibe_OnFrame()
+{
+ OpenVibeJS_ServerThink();
+}
+'''
+
+# Remove the huge legacy COpenVibeJsBridge class and old ov_js_cmd that spawned Node.
+marker = "// ==========================================\n// OpenVibe.JS - Scripting Engine C++ Bridge"
+start = s.find(marker)
+if start == -1:
+ # The exact banner can vary because of long ===== lines. Fall back to class start.
+ start = s.find("class COpenVibeJsBridge")
+ if start != -1:
+ # Include preceding banner if present.
+ banner = s.rfind("// =================================", 0, start)
+ if banner != -1:
+ start = banner
+
+if start != -1:
+ s = s[:start].rstrip() + "\n\n" + bottom + "\n"
+else:
+ # If already cleaned, normalize the bottom hook functions only.
+ def replace_function(src, regex, body):
+ m = re.search(regex, src)
+ if not m:
+ return src
+ brace = src.find('{', m.end())
+ if brace < 0:
+ return src
+ depth = 0
+ end = None
+ for i in range(brace, len(src)):
+ if src[i] == '{':
+ depth += 1
+ elif src[i] == '}':
+ depth -= 1
+ if depth == 0:
+ end = i
+ break
+ if end is None:
+ return src
+ return src[:brace+1] + "\n" + body.strip() + "\n" + src[end:]
+
+ s = replace_function(s, r'void\s+OpenVibe_OnClientDisconnect\s*\(\s*CBasePlayer\s*\*\s*pPlayer\s*\)',
+ ' CHL2MP_Player *pHL2MP = ToHL2MPPlayer( pPlayer );\n if ( pHL2MP )\n OpenVibeJS_Server_PlayerDisconnected( pHL2MP );')
+ s = replace_function(s, r'void\s+OpenVibe_OnPlayerDeath\s*\(\s*CHL2MP_Player\s*\*\s*pPlayer\s*,\s*CBaseEntity\s*\*\s*pKiller\s*\)',
+ ' OpenVibeJS_Server_PlayerDeath( pPlayer, pKiller, NULL );')
+ s = replace_function(s, r'void\s+OpenVibe_OnFrame\s*\(\s*\)', ' OpenVibeJS_ServerThink();')
+
+p.write_text(s)
+print("[openvibe] cleaned legacy Node bridge from openvibe_server.cpp")
+PY
+
+cat > sdk/openvibe/server/hl2mp/openvibe_js_server.h <<'CPP_H'
+#pragma once
+
+class CHL2MP_Player;
+class CBasePlayer;
+class CBaseEntity;
+
+void OpenVibeJS_ServerInit();
+void OpenVibeJS_ServerShutdown();
+void OpenVibeJS_ServerThink();
+
+void OpenVibeJS_Server_PlayerInitialSpawn(CHL2MP_Player *player);
+void OpenVibeJS_Server_PlayerSpawn(CHL2MP_Player *player);
+void OpenVibeJS_Server_PlayerDeath(CHL2MP_Player *victim, CBaseEntity *attacker, CBaseEntity *inflictor);
+void OpenVibeJS_Server_PlayerDisconnected(CHL2MP_Player *player);
+bool OpenVibeJS_Server_PlayerSay(CHL2MP_Player *player, const char *text);
+
+void OpenVibeJS_Server_ConsoleCommand(const char *text);
+void OpenVibeJS_Server_FireHook(const char *hookName);
+CPP_H
+
+python3 <<'PY'
+from pathlib import Path
+
+p = Path("sdk/openvibe/server/hl2mp/openvibe_js_server.cpp")
+s = p.read_text()
+
+if "OpenVibeJS_Server_ConsoleCommand" not in s:
+ insert = r'''
+void OpenVibeJS_Server_ConsoleCommand(const char *text)
+{
+ OpenVibeJS_EnsureStarted();
+
+ if (!OpenVibeJS_IsRunning() || !text)
+ return;
+
+ JSContext *ctx = g_OVServerJS.Context();
+ JSValue arg = JS_NewString(ctx, text);
+
+ JSValueConst argv[] = { arg };
+ g_OVServerJS.CallHookVoid("ConsoleCommand", 1, argv);
+
+ JS_FreeValue(ctx, arg);
+}
+
+void OpenVibeJS_Server_FireHook(const char *hookName)
+{
+ OpenVibeJS_EnsureStarted();
+
+ if (!OpenVibeJS_IsRunning() || !hookName || !hookName[0])
+ return;
+
+ g_OVServerJS.CallHookVoid(hookName);
+}
+
+static void OV_JsCmd_f(const CCommand &args)
+{
+ if (args.ArgC() < 2)
+ {
+ Msg("Usage: ov_js_cmd <text>\n");
+ return;
+ }
+
+ OpenVibeJS_Server_ConsoleCommand(args.ArgS());
+}
+
+static ConCommand ov_js_cmd(
+ "ov_js_cmd",
+ OV_JsCmd_f,
+ "Send a string to the embedded OpenVibe JS ConsoleCommand hook.",
+ FCVAR_GAMEDLL
+);
+
+static void OV_JSFire_f(const CCommand &args)
+{
+ if (args.ArgC() < 2)
+ {
+ Msg("Usage: ov_js_fire <HookName>\n");
+ return;
+ }
+
+ OpenVibeJS_Server_FireHook(args[1]);
+}
+
+static ConCommand ov_js_fire(
+ "ov_js_fire",
+ OV_JSFire_f,
+ "Fire an embedded OpenVibe JS hook by name.",
+ FCVAR_GAMEDLL
+);
+
+static void OV_JSStatus_f()
+{
+ Msg("[OV JS] enabled=%d running=%d mode=%s map=%s\n",
+ ov_js_enabled.GetBool() ? 1 : 0,
+ OpenVibeJS_IsRunning() ? 1 : 0,
+ ov_mode.GetString(),
+ gpGlobals ? STRING(gpGlobals->mapname) : "" );
+}
+
+static ConCommand ov_js_status(
+ "ov_js_status",
+ OV_JSStatus_f,
+ "Print embedded OpenVibe JS runtime status.",
+ FCVAR_GAMEDLL
+);
+'''
+ # Put commands after ov_js_reload command block.
+ s = s.rstrip() + "\n\n" + insert.lstrip() + "\n"
+
+p.write_text(s)
+print("[openvibe] added embedded ov_js_cmd / ov_js_fire / ov_js_status commands")
+PY
+
+python3 <<'PY'
+from pathlib import Path
+
+for path in [
+ Path("game/openvibe.games/js/gamemodes/base/server.js"),
+ Path("game/openvibe.games/js/gamemodes/hub/server.js"),
+]:
+ if not path.exists():
+ continue
+ s = path.read_text()
+ if "ConsoleCommand(" in s:
+ continue
+
+ snippet = '''
+
+ ConsoleCommand(text) {
+ OV.log(`ConsoleCommand: ${text}`);
+
+ if (text === "smoke" || text === "test") {
+ OV.broadcast("Embedded JS ConsoleCommand hook is working.");
+ return false;
+ }
+
+ return undefined;
+ },
+'''
+
+ marker = "\n Think()"
+ if marker in s:
+ s = s.replace(marker, snippet + marker, 1)
+ else:
+ s = s.replace("\n};", snippet.rstrip(",\n") + "\n};", 1)
+ path.write_text(s)
+ print(f"[openvibe] added ConsoleCommand hook to {path}")
+PY
+
+echo "[openvibe] cleaning apply-openvibe-sdk.sh duplicate stale link block"
+python3 <<'PY'
+from pathlib import Path
+import re
+
+p = Path("tools/apply-openvibe-sdk.sh")
+s = p.read_text()
+
+# Remove stale block after final echo, if present. It is harmless but confusing.
+s = re.sub(
+ r'\n\n# Link prebuilt QuickJS C static library\. QuickJS must be compiled as C, not C\+\+\.\nif ! grep -q '\''libquickjs_openvibe'\'' "\$SERVER_VPC"; then\n perl -0pi -e '\''s/\(\\\$File\\s\+"hl2mp\\\\openvibe_js_server\\\.cpp"\\n\)/\$1\\t\\t\\t\\\$Lib\\t"\.\.\\\\shared\\\\openvibe\\\\third_party\\\\quickjs\\\\build\\\\libquickjs_openvibe"\\n/s'\'' "\$SERVER_VPC"\n echo "\[openvibe-sdk\] linked QuickJS static library"\nfi\n*',
+ '\n',
+ s,
+ flags=re.S,
+)
+
+# Ensure final V2 block exists.
+if "OPENVIBE_FIX_QUICKJS_LINK_FINAL_V2" not in s:
+ final_block = r'''
+
+# OPENVIBE_FIX_QUICKJS_LINK_FINAL_V2
+# QuickJS is C, so do not compile its .c files through Source SDK/VPC C++.
+# We build libquickjs_openvibe.a with cc and link it as a library.
+if [[ -f "$SERVER_VPC" ]]; then
+ perl -0pi -e '
+ s/^.*quickjs\\quickjs\.c.*\n//mg;
+ s/^.*quickjs\\libregexp\.c.*\n//mg;
+ s/^.*quickjs\\libunicode\.c.*\n//mg;
+ s/^.*quickjs\\cutils\.c.*\n//mg;
+ s/^.*quickjs\\dtoa\.c.*\n//mg;
+ s/^.*quickjs\\libbf\.c.*\n//mg;
+ ' "$SERVER_VPC"
+
+ perl -0pi -e 's/^.*libquickjs_openvibe(?:\.a)?".*\n//mg' "$SERVER_VPC"
+
+ perl -0pi -e 's/(\$File\s+"hl2mp\\openvibe_js_server\.cpp"\n)/$1\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s' "$SERVER_VPC"
+
+ echo "[openvibe-sdk] linked QuickJS static library"
+fi
+'''
+ s = s.replace('echo "[openvibe-sdk] Source SDK OpenVibe patch applied"', final_block + '\n\necho "[openvibe-sdk] Source SDK OpenVibe patch applied"')
+
+p.write_text(s)
+PY
+
+echo "[openvibe] writing embedded JS runtime smoke helper"
+cat > tools/smoke-embedded-js-runtime.sh <<'SMOKE'
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
+cd "$ROOT"
+
+tools/apply-openvibe-sdk.sh
+tools/build-sdk-linux.sh
+tools/setup-openvibe-bin.sh
+
+echo
+cat <<'MSG'
+Embedded JS build/setup completed.
+
+Start the dev stack:
+ OPENVIBE_SRCDS_MAP_DELAY=3 tools/dev-up.sh
+
+Then in server console or via rcon/tmux pane, test:
+ ov_js_status
+ ov_js_fire Initialize
+ ov_js_cmd smoke
+
+Then in game:
+ connect 127.0.0.1:27015
+ say !js
+ say !hp
+ say !players
+MSG
+SMOKE
+chmod +x tools/smoke-embedded-js-runtime.sh
+
+echo "[openvibe] applying SDK patch"
+tools/apply-openvibe-sdk.sh
+
+if [[ "$RUN_BUILD" = "1" ]]; then
+ echo "[openvibe] running SDK build"
+ tools/build-sdk-linux.sh 2>&1 | tee "$HOME/ov-build.log"
+ echo "[openvibe] running setup-openvibe-bin.sh"
+ tools/setup-openvibe-bin.sh
+fi
+
+echo
+echo "[openvibe] next phase complete. Useful checks:"
+echo " grep -R \"COpenVibeJsBridge\|SpawnNodeJs\|system( cmd\" sdk/openvibe/server/hl2mp/openvibe_server.cpp || true"
+echo " grep -nE 'ov_js_cmd|ov_js_fire|ov_js_status' sdk/openvibe/server/hl2mp/openvibe_js_server.cpp"
+echo " tail -80 ~/ov-build.log"
+echo
+echo "Commit after testing:"
+echo " git add ."
+echo " git commit -m \"Remove legacy Node JS bridge and add embedded JS console hooks\""
+echo " git push"