Engine: large server host code refactor

This code was written at the start of the project before much of the engine was reverse engineered. There were some design problems where core server functions were shared with UI script.

A second problem was that not everything was properly synced between another; CServerHostManager held its own instance of the server name and server description, while the engine has actual convars to store these. So scripters weren't able to get the server name and server description if the server was a listen server and launched through the title screen.

The CServerHostManager class has been reworked to not keep a second instance, and the script functions for creating the servers have been reworked to always store the name and description in aforementioned convars.

Also moved the CreateServer and DestroyServer function to the UI namespace of scripts, and it is now only registered in UI context. The server should use GameRules_ChangeMap() to switch levels instead.

UI script also no longer registers core server functions, this was a design mistake and has been fully factored out in this patch.

The server script function 'SetClassVarSynced' has also been deprecated and removed, since calling this sends a netmsg to all connected clients, and running this once will not apply the new class var values to clients connecting afterwards. This should be managed through player.SetClassVar in the connect codecallbacks in scripts.
This commit is contained in:
Kawe Mazidjatari 2024-12-05 15:53:40 +01:00
parent 67b43192ef
commit 3209af9a6c
13 changed files with 227 additions and 265 deletions

View File

@ -267,15 +267,17 @@ void Systems_Init()
#ifndef CLIENT_DLL
ServerScriptRegister_Callback = Script_RegisterServerFunctions;
CoreServerScriptRegister_Callback = Script_RegisterCoreServerFunctions;
AdminPanelScriptRegister_Callback = Script_RegisterAdminPanelFunctions;
ServerScriptRegisterEnum_Callback = Script_RegisterServerEnums;
#endif// !CLIENT_DLL
#ifndef SERVER_DLL
ClientScriptRegister_Callback = Script_RegisterClientFunctions;
UiScriptRegister_Callback = Script_RegisterUIFunctions;
#ifndef CLIENT_DLL
UiServerScriptRegister_Callback = Script_RegisterUIServerFunctions;
UiAdminPanelScriptRegister_Callback = Script_RegisterAdminServerFunctions;
#endif // !CLIENT_DLL
#endif // !SERVER_DLL
#ifdef CLIENT_DLL

View File

