Added experimental static/global VScript hooks not tied to any particular class, starting with the integration of OnSave/OnRestore

This commit is contained in:
Blixibon 2021-06-28 23:51:24 -05:00
parent ea7d1afa08
commit 29075a2c90
5 changed files with 260 additions and 219 deletions

View File

@ -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" );

View 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

View File

@ -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 )

View File

@ -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)

View File

@ -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.");
}
}