mirror of
https://github.com/mapbase-source/source-sdk-2013.git
synced 2024-12-26 23:05:30 +03:00
Added experimental static/global VScript hooks not tied to any particular class, starting with the integration of OnSave/OnRestore
This commit is contained in:
parent
ea7d1afa08
commit
29075a2c90
@ -835,6 +835,9 @@ static void FireGameEventLocal( const char* szEvent, HSCRIPT hTable )
|
||||
}
|
||||
#endif // !CLIENT_DLL
|
||||
|
||||
static ScriptHook_t g_Hook_OnSave;
|
||||
static ScriptHook_t g_Hook_OnRestore;
|
||||
|
||||
//=============================================================================
|
||||
// Save/Restore Utility
|
||||
// Based on L4D2 API
|
||||
@ -852,6 +855,9 @@ public: // IGameSystem
|
||||
{
|
||||
if ( g_pScriptVM )
|
||||
{
|
||||
g_Hook_OnSave.Call( NULL, NULL, NULL );
|
||||
|
||||
// Legacy hook
|
||||
HSCRIPT hFunc = g_pScriptVM->LookupFunction( "OnSave" );
|
||||
if ( hFunc )
|
||||
{
|
||||
@ -870,6 +876,9 @@ public: // IGameSystem
|
||||
{
|
||||
if ( g_pScriptVM )
|
||||
{
|
||||
g_Hook_OnRestore.Call( NULL, NULL, NULL );
|
||||
|
||||
// Legacy hook
|
||||
HSCRIPT hFunc = g_pScriptVM->LookupFunction( "OnRestore" );
|
||||
if ( hFunc )
|
||||
{
|
||||
@ -3033,6 +3042,8 @@ void RegisterScriptSingletons()
|
||||
ScriptRegisterFunctionNamed( g_pScriptVM, CScriptSaveRestoreUtil::SaveTable, "SaveTable", "Store a table with primitive values that will persist across level transitions and save loads." );
|
||||
ScriptRegisterFunctionNamed( g_pScriptVM, CScriptSaveRestoreUtil::RestoreTable, "RestoreTable", "Retrieves a table from storage. Write into input table." );
|
||||
ScriptRegisterFunctionNamed( g_pScriptVM, CScriptSaveRestoreUtil::ClearSavedTable, "ClearSavedTable", "Removes the table with the given context." );
|
||||
ScriptRegisterSimpleHook( g_pScriptVM, g_Hook_OnSave, "OnSave", FIELD_VOID, "Called when the game is saved." );
|
||||
ScriptRegisterSimpleHook( g_pScriptVM, g_Hook_OnRestore, "OnRestore", FIELD_VOID, "Called when the game is restored." );
|
||||
ScriptRegisterFunctionNamed( g_pScriptVM, CScriptReadWriteFile::FileWrite, "StringToFile", "Stores the string into the file" );
|
||||
ScriptRegisterFunctionNamed( g_pScriptVM, CScriptReadWriteFile::FileRead, "FileToString", "Returns the string from the file, null if no file or file is too big." );
|
||||
ScriptRegisterFunctionNamed( g_pScriptVM, CScriptReadWriteFile::KeyValuesWrite, "KeyValuesToFile", "Stores the CScriptKeyValues into the file" );
|
||||
|
@ -755,6 +755,23 @@ static inline int ToConstantVariant(int value)
|
||||
pDesc->m_Hooks.AddToTail(pHook); \
|
||||
}
|
||||
|
||||
// Static hooks (or "global" hooks) are not tied to specific classes
|
||||
#define END_SCRIPTHOOK_STATIC( pVM ) \
|
||||
pVM->RegisterHook( pHook ); \
|
||||
}
|
||||
|
||||
#define ScriptRegisterSimpleHook( pVM, hook, hookName, returnType, description ) \
|
||||
if (!hook.m_bDefined) \
|
||||
{ \
|
||||
ScriptHook_t *pHook = &hook; \
|
||||
pHook->m_desc.m_pszScriptName = hookName; pHook->m_desc.m_pszFunction = #hook; pHook->m_desc.m_ReturnType = returnType; pHook->m_desc.m_pszDescription = description; \
|
||||
pVM->RegisterHook( pHook ); \
|
||||
}
|
||||
|
||||
#define ScriptRegisterConstant( pVM, constant, description ) ScriptRegisterConstantNamed( pVM, constant, #constant, description )
|
||||
#define ScriptRegisterConstantNamed( pVM, constant, scriptName, description ) do { static ScriptConstantBinding_t binding; binding.m_pszScriptName = scriptName; binding.m_pszDescription = description; binding.m_data = ToConstantVariant(constant); pVM->RegisterConstant( &binding ); } while (0)
|
||||
|
||||
|
||||
#define DEFINE_MEMBERVAR( varName, returnType, description ) \
|
||||
do { ScriptMemberDesc_t *pBinding = &((pDesc)->m_Members[(pDesc)->m_Members.AddToTail()]); pBinding->m_pszScriptName = varName; pBinding->m_pszDescription = description; pBinding->m_ReturnType = returnType; } while (0);
|
||||
#endif
|
||||
|
@ -43,6 +43,9 @@ extern ScriptClassDesc_t * GetScriptDesc( CBaseEntity * );
|
||||
extern int vscript_token;
|
||||
int vscript_token_hack = vscript_token;
|
||||
|
||||
// HACKHACK: VScript library relies on developer convar existing
|
||||
ConVar developer( "developer", "1", 0, "Set developer message level." ); // developer mode
|
||||
|
||||
HSCRIPT VScriptCompileScript( const char *pszScriptName, bool bWarnMissing )
|
||||
{
|
||||
if ( !g_pScriptVM )
|
||||
|
@ -2350,7 +2350,11 @@ ScriptStatus_t SquirrelVM::ExecuteFunction(HSCRIPT hFunction, ScriptVariant_t* p
|
||||
|
||||
bool SquirrelVM::ScopeIsHooked( HSCRIPT hScope, const char *pszEventName )
|
||||
{
|
||||
Assert( hScope && hScope != INVALID_HSCRIPT );
|
||||
// For now, assume null scope (which is used for global hooks) is always hooked
|
||||
if (!hScope)
|
||||
return true;
|
||||
|
||||
Assert(hScope != INVALID_HSCRIPT);
|
||||
|
||||
sq_pushroottable(vm_);
|
||||
sq_pushstring(vm_, "Hooks", -1);
|
||||
@ -2375,7 +2379,7 @@ bool SquirrelVM::ScopeIsHooked( HSCRIPT hScope, const char *pszEventName )
|
||||
|
||||
HSCRIPT SquirrelVM::LookupHookFunction(const char *pszEventName, HSCRIPT hScope, bool &bLegacy)
|
||||
{
|
||||
HSCRIPT hFunc = LookupFunction( pszEventName, hScope );
|
||||
HSCRIPT hFunc = hScope ? LookupFunction( pszEventName, hScope ) : nullptr;
|
||||
if (hFunc)
|
||||
{
|
||||
bLegacy = true;
|
||||
@ -2421,7 +2425,11 @@ ScriptStatus_t SquirrelVM::ExecuteHookFunction(const char *pszEventName, HSCRIPT
|
||||
// TODO: Run in hook scope
|
||||
sq_pushroottable(vm_);
|
||||
|
||||
sq_pushobject(vm_, *((HSQOBJECT*)hScope));
|
||||
if (hScope)
|
||||
sq_pushobject(vm_, *((HSQOBJECT*)hScope));
|
||||
else
|
||||
sq_pushnull(vm_); // global hook
|
||||
|
||||
sq_pushstring(vm_, pszEventName, -1);
|
||||
|
||||
for (int i = 0; i < nArgs; ++i)
|
||||
|
@ -122,8 +122,6 @@ class CSimpleCallChainer
|
||||
chain = null;
|
||||
}
|
||||
|
||||
local developer = (delete developer)()
|
||||
|
||||
//---------------------------------------------------------
|
||||
// Hook handler
|
||||
//---------------------------------------------------------
|
||||
@ -199,7 +197,26 @@ Hooks <-
|
||||
{
|
||||
local firstReturn = null
|
||||
|
||||
if ( scope in s_List )
|
||||
if ( scope == null )
|
||||
{
|
||||
// null scope = global hook; call all scopes
|
||||
vargv.insert(0,this)
|
||||
foreach ( t in s_List )
|
||||
{
|
||||
if ( event in t )
|
||||
{
|
||||
foreach( context, callback in t[event] )
|
||||
{
|
||||
//printf( "(%.4f) Calling hook '%s' of context '%s' in static iteration\n", Time(), event, context )
|
||||
|
||||
local curReturn = callback.acall(vargv)
|
||||
if (firstReturn == null)
|
||||
firstReturn = curReturn
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( scope in s_List )
|
||||
{
|
||||
local t = s_List[scope]
|
||||
if ( event in t )
|
||||
@ -236,246 +253,231 @@ Hooks <-
|
||||
//---------------------------------------------------------
|
||||
__Documentation <- {}
|
||||
|
||||
local DocumentedFuncs
|
||||
local DocumentedClasses
|
||||
local DocumentedEnums
|
||||
local DocumentedConsts
|
||||
local DocumentedHooks
|
||||
local DocumentedMembers
|
||||
local developer = (delete developer)()
|
||||
|
||||
if (developer)
|
||||
{
|
||||
DocumentedFuncs = {}
|
||||
DocumentedClasses = {}
|
||||
DocumentedEnums = {}
|
||||
DocumentedConsts = {}
|
||||
DocumentedHooks = {}
|
||||
DocumentedMembers = {}
|
||||
}
|
||||
local DocumentedFuncs = {}
|
||||
local DocumentedClasses = {}
|
||||
local DocumentedEnums = {}
|
||||
local DocumentedConsts = {}
|
||||
local DocumentedHooks = {}
|
||||
local DocumentedMembers = {}
|
||||
|
||||
local function AddAliasedToTable(name, signature, description, table)
|
||||
{
|
||||
// This is an alias function, could use split() if we could guarantee
|
||||
// that ':' would not occur elsewhere in the description and Squirrel had
|
||||
// a convience join() function -- It has split()
|
||||
local colon = description.find(":");
|
||||
if (colon == null)
|
||||
colon = description.len();
|
||||
local alias = description.slice(1, colon);
|
||||
description = description.slice(colon + 1);
|
||||
name = alias;
|
||||
signature = null;
|
||||
|
||||
table[name] <- [signature, description];
|
||||
}
|
||||
|
||||
function __Documentation::RegisterHelp(name, signature, description)
|
||||
{
|
||||
if ( !developer )
|
||||
return
|
||||
|
||||
if (description.len() && description[0] == '#')
|
||||
local function AddAliasedToTable(name, signature, description, table)
|
||||
{
|
||||
AddAliasedToTable(name, signature, description, DocumentedFuncs)
|
||||
// This is an alias function, could use split() if we could guarantee
|
||||
// that ':' would not occur elsewhere in the description and Squirrel had
|
||||
// a convience join() function -- It has split()
|
||||
local colon = description.find(":");
|
||||
if (colon == null)
|
||||
colon = description.len();
|
||||
local alias = description.slice(1, colon);
|
||||
description = description.slice(colon + 1);
|
||||
name = alias;
|
||||
signature = null;
|
||||
|
||||
table[name] <- [signature, description];
|
||||
}
|
||||
else
|
||||
|
||||
function __Documentation::RegisterHelp(name, signature, description)
|
||||
{
|
||||
DocumentedFuncs[name] <- [signature, description];
|
||||
}
|
||||
}
|
||||
|
||||
function __Documentation::RegisterClassHelp(name, baseclass, description)
|
||||
{
|
||||
if ( !developer )
|
||||
return
|
||||
|
||||
DocumentedClasses[name] <- [baseclass, description];
|
||||
}
|
||||
|
||||
function __Documentation::RegisterEnumHelp(name, num_elements, description)
|
||||
{
|
||||
if ( !developer )
|
||||
return
|
||||
|
||||
DocumentedEnums[name] <- [num_elements, description];
|
||||
}
|
||||
|
||||
function __Documentation::RegisterConstHelp(name, signature, description)
|
||||
{
|
||||
if ( !developer )
|
||||
return
|
||||
|
||||
if (description.len() && description[0] == '#')
|
||||
{
|
||||
AddAliasedToTable(name, signature, description, DocumentedConsts)
|
||||
}
|
||||
else
|
||||
{
|
||||
DocumentedConsts[name] <- [signature, description];
|
||||
}
|
||||
}
|
||||
|
||||
function __Documentation::RegisterHookHelp(name, signature, description)
|
||||
{
|
||||
if ( !developer )
|
||||
return
|
||||
|
||||
DocumentedHooks[name] <- [signature, description];
|
||||
}
|
||||
|
||||
function __Documentation::RegisterMemberHelp(name, signature, description)
|
||||
{
|
||||
if ( !developer )
|
||||
return
|
||||
|
||||
DocumentedMembers[name] <- [signature, description];
|
||||
}
|
||||
|
||||
local function printdoc( text )
|
||||
{
|
||||
return ::printc(200,224,255,text);
|
||||
}
|
||||
|
||||
local function printdocl( text )
|
||||
{
|
||||
return printdoc(text + "\n");
|
||||
}
|
||||
|
||||
local function PrintClass(name, doc)
|
||||
{
|
||||
local text = "=====================================\n";
|
||||
text += ("Class: " + name + "\n");
|
||||
text += ("Base: " + doc[0] + "\n");
|
||||
if (doc[1].len())
|
||||
text += ("Description: " + doc[1] + "\n");
|
||||
text += "=====================================\n\n";
|
||||
|
||||
printdoc(text);
|
||||
}
|
||||
|
||||
local function PrintFunc(name, doc)
|
||||
{
|
||||
local text = "Function: " + name + "\n"
|
||||
|
||||
if (doc[0] == null)
|
||||
{
|
||||
// Is an aliased function
|
||||
text += ("Signature: function " + name + "(");
|
||||
foreach(k,v in this[name].getinfos().parameters)
|
||||
if (description.len() && description[0] == '#')
|
||||
{
|
||||
if (k == 0 && v == "this") continue;
|
||||
if (k > 1) text += (", ");
|
||||
text += (v);
|
||||
AddAliasedToTable(name, signature, description, DocumentedFuncs)
|
||||
}
|
||||
else
|
||||
{
|
||||
DocumentedFuncs[name] <- [signature, description];
|
||||
}
|
||||
text += (")\n");
|
||||
}
|
||||
else
|
||||
|
||||
function __Documentation::RegisterClassHelp(name, baseclass, description)
|
||||
{
|
||||
DocumentedClasses[name] <- [baseclass, description];
|
||||
}
|
||||
|
||||
function __Documentation::RegisterEnumHelp(name, num_elements, description)
|
||||
{
|
||||
DocumentedEnums[name] <- [num_elements, description];
|
||||
}
|
||||
|
||||
function __Documentation::RegisterConstHelp(name, signature, description)
|
||||
{
|
||||
if (description.len() && description[0] == '#')
|
||||
{
|
||||
AddAliasedToTable(name, signature, description, DocumentedConsts)
|
||||
}
|
||||
else
|
||||
{
|
||||
DocumentedConsts[name] <- [signature, description];
|
||||
}
|
||||
}
|
||||
|
||||
function __Documentation::RegisterHookHelp(name, signature, description)
|
||||
{
|
||||
DocumentedHooks[name] <- [signature, description];
|
||||
}
|
||||
|
||||
function __Documentation::RegisterMemberHelp(name, signature, description)
|
||||
{
|
||||
DocumentedMembers[name] <- [signature, description];
|
||||
}
|
||||
|
||||
local function printdoc( text )
|
||||
{
|
||||
return ::printc(200,224,255,text);
|
||||
}
|
||||
|
||||
local function printdocl( text )
|
||||
{
|
||||
return printdoc(text + "\n");
|
||||
}
|
||||
|
||||
local function PrintClass(name, doc)
|
||||
{
|
||||
local text = "=====================================\n";
|
||||
text += ("Class: " + name + "\n");
|
||||
text += ("Base: " + doc[0] + "\n");
|
||||
if (doc[1].len())
|
||||
text += ("Description: " + doc[1] + "\n");
|
||||
text += "=====================================\n\n";
|
||||
|
||||
printdoc(text);
|
||||
}
|
||||
|
||||
local function PrintFunc(name, doc)
|
||||
{
|
||||
local text = "Function: " + name + "\n"
|
||||
|
||||
if (doc[0] == null)
|
||||
{
|
||||
// Is an aliased function
|
||||
text += ("Signature: function " + name + "(");
|
||||
foreach(k,v in this[name].getinfos().parameters)
|
||||
{
|
||||
if (k == 0 && v == "this") continue;
|
||||
if (k > 1) text += (", ");
|
||||
text += (v);
|
||||
}
|
||||
text += (")\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
text += ("Signature: " + doc[0] + "\n");
|
||||
}
|
||||
if (doc[1].len())
|
||||
text += ("Description: " + doc[1] + "\n");
|
||||
printdocl(text);
|
||||
}
|
||||
|
||||
local function PrintMember(name, doc)
|
||||
{
|
||||
local text = ("Member: " + name + "\n");
|
||||
text += ("Signature: " + doc[0] + "\n");
|
||||
if (doc[1].len())
|
||||
text += ("Description: " + doc[1] + "\n");
|
||||
printdocl(text);
|
||||
}
|
||||
if (doc[1].len())
|
||||
text += ("Description: " + doc[1] + "\n");
|
||||
printdocl(text);
|
||||
}
|
||||
|
||||
local function PrintMember(name, doc)
|
||||
{
|
||||
local text = ("Member: " + name + "\n");
|
||||
text += ("Signature: " + doc[0] + "\n");
|
||||
if (doc[1].len())
|
||||
text += ("Description: " + doc[1] + "\n");
|
||||
printdocl(text);
|
||||
}
|
||||
|
||||
local function PrintEnum(name, doc)
|
||||
{
|
||||
local text = "=====================================\n";
|
||||
text += ("Enum: " + name + "\n");
|
||||
text += ("Elements: " + doc[0] + "\n");
|
||||
if (doc[1].len())
|
||||
text += ("Description: " + doc[1] + "\n");
|
||||
text += "=====================================\n\n";
|
||||
|
||||
printdoc(text);
|
||||
}
|
||||
|
||||
local function PrintConst(name, doc)
|
||||
{
|
||||
local text = ("Constant: " + name + "\n");
|
||||
if (doc[0] == null)
|
||||
local function PrintEnum(name, doc)
|
||||
{
|
||||
text += ("Value: null\n");
|
||||
local text = "=====================================\n";
|
||||
text += ("Enum: " + name + "\n");
|
||||
text += ("Elements: " + doc[0] + "\n");
|
||||
if (doc[1].len())
|
||||
text += ("Description: " + doc[1] + "\n");
|
||||
text += "=====================================\n\n";
|
||||
|
||||
printdoc(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
text += ("Value: " + doc[0] + "\n");
|
||||
}
|
||||
if (doc[1].len())
|
||||
text += ("Description: " + doc[1] + "\n");
|
||||
printdocl(text);
|
||||
}
|
||||
|
||||
local function PrintHook(name, doc)
|
||||
{
|
||||
local text = ("Hook: " + name + "\n");
|
||||
if (doc[0] == null)
|
||||
local function PrintConst(name, doc)
|
||||
{
|
||||
// Is an aliased function
|
||||
text += ("Signature: function " + name + "(");
|
||||
foreach(k,v in this[name].getinfos().parameters)
|
||||
local text = ("Constant: " + name + "\n");
|
||||
if (doc[0] == null)
|
||||
{
|
||||
if (k == 0 && v == "this") continue;
|
||||
if (k > 1) text += (", ");
|
||||
text += (v);
|
||||
text += ("Value: null\n");
|
||||
}
|
||||
text += (")\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
text += ("Signature: " + doc[0] + "\n");
|
||||
}
|
||||
if (doc[1].len())
|
||||
text += ("Description: " + doc[1] + "\n");
|
||||
printdocl(text);
|
||||
}
|
||||
|
||||
local function PrintMatchesInDocList(pattern, list, printfunc)
|
||||
{
|
||||
local foundMatches = 0;
|
||||
|
||||
foreach(name, doc in list)
|
||||
{
|
||||
if (pattern == "*" || name.tolower().find(pattern) != null || (doc[1].len() && doc[1].tolower().find(pattern) != null))
|
||||
else
|
||||
{
|
||||
foundMatches = 1;
|
||||
printfunc(name, doc)
|
||||
text += ("Value: " + doc[0] + "\n");
|
||||
}
|
||||
if (doc[1].len())
|
||||
text += ("Description: " + doc[1] + "\n");
|
||||
printdocl(text);
|
||||
}
|
||||
|
||||
local function PrintHook(name, doc)
|
||||
{
|
||||
local text = ("Hook: " + name + "\n");
|
||||
if (doc[0] == null)
|
||||
{
|
||||
// Is an aliased function
|
||||
text += ("Signature: function " + name + "(");
|
||||
foreach(k,v in this[name].getinfos().parameters)
|
||||
{
|
||||
if (k == 0 && v == "this") continue;
|
||||
if (k > 1) text += (", ");
|
||||
text += (v);
|
||||
}
|
||||
text += (")\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
text += ("Signature: " + doc[0] + "\n");
|
||||
}
|
||||
if (doc[1].len())
|
||||
text += ("Description: " + doc[1] + "\n");
|
||||
printdocl(text);
|
||||
}
|
||||
|
||||
local function PrintMatchesInDocList(pattern, list, printfunc)
|
||||
{
|
||||
local foundMatches = 0;
|
||||
|
||||
foreach(name, doc in list)
|
||||
{
|
||||
if (pattern == "*" || name.tolower().find(pattern) != null || (doc[1].len() && doc[1].tolower().find(pattern) != null))
|
||||
{
|
||||
foundMatches = 1;
|
||||
printfunc(name, doc)
|
||||
}
|
||||
}
|
||||
|
||||
return foundMatches;
|
||||
}
|
||||
|
||||
function __Documentation::PrintHelp(pattern = "*")
|
||||
{
|
||||
local patternLower = pattern.tolower();
|
||||
|
||||
// Have a specific order
|
||||
if (!(
|
||||
PrintMatchesInDocList( patternLower, DocumentedEnums, PrintEnum ) |
|
||||
PrintMatchesInDocList( patternLower, DocumentedConsts, PrintConst ) |
|
||||
PrintMatchesInDocList( patternLower, DocumentedClasses, PrintClass ) |
|
||||
PrintMatchesInDocList( patternLower, DocumentedFuncs, PrintFunc ) |
|
||||
PrintMatchesInDocList( patternLower, DocumentedMembers, PrintMember ) |
|
||||
PrintMatchesInDocList( patternLower, DocumentedHooks, PrintHook )
|
||||
))
|
||||
{
|
||||
printdocl("Pattern " + pattern + " not found");
|
||||
}
|
||||
}
|
||||
|
||||
return foundMatches;
|
||||
}
|
||||
|
||||
function __Documentation::PrintHelp(pattern = "*")
|
||||
else
|
||||
{
|
||||
if ( !developer )
|
||||
{
|
||||
printdocl("Documentation is not enabled. To enable documentation, restart the server with the 'developer' cvar set to 1 or higher.");
|
||||
return
|
||||
}
|
||||
__Documentation.RegisterHelp <-
|
||||
__Documentation.RegisterClassHelp <-
|
||||
__Documentation.RegisterEnumHelp <-
|
||||
__Documentation.RegisterConstHelp <-
|
||||
__Documentation.RegisterHookHelp <-
|
||||
__Documentation.RegisterMemberHelp <- dummy
|
||||
|
||||
local patternLower = pattern.tolower();
|
||||
|
||||
// Have a specific order
|
||||
if (!(
|
||||
PrintMatchesInDocList( patternLower, DocumentedEnums, PrintEnum ) |
|
||||
PrintMatchesInDocList( patternLower, DocumentedConsts, PrintConst ) |
|
||||
PrintMatchesInDocList( patternLower, DocumentedClasses, PrintClass ) |
|
||||
PrintMatchesInDocList( patternLower, DocumentedFuncs, PrintFunc ) |
|
||||
PrintMatchesInDocList( patternLower, DocumentedMembers, PrintMember ) |
|
||||
PrintMatchesInDocList( patternLower, DocumentedHooks, PrintHook )
|
||||
))
|
||||
function __Documentation::PrintHelp( pattern = null )
|
||||
{
|
||||
printdocl("Pattern " + pattern + " not found");
|
||||
printcl(200, 224, 255, "Documentation is not enabled. To enable documentation, restart the server with the 'developer' cvar set to 1 or higher.");
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user