@ -63,8 +63,9 @@ static ConVar host_autoReloadRate("host_autoReloadRate", "0", FCVAR_RELEASE, "Ti
static ConVar host_autoReloadRespectGameState("host_autoReloadRespectGameState", "0", FCVAR_RELEASE, "Check the game state before proceeding to auto-reload (don't reload in the middle of a match).");
#endif // !CLIENT_DLL
ConVar hostdesc("hostdesc", "", FCVAR_RELEASE, "Host game server description.");
#ifdef DEDICATED
static ConVar hostdesc("hostdesc", "", FCVAR_RELEASE, "Host game server description.");
//-----------------------------------------------------------------------------
// Purpose: Send keep alive request to Pylon Master Server.
// Output : Returns true on success, false otherwise.

View File

@ -1,6 +1,8 @@
#pragma once
#include "mathlib/vector.h"
extern ConVar hostdesc;
enum class HostStates_t : int
{
HS_NEW_GAME = 0x0,

View File

@ -15,6 +15,7 @@
#include "engine/client/cl_main.h"
#include "networksystem/pylon.h"
#include "networksystem/listmanager.h"
#include "networksystem/hostmanager.h"
#include "game/shared/vscript_shared.h"
#include "vscript/vscript.h"
@ -81,6 +82,17 @@ static SQBool Script_CheckServerIndexAndFailure(HSQUIRRELVM v, SQInteger iServer
namespace VScriptCode
{
namespace Client
{
//-----------------------------------------------------------------------------
// 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);
}
}
namespace Ui
{
//-----------------------------------------------------------------------------
// Purpose: refreshes the server list
@ -436,11 +448,52 @@ namespace VScriptCode
}
//-----------------------------------------------------------------------------
// Purpose: checks whether this SDK build is a client dll
// 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 IsClientDLL(HSQUIRRELVM v)
SQRESULT CreateServer(HSQUIRRELVM v)
{
sq_pushbool(v, ::IsClientDLL());
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);
}
hostname->SetValue(serverName);
hostdesc.SetValue(serverDescription);
// Launch server.
g_ServerHostManager.SetVisibility(ServerVisibility_e(serverVisibility));
g_ServerHostManager.LaunchServer(serverMapName, serverPlaylist);
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);
}
}
@ -456,6 +509,15 @@ void Script_RegisterClientFunctions(CSquirrelVM* s)
Script_RegisterCoreClientFunctions(s);
}
//---------------------------------------------------------------------------------
// 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: registers script functions in UI context
// Input : *s -
@ -465,37 +527,34 @@ 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", "");
DEFINE_UI_SCRIPTFUNC_NAMED(s, RefreshServerList, "Refreshes the public server list and returns the count", "int", "");
DEFINE_UI_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_UI_SCRIPTFUNC_NAMED(s, GetHiddenServerName, "Gets hidden server name by token", "string", "string");
DEFINE_UI_SCRIPTFUNC_NAMED(s, GetServerName, "Gets the name of the server at the specified index of the server list", "string", "int");
DEFINE_UI_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_UI_SCRIPTFUNC_NAMED(s, GetServerMap, "Gets the map of the server at the specified index of the server list", "string", "int");
DEFINE_UI_SCRIPTFUNC_NAMED(s, GetServerPlaylist, "Gets the playlist of the server at the specified index of the server list", "string", "int");
DEFINE_UI_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");
DEFINE_UI_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", "");
DEFINE_UI_SCRIPTFUNC_NAMED(s, GetPromoData, "Gets promo data for specified slot type", "string", "int");
DEFINE_UI_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");
DEFINE_UI_SCRIPTFUNC_NAMED(s, ConnectToServer, "Joins server by ip address and encryption key", "void", "string, string");
DEFINE_UI_SCRIPTFUNC_NAMED(s, ConnectToListedServer, "Joins listed server by index", "void", "int");
DEFINE_UI_SCRIPTFUNC_NAMED(s, ConnectToHiddenServer, "Joins hidden server by token", "void", "string");
}
//---------------------------------------------------------------------------------
// Purpose: core client script functions
// Input : *s -
//---------------------------------------------------------------------------------
void Script_RegisterCoreClientFunctions(CSquirrelVM* s)
void Script_RegisterUIServerFunctions(CSquirrelVM* s)
{
DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, IsClientDLL, "Returns whether this build is client only", "bool", "");
DEFINE_UI_SCRIPTFUNC_NAMED(s, CreateServer, "Starts server with the specified settings", "void", "string, string, string, string, int");
DEFINE_UI_SCRIPTFUNC_NAMED(s, DestroyServer, "Shuts the local server down", "void", "");
}
//---------------------------------------------------------------------------------

View File

