r5sdk/r5dev/game/client/vscript_client.cpp
Kawe Mazidjatari 250fd504af VScript: properly implement 'sq_getstring' and 'sq_getinteger' + more
sq_getstring and sq_getinteger are now properly implemented (code matches squirrel code, and generated assembly matches that of the game). Adjusted call sites to accommodate the new implementation and added a few extra checks. Also added:
* sq_getfloat
* sq_getbool
* sq_getthread
2024-04-05 18:40:37 +02:00

511 lines
20 KiB
C++

//=============================================================================//
//
// Purpose: Expose native code to VScript API
//
//-----------------------------------------------------------------------------
//
// See 'game/shared/vscript_shared.cpp' for more details.
//
//=============================================================================//
#include "core/stdafx.h"
#include "tier1/keyvalues.h"
#include "engine/cmodel_bsp.h"
#include "engine/host_state.h"
#include "engine/client/cl_main.h"
#include "networksystem/pylon.h"
#include "networksystem/listmanager.h"
#include "game/shared/vscript_shared.h"
#include "vscript/vscript.h"
#include "vscript/languages/squirrel_re/include/sqvm.h"
#include "vscript_client.h"
/*
=====================
SQVM_ClientScript_f
Executes input on the
VM in CLIENT context.
=====================
*/
static void SQVM_ClientScript_f(const CCommand& args)
{
if (args.ArgC() >= 2)
{
Script_Execute(args.ArgS(), SQCONTEXT::CLIENT);
}
}
/*
=====================
SQVM_UIScript_f
Executes input on the
VM in UI context.
=====================
*/
static void SQVM_UIScript_f(const CCommand& args)
{
if (args.ArgC() >= 2)
{
Script_Execute(args.ArgS(), SQCONTEXT::UI);
}
}
static ConCommand script_client("script_client", SQVM_ClientScript_f, "Run input code as CLIENT script on the VM", FCVAR_DEVELOPMENTONLY | FCVAR_CLIENTDLL | FCVAR_CHEAT);
static ConCommand script_ui("script_ui", SQVM_UIScript_f, "Run input code as UI script on the VM", FCVAR_DEVELOPMENTONLY | FCVAR_CLIENTDLL | FCVAR_CHEAT);
//-----------------------------------------------------------------------------
// Purpose: checks if the server index is valid, raises an error if not
//-----------------------------------------------------------------------------
static SQBool Script_CheckServerIndexAndFailure(HSQUIRRELVM v, SQInteger iServer)
{
SQInteger iCount = static_cast<SQInteger>(g_ServerListManager.m_vServerList.size());
if (iServer >= iCount)
{
v_SQVM_RaiseError(v, "Index must be less than %i.\n", iCount);
return false;
}
else if (iServer == -1) // If its still -1, then 'sq_getinteger' failed
{
v_SQVM_RaiseError(v, "Invalid argument type provided.\n");
return false;
}
return true;
}
namespace VScriptCode
{
namespace Client
{
//-----------------------------------------------------------------------------
// Purpose: refreshes the server list
//-----------------------------------------------------------------------------
SQRESULT RefreshServerList(HSQUIRRELVM v)
{
string serverMessage; // Refresh list.
size_t iCount;
// TODO: return error string on failure?
g_ServerListManager.RefreshServerList(serverMessage, iCount);
sq_pushinteger(v, static_cast<SQInteger>(iCount));
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
//-----------------------------------------------------------------------------
// Purpose: get current server count from pylon
//-----------------------------------------------------------------------------
SQRESULT GetServerCount(HSQUIRRELVM v)
{
size_t iCount = g_ServerListManager.m_vServerList.size();
sq_pushinteger(v, static_cast<SQInteger>(iCount));
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
//-----------------------------------------------------------------------------
// Purpose: get response from private server request
//-----------------------------------------------------------------------------
SQRESULT GetHiddenServerName(HSQUIRRELVM v)
{
const SQChar* privateToken = nullptr;
if (SQ_FAILED(sq_getstring(v, 2, &privateToken)) || VALID_CHARSTAR(privateToken))
{
v_SQVM_ScriptError("Empty or null private token");
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
}
string hiddenServerRequestMessage;
NetGameServer_t serverListing;
bool result = g_MasterServer.GetServerByToken(serverListing, hiddenServerRequestMessage, privateToken); // Send token connect request.
if (!result)
{
if (hiddenServerRequestMessage.empty())
sq_pushstring(v, "Request failed", -1);
else
{
hiddenServerRequestMessage = Format("Request failed: %s", hiddenServerRequestMessage.c_str());
sq_pushstring(v, hiddenServerRequestMessage.c_str(), -1);
}
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
if (serverListing.name.empty())
{
if (hiddenServerRequestMessage.empty())
hiddenServerRequestMessage = Format("Server listing empty");
else
hiddenServerRequestMessage = Format("Server listing empty: %s", hiddenServerRequestMessage.c_str());
sq_pushstring(v, hiddenServerRequestMessage.c_str(), -1);
}
else
{
hiddenServerRequestMessage = Format("Found server: %s", serverListing.name.c_str());
sq_pushstring(v, hiddenServerRequestMessage.c_str(), -1);
}
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
//-----------------------------------------------------------------------------
// Purpose: get server's current name from serverlist index
//-----------------------------------------------------------------------------
SQRESULT GetServerName(HSQUIRRELVM v)
{
AUTO_LOCK(g_ServerListManager.m_Mutex);
SQInteger iServer = -1;
sq_getinteger(v, 2, &iServer);
if (!Script_CheckServerIndexAndFailure(v, iServer))
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
const string& serverName = g_ServerListManager.m_vServerList[iServer].name;
sq_pushstring(v, serverName.c_str(), -1);
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
//-----------------------------------------------------------------------------
// Purpose: get server's current description from serverlist index
//-----------------------------------------------------------------------------
SQRESULT GetServerDescription(HSQUIRRELVM v)
{
AUTO_LOCK(g_ServerListManager.m_Mutex);
SQInteger iServer = -1;
sq_getinteger(v, 2, &iServer);
if (!Script_CheckServerIndexAndFailure(v, iServer))
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
const string& serverDescription = g_ServerListManager.m_vServerList[iServer].description;
sq_pushstring(v, serverDescription.c_str(), -1);
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
//-----------------------------------------------------------------------------
// Purpose: get server's current map via serverlist index
//-----------------------------------------------------------------------------
SQRESULT GetServerMap(HSQUIRRELVM v)
{
AUTO_LOCK(g_ServerListManager.m_Mutex);
SQInteger iServer = -1;
sq_getinteger(v, 2, &iServer);
if (!Script_CheckServerIndexAndFailure(v, iServer))
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
const string& svServerMapName = g_ServerListManager.m_vServerList[iServer].map;
sq_pushstring(v, svServerMapName.c_str(), -1);
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
//-----------------------------------------------------------------------------
// Purpose: get server's current playlist via serverlist index
//-----------------------------------------------------------------------------
SQRESULT GetServerPlaylist(HSQUIRRELVM v)
{
AUTO_LOCK(g_ServerListManager.m_Mutex);
SQInteger iServer = -1;
sq_getinteger(v, 2, &iServer);
if (!Script_CheckServerIndexAndFailure(v, iServer))
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
const string& serverPlaylist = g_ServerListManager.m_vServerList[iServer].playlist;
sq_pushstring(v, serverPlaylist.c_str(), -1);
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
//-----------------------------------------------------------------------------
// Purpose: get server's current player count via serverlist index
//-----------------------------------------------------------------------------
SQRESULT GetServerCurrentPlayers(HSQUIRRELVM v)
{
AUTO_LOCK(g_ServerListManager.m_Mutex);
SQInteger iServer = -1;
sq_getinteger(v, 2, &iServer);
if (!Script_CheckServerIndexAndFailure(v, iServer))
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
const SQInteger playerCount = g_ServerListManager.m_vServerList[iServer].numPlayers;
sq_pushinteger(v, playerCount);
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
//-----------------------------------------------------------------------------
// Purpose: get server's current player count via serverlist index
//-----------------------------------------------------------------------------
SQRESULT GetServerMaxPlayers(HSQUIRRELVM v)
{
AUTO_LOCK(g_ServerListManager.m_Mutex);
SQInteger iServer = -1;
sq_getinteger(v, 2, &iServer);
if (!Script_CheckServerIndexAndFailure(v, iServer))
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
const SQInteger maxPlayers = g_ServerListManager.m_vServerList[iServer].maxPlayers;
sq_pushinteger(v, maxPlayers);
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
//-----------------------------------------------------------------------------
// Purpose: get promo data for serverbrowser panels
//-----------------------------------------------------------------------------
SQRESULT GetPromoData(HSQUIRRELVM v)
{
enum class R5RPromoData : SQInteger
{
PromoLargeTitle,
PromoLargeDesc,
PromoLeftTitle,
PromoLeftDesc,
PromoRightTitle,
PromoRightDesc
};
SQInteger idx = 0;
sq_getinteger(v, 2, &idx);
R5RPromoData ePromoIndex = static_cast<R5RPromoData>(idx);
const char* pszPromoKey;
switch (ePromoIndex)
{
case R5RPromoData::PromoLargeTitle:
{
pszPromoKey = "#PROMO_LARGE_TITLE";
break;
}
case R5RPromoData::PromoLargeDesc:
{
pszPromoKey = "#PROMO_LARGE_DESCRIPTION";
break;
}
case R5RPromoData::PromoLeftTitle:
{
pszPromoKey = "#PROMO_LEFT_TITLE";
break;
}
case R5RPromoData::PromoLeftDesc:
{
pszPromoKey = "#PROMO_LEFT_DESCRIPTION";
break;
}
case R5RPromoData::PromoRightTitle:
{
pszPromoKey = "#PROMO_RIGHT_TITLE";
break;
}
case R5RPromoData::PromoRightDesc:
{
pszPromoKey = "#PROMO_RIGHT_DESCRIPTION";
break;
}
default:
{
pszPromoKey = "#PROMO_SDK_ERROR";
break;
}
}
sq_pushstring(v, pszPromoKey, -1);
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
SQRESULT GetEULAContents(HSQUIRRELVM v)
{
MSEulaData_t eulaData;
string eulaRequestMessage;
if (g_MasterServer.GetEULA(eulaData, eulaRequestMessage))
{
// set EULA version cvar to the newly fetched EULA version
eula_version->SetValue(eulaData.version);
sq_pushstring(v, eulaData.contents.c_str(), -1);
}
else
{
string error = Format("Failed to load EULA Data: %s", eulaRequestMessage.c_str());
Warning(eDLL_T::UI, "%s\n", error.c_str());
sq_pushstring(v, error.c_str(), -1);
}
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
//-----------------------------------------------------------------------------
// Purpose: connect to server from native server browser entries
//-----------------------------------------------------------------------------
SQRESULT ConnectToServer(HSQUIRRELVM v)
{
const SQChar* ipAddress = nullptr;
if (SQ_FAILED(sq_getstring(v, 2, &ipAddress)))
{
v_SQVM_ScriptError("Missing ip address");
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
}
const SQChar* cryptoKey = nullptr;
if (SQ_FAILED(sq_getstring(v, 3, &cryptoKey)))
{
v_SQVM_ScriptError("Missing encryption key");
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
}
Msg(eDLL_T::UI, "Connecting to server with ip address '%s' and encryption key '%s'\n", ipAddress, cryptoKey);
g_ServerListManager.ConnectToServer(ipAddress, cryptoKey);
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
//-----------------------------------------------------------------------------
// Purpose: set netchannel encryption key and connect to server
//-----------------------------------------------------------------------------
SQRESULT ConnectToListedServer(HSQUIRRELVM v)
{
AUTO_LOCK(g_ServerListManager.m_Mutex);
SQInteger iServer = -1;
sq_getinteger(v, 2, &iServer);
if (!Script_CheckServerIndexAndFailure(v, iServer))
{
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
}
const NetGameServer_t& gameServer = g_ServerListManager.m_vServerList[iServer];
g_ServerListManager.ConnectToServer(gameServer.address, gameServer.port,
gameServer.netKey);
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
//-----------------------------------------------------------------------------
// Purpose: request token from pylon and join server with result.
//-----------------------------------------------------------------------------
SQRESULT ConnectToHiddenServer(HSQUIRRELVM v)
{
const SQChar* privateToken = nullptr;
const SQRESULT strRet = sq_getstring(v, 2, &privateToken);
if (SQ_FAILED(strRet) || VALID_CHARSTAR(privateToken))
{
v_SQVM_ScriptError("Empty or null private token");
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
}
string hiddenServerRequestMessage;
NetGameServer_t netListing;
const bool result = g_MasterServer.GetServerByToken(netListing, hiddenServerRequestMessage, privateToken); // Send token connect request.
if (result)
{
g_ServerListManager.ConnectToServer(netListing.address, netListing.port, netListing.netKey);
}
else
{
Warning(eDLL_T::UI, "Failed to connect to private server: %s\n", hiddenServerRequestMessage.c_str());
}
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
//-----------------------------------------------------------------------------
// Purpose: checks whether this SDK build is a client dll
//-----------------------------------------------------------------------------
SQRESULT IsClientDLL(HSQUIRRELVM v)
{
sq_pushbool(v, ::IsClientDLL());
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
}
}
//---------------------------------------------------------------------------------
// Purpose: registers script functions in CLIENT context
// Input : *s -
//---------------------------------------------------------------------------------
void Script_RegisterClientFunctions(CSquirrelVM* s)
{
Script_RegisterCommonAbstractions(s);
Script_RegisterCoreClientFunctions(s);
}
//---------------------------------------------------------------------------------
// Purpose: registers script functions in UI context
// Input : *s -
//---------------------------------------------------------------------------------
void Script_RegisterUIFunctions(CSquirrelVM* s)
{
Script_RegisterCommonAbstractions(s);
Script_RegisterCoreClientFunctions(s);
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, RefreshServerList, "Refreshes the public server list and returns the count", "int", "");
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, GetServerCount, "Gets the number of public servers", "int", "");
// Functions for retrieving server browser data
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, GetHiddenServerName, "Gets hidden server name by token", "string", "string");
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, GetServerName, "Gets the name of the server at the specified index of the server list", "string", "int");
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, GetServerDescription, "Gets the description of the server at the specified index of the server list", "string", "int");
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, GetServerMap, "Gets the map of the server at the specified index of the server list", "string", "int");
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, GetServerPlaylist, "Gets the playlist of the server at the specified index of the server list", "string", "int");
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, GetServerCurrentPlayers, "Gets the current player count of the server at the specified index of the server list", "int", "int");
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, GetServerMaxPlayers, "Gets the max player count of the server at the specified index of the server list", "int", "int");
// Misc main menu functions
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, GetPromoData, "Gets promo data for specified slot type", "string", "int");
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, GetEULAContents, "Gets EULA contents from masterserver", "string", "");
// Functions for connecting to servers
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, ConnectToServer, "Joins server by ip address and encryption key", "void", "string, string");
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, ConnectToListedServer, "Joins listed server by index", "void", "int");
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, ConnectToHiddenServer, "Joins hidden server by token", "void", "string");
}
//---------------------------------------------------------------------------------
// Purpose: core client script functions
// Input : *s -
//---------------------------------------------------------------------------------
void Script_RegisterCoreClientFunctions(CSquirrelVM* s)
{
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, IsClientDLL, "Returns whether this build is client only", "bool", "");
}
//---------------------------------------------------------------------------------
// Purpose: console variables for scripts, these should not be used in engine/sdk code !!!
//---------------------------------------------------------------------------------
static ConVar settings_reflex("settings_reflex", "1", FCVAR_RELEASE, "Selected NVIDIA Reflex mode.", "0 = Off. 1 = On. 2 = On + Boost.");
static ConVar serverbrowser_hideEmptyServers("serverbrowser_hideEmptyServers", "0", FCVAR_RELEASE, "Hide empty servers in the server browser.");
static ConVar serverbrowser_mapFilter("serverbrowser_mapFilter", "0", FCVAR_RELEASE, "Filter servers by map in the server browser.");
static ConVar serverbrowser_gamemodeFilter("serverbrowser_gamemodeFilter", "0", FCVAR_RELEASE, "Filter servers by gamemode in the server browser.");
// NOTE: if we want to make a certain promo only show once, add the playerprofile flag to the cvar below. Current behavior = always show after game restart.
static ConVar promo_version_accepted("promo_version_accepted", "0", FCVAR_RELEASE, "The accepted promo version.");