mirror of
https://github.com/Mauler125/r5sdk.git
synced 2025-02-09 19:15:03 +01:00
Previously we did sq_pushroottable() and a subsequent sq_call() after compiling the text buffer, but this didn't work for code that was threaded, or using Get/SetNetVar* functions. The second issue was that the callback for the "script" command was ran in the main thread. Server script should always run in the server frame thread, the Set/GetNetVar* functions check thread id to retrieve the correct VM context, so running server script from the main thread ended up with Set/GetNetVar* functions retrieving the client VM context rather than server's, causing undefined behavior. Script commands are now queued to the server frame thread, ultimately fixing this bug. Also fixed a small bug with function 'sq_compilebuffer()'; it takes an extra argument but this wasn't taken into account in the SDK.
349 lines
14 KiB
C++
349 lines
14 KiB
C++
//=============================================================================//
|
|
//
|
|
// Purpose: Expose native code to VScript API
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
// See 'game/shared/vscript_shared.cpp' for more details.
|
|
//
|
|
//=============================================================================//
|
|
|
|
#include "core/stdafx.h"
|
|
#include "engine/server/server.h"
|
|
#include "game/shared/vscript_shared.h"
|
|
#include "vscript/vscript.h"
|
|
#include "vscript/languages/squirrel_re/include/sqvm.h"
|
|
|
|
#include "liveapi/liveapi.h"
|
|
#include "vscript_server.h"
|
|
#include <engine/host_state.h>
|
|
#include <networksystem/hostmanager.h>
|
|
|
|
/*
|
|
=====================
|
|
SQVM_ServerScript_f
|
|
|
|
Executes input on the
|
|
VM in SERVER context.
|
|
=====================
|
|
*/
|
|
static void SQVM_ServerScript_f(const CCommand& args)
|
|
{
|
|
if (args.ArgC() >= 2)
|
|
{
|
|
const char* code = args.ArgS();
|
|
|
|
if (!ThreadInServerFrameThread())
|
|
{
|
|
const string scode(code);
|
|
g_ServerTaskQueue.Dispatch([scode]()
|
|
{
|
|
Script_Execute(scode.c_str(), SQCONTEXT::SERVER);
|
|
}, 0);
|
|
return; // Only run in server frame thread.
|
|
}
|
|
|
|
Script_Execute(args.ArgS(), SQCONTEXT::SERVER);
|
|
}
|
|
}
|
|
static ConCommand script("script", SQVM_ServerScript_f, "Run input code as SERVER script on the VM", FCVAR_DEVELOPMENTONLY | FCVAR_GAMEDLL | FCVAR_CHEAT | FCVAR_SERVER_FRAME_THREAD);
|
|
|
|
namespace VScriptCode
|
|
{
|
|
namespace Server
|
|
{
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: create server via native serverbrowser entries
|
|
// TODO: return a boolean on failure instead of raising an error, so we could
|
|
// determine from scripts whether or not to spin a local server, or connect
|
|
// to a dedicated server (for disconnecting and loading the lobby, for example)
|
|
//-----------------------------------------------------------------------------
|
|
SQRESULT CreateServer(HSQUIRRELVM v)
|
|
{
|
|
const SQChar* serverName = nullptr;
|
|
const SQChar* serverDescription = nullptr;
|
|
const SQChar* serverMapName = nullptr;
|
|
const SQChar* serverPlaylist = nullptr;
|
|
|
|
sq_getstring(v, 2, &serverName);
|
|
sq_getstring(v, 3, &serverDescription);
|
|
sq_getstring(v, 4, &serverMapName);
|
|
sq_getstring(v, 5, &serverPlaylist);
|
|
|
|
SQInteger serverVisibility = 0;
|
|
sq_getinteger(v, 6, &serverVisibility);
|
|
|
|
if (!VALID_CHARSTAR(serverName) ||
|
|
!VALID_CHARSTAR(serverMapName) ||
|
|
!VALID_CHARSTAR(serverPlaylist))
|
|
{
|
|
v_SQVM_ScriptError("Empty or null server criteria");
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
|
|
}
|
|
|
|
// Adjust browser settings.
|
|
NetGameServer_t& details = g_ServerHostManager.GetDetails();
|
|
|
|
details.name = serverName;
|
|
details.description = serverDescription;
|
|
details.map = serverMapName;
|
|
details.playlist = serverPlaylist;
|
|
|
|
// Launch server.
|
|
g_ServerHostManager.SetVisibility(ServerVisibility_e(serverVisibility));
|
|
g_ServerHostManager.LaunchServer(g_pServer->IsActive());
|
|
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: shuts the server down and disconnects all clients
|
|
//-----------------------------------------------------------------------------
|
|
SQRESULT DestroyServer(HSQUIRRELVM v)
|
|
{
|
|
if (g_pHostState->m_bActiveGame)
|
|
g_pHostState->m_iNextState = HostStates_t::HS_GAME_SHUTDOWN;
|
|
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: sets whether the server could auto reload at this time (e.g. if
|
|
// server admin has host_autoReloadRate AND host_autoReloadRespectGameState
|
|
// set, and its time to auto reload, but the match hasn't finished yet, wait
|
|
// until this is set to proceed the reload of the server
|
|
//-----------------------------------------------------------------------------
|
|
SQRESULT SetAutoReloadState(HSQUIRRELVM v)
|
|
{
|
|
SQBool state = false;
|
|
sq_getbool(v, 2, &state);
|
|
|
|
g_hostReloadState = state;
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: kicks a player by given name
|
|
//-----------------------------------------------------------------------------
|
|
SQRESULT KickPlayerByName(HSQUIRRELVM v)
|
|
{
|
|
const SQChar* playerName = nullptr;
|
|
const SQChar* reason = nullptr;
|
|
|
|
sq_getstring(v, 2, &playerName);
|
|
sq_getstring(v, 3, &reason);
|
|
|
|
if (!VALID_CHARSTAR(playerName))
|
|
{
|
|
v_SQVM_ScriptError("Empty or null player name");
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
|
|
}
|
|
|
|
// Discard empty strings, this will use the default message instead.
|
|
if (!VALID_CHARSTAR(reason))
|
|
reason = nullptr;
|
|
|
|
g_BanSystem.KickPlayerByName(playerName, reason);
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: kicks a player by given handle or id
|
|
//-----------------------------------------------------------------------------
|
|
SQRESULT KickPlayerById(HSQUIRRELVM v)
|
|
{
|
|
const SQChar* playerHandle = nullptr;
|
|
const SQChar* reason = nullptr;
|
|
|
|
sq_getstring(v, 2, &playerHandle);
|
|
sq_getstring(v, 3, &reason);
|
|
|
|
if (!VALID_CHARSTAR(playerHandle))
|
|
{
|
|
v_SQVM_ScriptError("Empty or null player handle");
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
|
|
}
|
|
|
|
// Discard empty strings, this will use the default message instead.
|
|
if (!VALID_CHARSTAR(reason))
|
|
reason = nullptr;
|
|
|
|
g_BanSystem.KickPlayerById(playerHandle, reason);
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: bans a player by given name
|
|
//-----------------------------------------------------------------------------
|
|
SQRESULT BanPlayerByName(HSQUIRRELVM v)
|
|
{
|
|
const SQChar* playerName = nullptr;
|
|
const SQChar* reason = nullptr;
|
|
|
|
sq_getstring(v, 2, &playerName);
|
|
sq_getstring(v, 3, &reason);
|
|
|
|
if (!VALID_CHARSTAR(playerName))
|
|
{
|
|
v_SQVM_ScriptError("Empty or null player name");
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
|
|
}
|
|
|
|
// Discard empty strings, this will use the default message instead.
|
|
if (!VALID_CHARSTAR(reason))
|
|
reason = nullptr;
|
|
|
|
g_BanSystem.BanPlayerByName(playerName, reason);
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: bans a player by given handle or id
|
|
//-----------------------------------------------------------------------------
|
|
SQRESULT BanPlayerById(HSQUIRRELVM v)
|
|
{
|
|
const SQChar* playerHandle = nullptr;
|
|
const SQChar* reason = nullptr;
|
|
|
|
sq_getstring(v, 2, &playerHandle);
|
|
sq_getstring(v, 3, &reason);
|
|
|
|
if (!VALID_CHARSTAR(playerHandle))
|
|
{
|
|
v_SQVM_ScriptError("Empty or null player handle");
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
|
|
}
|
|
|
|
// Discard empty strings, this will use the default message instead.
|
|
if (!VALID_CHARSTAR(reason))
|
|
reason = nullptr;
|
|
|
|
g_BanSystem.BanPlayerById(playerHandle, reason);
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: unbans a player by given nucleus id or ip address
|
|
//-----------------------------------------------------------------------------
|
|
SQRESULT UnbanPlayer(HSQUIRRELVM v)
|
|
{
|
|
const SQChar* szCriteria = nullptr;
|
|
sq_getstring(v, 2, &szCriteria);
|
|
|
|
if (!VALID_CHARSTAR(szCriteria))
|
|
{
|
|
v_SQVM_ScriptError("Empty or null player criteria");
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
|
|
}
|
|
|
|
g_BanSystem.UnbanPlayer(szCriteria);
|
|
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: gets the number of real players on this server
|
|
//-----------------------------------------------------------------------------
|
|
SQRESULT GetNumHumanPlayers(HSQUIRRELVM v)
|
|
{
|
|
sq_pushinteger(v, g_pServer->GetNumHumanPlayers());
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: gets the number of fake players on this server
|
|
//-----------------------------------------------------------------------------
|
|
SQRESULT GetNumFakeClients(HSQUIRRELVM v)
|
|
{
|
|
sq_pushinteger(v, g_pServer->GetNumFakeClients());
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: gets the current server id
|
|
//-----------------------------------------------------------------------------
|
|
SQRESULT GetServerID(HSQUIRRELVM v)
|
|
{
|
|
sq_pushstring(v, g_LogSessionUUID.c_str(), (SQInteger)g_LogSessionUUID.length());
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: checks whether the server is active
|
|
//-----------------------------------------------------------------------------
|
|
SQRESULT IsServerActive(HSQUIRRELVM v)
|
|
{
|
|
bool isActive = g_pServer->IsActive();
|
|
sq_pushbool(v, isActive);
|
|
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: checks whether this SDK build is a dedicated server
|
|
//-----------------------------------------------------------------------------
|
|
SQRESULT IsDedicated(HSQUIRRELVM v)
|
|
{
|
|
sq_pushbool(v, ::IsDedicated());
|
|
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------
|
|
// Purpose: registers script functions in SERVER context
|
|
// Input : *s -
|
|
//---------------------------------------------------------------------------------
|
|
void Script_RegisterServerFunctions(CSquirrelVM* s)
|
|
{
|
|
Script_RegisterCommonAbstractions(s);
|
|
Script_RegisterCoreServerFunctions(s);
|
|
Script_RegisterAdminPanelFunctions(s);
|
|
|
|
Script_RegisterLiveAPIFunctions(s);
|
|
}
|
|
|
|
void Script_RegisterServerEnums(CSquirrelVM* const s)
|
|
{
|
|
Script_RegisterLiveAPIEnums(s);
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------
|
|
// Purpose: core server script functions
|
|
// Input : *s -
|
|
//---------------------------------------------------------------------------------
|
|
void Script_RegisterCoreServerFunctions(CSquirrelVM* s)
|
|
{
|
|
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, IsServerActive, "Returns whether the server is active", "bool", "");
|
|
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, IsDedicated, "Returns whether this is a dedicated server", "bool", "");
|
|
|
|
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, CreateServer, "Starts server with the specified settings", "void", "string, string, string, string, int");
|
|
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, DestroyServer, "Shuts the local server down", "void", "");
|
|
|
|
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, SetAutoReloadState, "Set whether we can auto-reload the server", "void", "bool");
|
|
|
|
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, GetServerID, "Gets the current server ID", "string", "");
|
|
}
|
|
|
|
//---------------------------------------------------------------------------------
|
|
// Purpose: admin panel script functions
|
|
// Input : *s -
|
|
//
|
|
// Ideally, these get dropped entirely in favor of remote functions. Currently,
|
|
// the s3 build only supports remote function calls from server to client/ui.
|
|
// Client/ui to server is all done through clientcommands.
|
|
//---------------------------------------------------------------------------------
|
|
void Script_RegisterAdminPanelFunctions(CSquirrelVM* s)
|
|
{
|
|
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, GetNumHumanPlayers, "Gets the number of human players on the server", "int", "");
|
|
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, GetNumFakeClients, "Gets the number of bot players on the server", "int", "");
|
|
|
|
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, KickPlayerByName, "Kicks a player from the server by name", "void", "string, string");
|
|
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, KickPlayerById, "Kicks a player from the server by handle or nucleus id", "void", "string, string");
|
|
|
|
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, BanPlayerByName, "Bans a player from the server by name", "void", "string, string");
|
|
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, BanPlayerById, "Bans a player from the server by handle or nucleus id", "void", "string, string");
|
|
|
|
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, UnbanPlayer, "Unbans a player from the server by nucleus id or ip address", "void", "string");
|
|
}
|