diff --git a/sp/src/game/shared/mapbase/vscript_singletons.cpp b/sp/src/game/shared/mapbase/vscript_singletons.cpp index 6aba49af..b2f829a5 100644 --- a/sp/src/game/shared/mapbase/vscript_singletons.cpp +++ b/sp/src/game/shared/mapbase/vscript_singletons.cpp @@ -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" ); diff --git a/sp/src/public/vscript/ivscript.h b/sp/src/public/vscript/ivscript.h index a0c2bdcf..5fe1d570 100644 --- a/sp/src/public/vscript/ivscript.h +++ b/sp/src/public/vscript/ivscript.h @@ -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 diff --git a/sp/src/utils/vbsp/vscript_vbsp.cpp b/sp/src/utils/vbsp/vscript_vbsp.cpp index c8295e02..abaa2d66 100644 --- a/sp/src/utils/vbsp/vscript_vbsp.cpp +++ b/sp/src/utils/vbsp/vscript_vbsp.cpp @@ -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 ) diff --git a/sp/src/vscript/vscript_squirrel.cpp b/sp/src/vscript/vscript_squirrel.cpp index f14618e5..fa4c15d2 100644 --- a/sp/src/vscript/vscript_squirrel.cpp +++ b/sp/src/vscript/vscript_squirrel.cpp @@ -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) diff --git a/sp/src/vscript/vscript_squirrel.nut b/sp/src/vscript/vscript_squirrel.nut index 85c895ac..79cfb6c4 100644 --- a/sp/src/vscript/vscript_squirrel.nut +++ b/sp/src/vscript/vscript_squirrel.nut @@ -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."); } }