@ -4,6 +4,11 @@
namespace VScriptCode
{
namespace Client
{
SQRESULT IsClientDLL(HSQUIRRELVM v);
}
namespace Ui
{
SQRESULT RefreshServerList(HSQUIRRELVM v);
SQRESULT GetServerCount(HSQUIRRELVM v);
@ -23,13 +28,12 @@ namespace VScriptCode
SQRESULT ConnectToListedServer(HSQUIRRELVM v);
SQRESULT ConnectToHiddenServer(HSQUIRRELVM v);
SQRESULT ConnectToServer(HSQUIRRELVM v);
SQRESULT IsClientDLL(HSQUIRRELVM v);
}
}
void Script_RegisterClientFunctions(CSquirrelVM* s);
void Script_RegisterUIFunctions(CSquirrelVM* s);
void Script_RegisterUIServerFunctions(CSquirrelVM* s);
void Script_RegisterCoreClientFunctions(CSquirrelVM* s);
#define DEFINE_CLIENT_SCRIPTFUNC_NAMED(s, functionName, helpString, \
@ -37,6 +41,11 @@ void Script_RegisterCoreClientFunctions(CSquirrelVM* s);
s->RegisterFunction(#functionName, MKSTRING(Script_##functionName), \
helpString, returnType, parameters, VScriptCode::Client::##functionName); \
#define DEFINE_UI_SCRIPTFUNC_NAMED(s, functionName, helpString, \
returnType, parameters) \
s->RegisterFunction(#functionName, MKSTRING(Script_##functionName), \
helpString, returnType, parameters, VScriptCode::Ui::##functionName); \
inline void (*v_Script_RegisterClientEntityClassFuncs)();
inline void (*v_Script_RegisterClientPlayerClassFuncs)();
inline void (*v_Script_RegisterClientAIClassFuncs)();

View File

@ -17,7 +17,6 @@
#include "liveapi/liveapi.h"
#include "vscript_server.h"
#include <engine/host_state.h>
#include <networksystem/hostmanager.h>
#include "player.h"
#include <common/callback.h>
@ -42,61 +41,6 @@ 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
@ -268,75 +212,6 @@ namespace VScriptCode
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: sets a class var on the server and each client
// TODO: it might also be good to research potential ways to track class var
// changes and sync them back to clients connecting after this has been called.
//-----------------------------------------------------------------------------
SQRESULT SetClassVarSynced(HSQUIRRELVM v)
{
const SQChar* key = nullptr;
sq_getstring(v, 2, &key);
if (!VALID_CHARSTAR(key))
{
v_SQVM_ScriptError("Empty or null class key");
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
}
const SQChar* val = nullptr;
sq_getstring(v, 3, &val);
if (!VALID_CHARSTAR(val))
{
v_SQVM_ScriptError("Empty or null class var");
SCRIPT_CHECK_AND_RETURN(v, SQ_ERROR);
}
const char* pArgs[3] = {
"_setClassVarServer",
key,
val
};
SVC_SetClassVar msg(key, val);
const CCommand cmd((int)V_ARRAYSIZE(pArgs), pArgs, cmd_source_t::kCommandSrcCode);
bool failure = false;
const int oldIdx = *g_nCommandClientIndex;
for (int i = 0; i < gpGlobals->maxClients; i++)
{
CClient* const client = g_pServer->GetClient(i);
// is this client fully connected
if (client->GetSignonState() != SIGNONSTATE::SIGNONSTATE_FULL)
continue;
if (client->SendNetMsgEx(&msg, false, true, false))
{
*g_nCommandClientIndex = client->GetUserID();
v__setClassVarServer_f(cmd);
}
else // Not all clients have their class var set.
failure = true;
}
*g_nCommandClientIndex = oldIdx;
sq_pushbool(v, !failure);
SCRIPT_CHECK_AND_RETURN(v, SQ_OK);
}
}
namespace PlayerEntity
@ -405,7 +280,7 @@ void Script_RegisterServerFunctions(CSquirrelVM* s)
{
Script_RegisterCommonAbstractions(s);
Script_RegisterCoreServerFunctions(s);
Script_RegisterAdminPanelFunctions(s);
Script_RegisterAdminServerFunctions(s);
Script_RegisterLiveAPIFunctions(s);
}
@ -421,28 +296,15 @@ void Script_RegisterServerEnums(CSquirrelVM* const 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", "");
DEFINE_SERVER_SCRIPTFUNC_NAMED(s, SetClassVarSynced, "Change a variable in the class settings for server and all connected clients", "bool", "string, string");
}
//---------------------------------------------------------------------------------
// Purpose: admin panel script functions
// Purpose: admin server 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)
void Script_RegisterAdminServerFunctions(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", "");

View File

@ -29,7 +29,7 @@ namespace VScriptCode
void Script_RegisterServerFunctions(CSquirrelVM* s);
void Script_RegisterCoreServerFunctions(CSquirrelVM* s);
void Script_RegisterAdminPanelFunctions(CSquirrelVM* s);
void Script_RegisterAdminServerFunctions(CSquirrelVM* s);
void Script_RegisterServerEnums(CSquirrelVM* const s);

View File

@ -58,6 +58,9 @@ CBrowser::CBrowser(void)
memset(m_serverNetKeyTextBuf, '\0', sizeof(m_serverNetKeyTextBuf));
m_lockedIconDataResource = GetModuleResource(IDB_PNG2);
m_levelName = "mp_lobby";
m_gameMode = "dev_default";
}
//-----------------------------------------------------------------------------
@ -517,47 +520,71 @@ void CBrowser::HiddenServersModal(void)
}
}
void CBrowser::HandleInvalidFields(const bool offline)
{
if (!offline && m_serverName.empty())
{
m_hostMessage = "Server name is required.";
m_hostMessageColor = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
}
else if (m_gameMode.empty())
{
m_hostMessage = "Game mode is required.";
m_hostMessageColor = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
}
else if (m_levelName.empty())
{
m_hostMessage = "Level name is required.";
m_hostMessageColor = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
}
}
//-----------------------------------------------------------------------------
// Purpose: draws the host section
//-----------------------------------------------------------------------------
void CBrowser::DrawHostPanel(void)
{
#ifndef CLIENT_DLL
NetGameServer_t& details = g_ServerHostManager.GetDetails();
if (ImGui::InputTextWithHint("##ServerHost_ServerName", "Server name (required)", &m_serverName))
{
hostname->SetValue(m_serverName.c_str());
}
if (ImGui::InputTextWithHint("##ServerHost_ServerDesc", "Server description (optional)", &m_serverDescription))
{
hostdesc.SetValue(m_serverDescription.c_str());
}
ImGui::InputTextWithHint("##ServerHost_ServerName", "Server name (required)", &details.name);
ImGui::InputTextWithHint("##ServerHost_ServerDesc", "Server description (optional)", &details.description);
ImGui::Spacing();
const char* const selectedPlaylists = details.playlist.c_str();
if (ImGui::BeginCombo("Mode", selectedPlaylists))
if (ImGui::BeginCombo("Mode", m_gameMode.c_str()))
{
for (const CUtlString& svPlaylist : g_vecAllPlaylists)
for (const CUtlString& playlist : g_vecAllPlaylists)
{
const char* const cachedPlaylists = svPlaylist.String();
const char* const cachedPlaylists = playlist.String();
if (ImGui::Selectable(cachedPlaylists, (strcmp(cachedPlaylists, selectedPlaylists) == 0)))
if (ImGui::Selectable(cachedPlaylists,
playlist.IsEqual_CaseInsensitive(m_gameMode.c_str())))
{
details.playlist = svPlaylist;
m_gameMode = cachedPlaylists;
}
}
ImGui::EndCombo();
}
if (ImGui::BeginCombo("Map", details.map.c_str()))
if (ImGui::BeginCombo("Map", m_levelName.c_str()))
{
g_InstalledMapsMutex.Lock();
FOR_EACH_VEC(g_InstalledMaps, i)
for (const CUtlString& mapName : g_InstalledMaps)
{
const CUtlString& mapName = g_InstalledMaps[i];
const char* const cachedMapName = mapName.String();
if (ImGui::Selectable(mapName.String(),
mapName.IsEqual_CaseInsensitive(details.map.c_str())))
if (ImGui::Selectable(cachedMapName,
mapName.IsEqual_CaseInsensitive(m_levelName.c_str())))
{
details.map = mapName.String();
m_levelName = cachedMapName;
}
}
@ -599,37 +626,22 @@ void CBrowser::DrawHostPanel(void)
const bool serverActive = g_pServer->IsActive();
const bool clientActive = g_pClientState->IsActive();
const bool isOffline = g_ServerHostManager.GetVisibility() == ServerVisibility_e::OFFLINE;
const bool hasName = isOffline ? true : !m_serverName.empty();
if (!g_pHostState->m_bActiveGame)
{
if (ImGui::Button("Start server", ImVec2(contentRegionMax.x, 32)))
{
m_hostMessage.clear();
const bool enforceField = g_ServerHostManager.GetVisibility() == ServerVisibility_e::OFFLINE
? true
: !details.name.empty();
if (enforceField && !details.playlist.empty() && !details.map.empty())
if (hasName && !m_levelName.empty() && !m_gameMode.empty())
{
g_ServerHostManager.LaunchServer(serverActive); // Launch server.
g_ServerHostManager.LaunchServer(m_levelName.c_str(), m_gameMode.c_str()); // Launch server.
}
else
{
if (details.name.empty())
{
m_hostMessage = "Server name is required.";
m_hostMessageColor = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
}
else if (details.playlist.empty())
{
m_hostMessage = "Playlist is required.";
m_hostMessageColor = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
}
else if (details.map.empty())
{
m_hostMessage = "Level name is required.";
m_hostMessageColor = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
}
HandleInvalidFields(isOffline);
}
}
@ -654,12 +666,14 @@ void CBrowser::DrawHostPanel(void)
if (ImGui::Button("Change level", ImVec2(contentRegionMax.x, 32)))
{
if (!details.map.empty())
if (!m_levelName.empty() && !m_gameMode.empty())
{
g_ServerHostManager.LaunchServer(serverActive);
g_ServerHostManager.ChangeLevel(m_levelName.c_str(), m_gameMode.c_str());
}
else
{
HandleInvalidFields(isOffline);
m_hostMessage = "Failed to change level: 'levelname' was empty.";
m_hostMessageColor = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
}
@ -783,37 +797,23 @@ void CBrowser::UpdateHostingStatus(void)
}
case HostStatus_e::HOSTING:
{
if (*g_nServerRemoteChecksum == NULL) // Check if script checksum is valid yet.
{
break;
}
const ServerVisibility_e serverVisibility = g_ServerHostManager.GetVisibility();
NetGameServer_t& details = g_ServerHostManager.GetDetails();
if (serverVisibility == ServerVisibility_e::OFFLINE)
{
break;
}
if (*g_nServerRemoteChecksum == NULL) // Check if script checksum is valid yet.
{
break;
}
switch (serverVisibility)
{
case ServerVisibility_e::HIDDEN:
details.hidden = true;
break;
case ServerVisibility_e::PUBLIC:
details.hidden = false;
break;
default:
break;
}
const NetGameServer_t netGameServer
{
details.name,
details.description,
details.hidden,
hostname->GetString(),
hostdesc.GetString(),
serverVisibility == ServerVisibility_e::PUBLIC,
g_pHostState->m_levelName,
v_Playlists_GetCurrent(),
hostip->GetString(),

View File

@ -26,6 +26,8 @@ public:
void RefreshServerList(void);
void HiddenServersModal(void);
void HandleInvalidFields(const bool offline);
void DrawHostPanel(void);
void UpdateHostingStatus(void);
@ -72,6 +74,12 @@ private:
////////////////////
string m_hiddenServerRequestMessage;
ImVec4 m_hiddenServerMessageColor;
string m_serverName;
string m_serverDescription;
string m_levelName;
string m_gameMode;
};
extern CBrowser g_Browser;

View File

@ -6,6 +6,7 @@
//
//=============================================================================//
#include "tier0/frametask.h"
#include "common/callback.h"
#include "rtech/playlists/playlists.h"
#include "engine/cmd.h"
#include "hostmanager.h"
@ -20,32 +21,52 @@ CServerHostManager::CServerHostManager(void)
}
//-----------------------------------------------------------------------------
// Purpose: Launch server with given parameters
// Purpose: internal server launch handler
//-----------------------------------------------------------------------------
void CServerHostManager::LaunchServer(const bool changeLevel) const
static void HostManager_HandleCommandInternal(const char* const map, const char* const mode, const bool changeLevel)
{
if (!ThreadInMainThread())
Assert(!ThreadInServerFrameThread(), "Use server script GameRules_ChangeMap() instead!");
Msg(eDLL_T::ENGINE, "Starting server with name: \"%s\" map: \"%s\" mode: \"%s\"\n",
hostname->GetString(), map, mode);
// NOTE: when the provided playlist is the same as the one we're currently
// on, and there's already a pending map load request, the game will run
// "map <mapName>" in Playlists_Parse, where the map name is dictated by
// g_pPlaylistMapToLoad. If changelevel was specified, we have to null the
// requested map here as to prevent Playlists_Parse from running the map
// command on it, as we are going to run the changelevel command anyways.
// Not doing this will result in running both map and changelevel commands.
if (changeLevel)
*g_pPlaylistMapToLoad = '\0';
const bool samePlaylist = v_Playlists_Parse(mode);
char commandBuf[512];
if (!samePlaylist || !*g_pPlaylistMapToLoad)
{
g_TaskQueue.Dispatch([this, changeLevel]()
{
this->LaunchServer(changeLevel);
}, 0);
return;
snprintf(commandBuf, sizeof(commandBuf), "%s %s\n", changeLevel ? "changelevel" : "map", map);
Cbuf_AddText(Cbuf_GetCurrentPlayer(), commandBuf, cmd_source_t::kCommandSrcCode);
}
Msg(eDLL_T::ENGINE, "Starting server with name: \"%s\" map: \"%s\" playlist: \"%s\"\n",
m_Server.name.c_str(), m_Server.map.c_str(), m_Server.playlist.c_str());
snprintf(commandBuf, sizeof(commandBuf), "mp_gamemode %s\n", mode);
Cbuf_AddText(Cbuf_GetCurrentPlayer(), commandBuf, cmd_source_t::kCommandSrcCode);
}
/*
* Playlist gets parsed in two instances, first in Playlists_Parse() with all the necessary
* values. Then when you would normally call launchplaylist which calls StartPlaylist it would cmd
* call mp_gamemode which parses the gamemode specific part of the playlist..
*/
v_Playlists_Parse(m_Server.playlist.c_str());
mp_gamemode->SetValue(m_Server.playlist.c_str());
//-----------------------------------------------------------------------------
// Purpose: Launch server with given parameters
//-----------------------------------------------------------------------------
void CServerHostManager::LaunchServer(const char* const map, const char* const mode) const
{
HostManager_HandleCommandInternal(map, mode, false);
}
const string command = Format("%s \"%s\"", changeLevel ? "changelevel" : "map", m_Server.map.c_str());
Cbuf_AddText(Cbuf_GetCurrentPlayer(), command.c_str(), cmd_source_t::kCommandSrcCode);
//-----------------------------------------------------------------------------
// Purpose: Change level with given parameters
//-----------------------------------------------------------------------------
void CServerHostManager::ChangeLevel(const char* const map, const char* const mode) const
{
HostManager_HandleCommandInternal(map, mode, true);
}
CServerHostManager g_ServerHostManager;

View File

@ -20,7 +20,8 @@ class CServerHostManager
public:
CServerHostManager();
void LaunchServer(const bool changeLevel) const;
void LaunchServer(const char* const map, const char* const mode) const;
void ChangeLevel(const char* const map, const char* const mode) const;
inline HostStatus_e GetHostStatus(void) const { return m_HostingStatus; }
inline void SetHostStatus(const HostStatus_e hostStatus) { m_HostingStatus = hostStatus; }
@ -28,8 +29,6 @@ public:
inline ServerVisibility_e GetVisibility(void) const { return m_ServerVisibility; }
inline void SetVisibility(const ServerVisibility_e visibility) { m_ServerVisibility = visibility; }
inline NetGameServer_t& GetDetails() { return m_Server; }
inline void SetCurrentToken(const string& token) { m_Token = token; }
inline const string& GetCurrentToken() const { return m_Token; }
@ -43,8 +42,6 @@ private:
HostStatus_e m_HostingStatus;
ServerVisibility_e m_ServerVisibility;
NetGameServer_t m_Server;
string m_Token;
string m_ErrorMsg;
string m_HostIP;

View File

@ -23,9 +23,9 @@ void(*ServerScriptRegisterEnum_Callback)(CSquirrelVM* const s) = nullptr;
void(*ClientScriptRegisterEnum_Callback)(CSquirrelVM* const s) = nullptr;
void(*UIScriptRegisterEnum_Callback)(CSquirrelVM* const s) = nullptr;
// Admin panel functions, NULL on client only builds.
void(*CoreServerScriptRegister_Callback)(CSquirrelVM* const s) = nullptr;
void(*AdminPanelScriptRegister_Callback)(CSquirrelVM* const s) = nullptr;
// Admin panel functions, NULL on dedicated and client only builds.
void(*UiServerScriptRegister_Callback)(CSquirrelVM* const s) = nullptr;
void(*UiAdminPanelScriptRegister_Callback)(CSquirrelVM* const s) = nullptr;
// Registering constants in scripts.
void(*ScriptConstantRegister_Callback)(CSquirrelVM* const s) = nullptr;
@ -66,10 +66,11 @@ bool CSquirrelVM::Init(CSquirrelVM* s, SQCONTEXT context, SQFloat curTime)
if (UiScriptRegister_Callback)
UiScriptRegister_Callback(s);
if (CoreServerScriptRegister_Callback)
CoreServerScriptRegister_Callback(s);
if (AdminPanelScriptRegister_Callback)
AdminPanelScriptRegister_Callback(s);
if (UiServerScriptRegister_Callback)
UiServerScriptRegister_Callback(s);
if (UiAdminPanelScriptRegister_Callback)
UiAdminPanelScriptRegister_Callback(s);
break;
}

View File

@ -64,8 +64,8 @@ extern void(*ServerScriptRegisterEnum_Callback)(CSquirrelVM* const s);
extern void(*ClientScriptRegisterEnum_Callback)(CSquirrelVM* const s);
extern void(*UIScriptRegisterEnum_Callback)(CSquirrelVM* const s);
extern void(*CoreServerScriptRegister_Callback)(CSquirrelVM* const s);
extern void(*AdminPanelScriptRegister_Callback)(CSquirrelVM* const s);
extern void(*UiServerScriptRegister_Callback)(CSquirrelVM* const s);
extern void(*UiAdminPanelScriptRegister_Callback)(CSquirrelVM* const s);
extern void(*ScriptConstantRegister_Callback)(CSquirrelVM* const s);