RoT2_PRC8/_module/nss/util_i_libraries.nss
Jaysyn904 379c5dce03 Added persistent player storage
Added persistent player storage.  Fixed store items.  Full compile.  Updated release archive.
2025-03-09 20:14:36 -04:00

482 lines
18 KiB
Plaintext

/// ----------------------------------------------------------------------------
/// @file util_i_libraries.nss
/// @author Michael A. Sinclair (Squatting Monk) <squattingmonk@gmail.com>
/// @author Ed Burke (tinygiant98) <af.hog.pilot@gmail.com>
/// @brief This file holds functions for packing scripts into libraries. This
/// allows the builder to dramatically reduce the module script count by
/// keeping related scripts in the same file.
/// @details
/// Libraries allow the builder to encapsulate many scripts into one,
/// dramatically reducing the script count in the module. In a library, each
/// script is a function bound to a unique name and/or number. When the library
/// is called, the name is routed to the proper function.
///
/// Since each script defined by a library has a unique name to identify it, the
/// builder can execute a library script without having to know the file it is
/// located in. This makes it easy to create script systems to override behavior
/// of another system; you don't have to edit the other system's code, you just
/// implement your own function to override it.
///
/// ## Anatomy of a Library
/// This is an example of a simple library:
///
/// ``` nwscript
/// #include "util_i_libraries"
///
/// void MyFunction()
/// {
/// // ...
/// }
///
/// void MyOtherFunction()
/// {
/// // ...
/// }
///
/// void OnLibraryLoad()
/// {
/// RegisterLibraryScript("MyFunction");
/// RegisterLibraryScript("MyOtherFunction");
/// }
/// ```
///
/// This script contains custom functions (`MyFunction()` and `MyOtherFunction()`)
/// as well as an `OnLibraryLoad()` function. `OnLibraryLoad()` is executed
/// whenever the library is loaded by `LoadLibrary()`; it calls
/// `RegisterLibraryScript()` to expose the names of the custom functions as
/// library scripts. When a library script is called with `RunLibraryScript()`,
/// the custom functions are called.
///
/// If you want to do something more complicated that can't be handled by a
/// single function call, you can pass a unique number to
/// `RegisterLibraryScript()` as its second parameter, which will cause
/// `RunLibraryScript()` to call a special customizable dispatching function
/// called `OnLibraryScript()`. This function takes the name and number of the
/// desired function and executes the desired code. For example:
///
/// ``` nwscript
/// #include "util_i_libraries"
///
/// void OnLibraryLoad()
/// {
/// RegisterLibraryScript("Give50GP", 1);
/// RegisterLibraryScript("Give100GP", 2);
/// }
///
/// void OnLibraryScript(string sScript, int nEntry)
/// {
/// switch (nEntry)
/// {
/// case 1: GiveGoldToCreature(OBJECT_SELF, 50); break;
/// case 2: GiveGoldToCreature(OBJECT_SELF, 100); break;
/// }
/// }
/// ```
///
/// **Note:** A library does not need to have a `main()` function, because this
/// will be automatically generated by the `LoadLibrary()` and
/// `RunLibraryScript()` functions.
///
/// ## Using a Library
/// `util_i_libraries.nss` is needed to load or run library scripts.
///
/// To use a library, you must first load it. This will activate the library's
/// `OnLibraryLoad()` function and register each desired function.
///
/// ``` nwscript
/// // Loads a single library
/// LoadLibrary("my_l_library");
///
/// // Loads a CSV list of library scripts
/// LoadLibraries("pw_l_plugin, dlg_l_example, prr_l_main");
///
/// // Loads all libraries matching a glob pattern
/// LoadLibrariesByPattern("*_l_*");
///
/// // Loads all libraries matching a prefix
/// LoadLibrariesByPrefix("pw_l_");
/// ```
///
/// If a library implements a script that has already been implemented in
/// another library, a warning will be issued and the newer script will take
/// precedence.
///
/// Calling a library script is done using `RunLibraryScript()`. The name
/// supplied should be the name bound to the function in the library's
/// `OnLibraryLoad()`. If the name supplied is implemented by a library, the
/// library will be JIT compiled and the desired function will be called with
/// `ExecuteScriptChunk()`. Otherwise, the name will be assumed to match a
/// normal script, which will be executed with `ExecuteScript()`.
///
/// ``` nwscript
/// // Executes a single library script on OBJECT_SELF
/// RunLibraryScript("MyFunction");
///
/// // Executes a CSV list of library scripts, for which oPC will be OBJECT_SELF
/// object oPC = GetFirstPC();
/// RunLibraryScripts("MyFunction, MyOtherFunction", oPC);
/// ```
///
/// ## Pre-Compiled Libraries
/// By default, libraries are run using `ExecuteScriptChunk()`, which JIT
/// compiles the script and runs it each time the library script is called. If
/// you wish to have your script pre-compiled, you can include the script
/// `util_i_library.nss` in your file in place of `util_i_libraries.nss`. This
/// script contains a `main()` function that will call either your
/// `OnLibraryLoad()` or `OnLibraryScript()` function as appropriate; thus, if
/// you use this method, you *must* provide an `OnLibraryScript()` dispatch
/// function.
///
/// **Note**: `util_i_library.nss` uses the nwnsc `default_function` pragma to
/// prevent compilation errors and will not compile with the toolset compiler.
/// If this is not desired, you can either comment those lines out or implement
/// the `main()` function yourself.
/// ----------------------------------------------------------------------------
#include "util_i_debug"
#include "util_i_csvlists"
#include "util_i_sqlite"
#include "util_i_nss"
#include "util_i_matching"
// -----------------------------------------------------------------------------
// Constants
// -----------------------------------------------------------------------------
const string LIB_RETURN = "LIB_RETURN"; ///< The return value of the library
const string LIB_LIBRARY = "LIB_LIBRARY"; ///< The library being processed
const string LIB_SCRIPT = "LIB_SCRIPT"; ///< The library script name
const string LIB_ENTRY = "LIB_ENTRY"; ///< The library script entry number
// -----------------------------------------------------------------------------
// Function Prototypes
// -----------------------------------------------------------------------------
/// @brief Create a library table in the module's volatile sqlite database.
/// @param bReset if TRUE, the table will be dropped if already present.
/// @note This is called automatically by the library functions.
void CreateLibraryTable(int bReset = FALSE);
/// @brief Add a database record associating a script with a library.
/// @param sLibrary The script to source from.
/// @param sScript The name to associate with the library script.
/// @param nEntry A number unique to sLibrary to identify this script. If this
/// is 0 and the library has not been pre-compiled, RunLibraryScript() will
/// call sScript directly. Otherwise, RunLibraryScript() will run a dispatch
/// function that can use this number to execute the correct code. Thus,
/// nEntry must be set if sScript does not exactly match the desired
/// function name or the function requires parameters.
void AddLibraryScript(string sLibrary, string sScript, int nEntry = 0);
/// @brief Return the name of the library containing a script from the database.
/// @param sScript The name of the library script.
string GetScriptLibrary(string sScript);
/// @brief Return the entry number associated with a library script.
/// @param sScript The name of the library script.
int GetScriptEntry(string sScript);
/// @brief Return a prepared query with the with the library and entry data
/// associated with a library script.
/// @param sScript The name of the library script.
/// @note This allows users to retrive the same data returned by
/// GetScriptLibrary() and GetScriptEntry() with one function.
sqlquery GetScriptData(string sScript);
/// @brief Return whether a script library has been loaded.
/// @param sLibrary The name of the script library file.
int GetIsLibraryLoaded(string sLibrary);
/// @brief Load a script library by executing its OnLibraryLoad() function.
/// @param sLibrary The name of the script library file.
/// @param bForce If TRUE, will re-load the library if it was already loaded.
void LoadLibrary(string sLibrary, int bForce = FALSE);
/// @brief Load a list of script libraries in sequence.
/// @param sLibraries A CSV list of libraries to load.
/// @param bForce If TRUE, will re-load the library if it was already loaded.
void LoadLibraries(string sLibraries, int bForce = FALSE);
/// @brief Return a json array of script names with a prefix.
/// @param sPrefix The prefix matching the scripts to find.
/// @returns A sorted json array of script names, minus the extensions.
/// @note The search includes both nss and ncs files, with duplicates removed.
json GetScriptsByPrefix(string sPrefix);
/// @brief Load all scripts matching the given glob pattern(s).
/// @param sPattern A CSV list of glob patterns to match with. Supported syntax:
/// - `*`: match zero or more characters
/// - `?`: match a single character
/// - `[abc]`: match any of a, b, or c
/// - `[a-z]`: match any character from a-z
/// - other text is matched literally
/// @param bForce If TRUE, will-reload the library if it was already loaded.
void LoadLibrariesByPattern(string sPattern, int bForce = FALSE);
/// @brief Load all scripts with a given prefix as script libraries.
/// @param sPrefix A prefix for the desired script libraries.
/// @param bForce If TRUE, will re-load the library if it was already loaded.
/// @see GetMatchesPattern() for the rules on glob syntax.
void LoadLibrariesByPrefix(string sPrefix, int bForce = FALSE);
/// @brief Execute a registered library script.
/// @param sScript The unique name of the library script.
/// @param oSelf The object that should execute the script as OBJECT_SELF.
/// @returns The integer value set with LibraryReturn() by sScript.
/// @note If sScript is not registered as a library script, it will be executed
/// as a regular script instead.
int RunLibraryScript(string sScript, object oSelf = OBJECT_SELF);
/// @brief Execute a list of registered library scripts in sequence.
/// @param sScripts A CSV list of library script names.
/// @param oSelf The object that should execute the scripts as OBJECT_SELF.
/// @note If any script in sScripts is not registered as a library script, it
/// will be executed as a regular script instead.
void RunLibraryScripts(string sScripts, object oSelf = OBJECT_SELF);
/// @brief Register a script to a library. The script can later be called using
/// RunLibraryScript().
/// @param sScript A name for the script. Must be unique in the module. If a
/// second script with the same name is registered, it will overwrite the
/// first one. This value does not have to match the function or script name.
/// @param nEntry A number unique to this library to identify this script. If
/// this is 0 and the library has not been pre-compiled, RunLibraryScript()
/// will call sScript directly. Otherwise, RunLibraryScript() will run a
/// dispatch function that can use this number to execute the correct code.
/// Thus, nEntry must be set if sScript does not exactly match the desired
/// function name or the function requires parameters.
/// @note Must be called within a script library's OnLibraryLoad() function. For
/// uses in other places, use AddLibraryScript().
void RegisterLibraryScript(string sScript, int nEntry = 0);
/// @brief Set the return value of the currently executing library script.
/// @param nValue The value to return to the calling script.
void LibraryReturn(int nValue);
// -----------------------------------------------------------------------------
// Function Definitions
// -----------------------------------------------------------------------------
void CreateLibraryTable(int bReset = FALSE)
{
SqlCreateTableModule("library_scripts",
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"sLibrary TEXT NOT NULL, " +
"sScript TEXT NOT NULL UNIQUE ON CONFLICT REPLACE, " +
"nEntry INTEGER NOT NULL);",
bReset);
}
void AddLibraryScript(string sLibrary, string sScript, int nEntry = 0)
{
CreateLibraryTable();
string sQuery = "INSERT INTO library_scripts (sLibrary, sScript, nEntry) " +
"VALUES (@sLibrary, @sScript, @nEntry);";
sqlquery sql = SqlPrepareQueryModule(sQuery);
SqlBindString(sql, "@sLibrary", sLibrary);
SqlBindString(sql, "@sScript", sScript);
SqlBindInt(sql, "@nEntry", nEntry);
SqlStep(sql);
}
string GetScriptFieldData(string sField, string sScript)
{
CreateLibraryTable();
string sQuery = "SELECT " + sField + " FROM library_scripts " +
"WHERE sScript = @sScript;";
sqlquery sql = SqlPrepareQueryModule(sQuery);
SqlBindString(sql, "@sScript", sScript);
return SqlStep(sql) ? SqlGetString(sql, 0) : "";
}
string GetScriptLibrary(string sScript)
{
return GetScriptFieldData("sLibrary", sScript);
}
int GetScriptEntry(string sScript)
{
return StringToInt(GetScriptFieldData("nEntry", sScript));
}
sqlquery GetScriptData(string sScript)
{
CreateLibraryTable();
string sQuery = "SELECT sLibrary, nEntry FROM library_scripts " +
"WHERE sScript = @sScript;";
sqlquery sql = SqlPrepareQueryModule(sQuery);
SqlBindString(sql, "@sScript", sScript);
return sql;
}
int GetIsLibraryLoaded(string sLibrary)
{
CreateLibraryTable();
string sQuery = "SELECT COUNT(sLibrary) FROM library_scripts " +
"WHERE sLibrary = @sLibrary LIMIT 1;";
sqlquery sql = SqlPrepareQueryModule(sQuery);
SqlBindString(sql, "@sLibrary", sLibrary);
return SqlStep(sql) ? SqlGetInt(sql, 0) : FALSE;
}
void LoadLibrary(string sLibrary, int bForce = FALSE)
{
Debug("Attempting to " + (bForce ? "force " : "") + "load library " + sLibrary);
if (bForce || !GetIsLibraryLoaded(sLibrary))
{
SetScriptParam(LIB_LIBRARY, sLibrary);
if (ResManGetAliasFor(sLibrary, RESTYPE_NCS) == "")
{
Debug(sLibrary + ".ncs not present; loading library as chunk");
string sChunk = NssInclude(sLibrary) + NssVoidMain(NssFunction("OnLibraryLoad"));
string sError = ExecuteScriptChunk(sChunk, GetModule(), FALSE);
if (sError != "")
CriticalError("Could not load " + sLibrary + ": " + sError);
}
else
ExecuteScript(sLibrary, GetModule());
}
else
Error("Library " + sLibrary + " already loaded!");
}
void LoadLibraries(string sLibraries, int bForce = FALSE)
{
Debug("Attempting to " + (bForce ? "force " : "") + "load libraries " + sLibraries);
int i, nCount = CountList(sLibraries);
for (i = 0; i < nCount; i++)
LoadLibrary(GetListItem(sLibraries, i), bForce);
}
// Private function for GetScriptsByPrefix*(). Adds all scripts of nResType
// matching a prefix to a json array and returns it.
json _GetScriptsByPrefix(json jArray, string sPrefix, int nResType)
{
int i;
string sScript;
while ((sScript = ResManFindPrefix(sPrefix, nResType, ++i)) != "")
jArray = JsonArrayInsert(jArray, JsonString(sScript));
return jArray;
}
json GetScriptsByPrefix(string sPrefix)
{
json jScripts = _GetScriptsByPrefix(JsonArray(), sPrefix, RESTYPE_NCS);
jScripts = _GetScriptsByPrefix(jScripts, sPrefix, RESTYPE_NSS);
jScripts = JsonArrayTransform(jScripts, JSON_ARRAY_UNIQUE);
jScripts = JsonArrayTransform(jScripts, JSON_ARRAY_SORT_ASCENDING);
return jScripts;
}
void LoadLibrariesByPattern(string sPatterns, int bForce = FALSE)
{
if (sPatterns == "")
return;
Debug("Finding libraries matching \"" + sPatterns + "\"");
json jPatterns = ListToJson(sPatterns);
json jLibraries = FilterByPatterns(GetScriptsByPrefix(""), jPatterns, TRUE);
LoadLibraries(JsonToList(jLibraries), bForce);
}
void LoadLibrariesByPrefix(string sPrefix, int bForce = FALSE)
{
Debug("Finding libraries with prefix \"" + sPrefix + "\"");
json jLibraries = GetScriptsByPrefix(sPrefix);
LoadLibraries(JsonToList(jLibraries), bForce);
}
void LoadPrefixLibraries(string sPrefix, int bForce = FALSE)
{
Debug("LoadPrefixLibraries() is deprecated; use LoadLibrariesByPrefix()");
LoadLibrariesByPrefix(sPrefix, bForce);
}
int RunLibraryScript(string sScript, object oSelf = OBJECT_SELF)
{
if (sScript == "") return -1;
string sLibrary;
int nEntry;
sqlquery sqlScriptData = GetScriptData(sScript);
if (SqlStep(sqlScriptData))
{
sLibrary = SqlGetString(sqlScriptData, 0);
nEntry = SqlGetInt(sqlScriptData, 1);
}
DeleteLocalInt(oSelf, LIB_RETURN);
if (sLibrary != "")
{
Debug("Library script " + sScript + " found in " + sLibrary +
(nEntry != 0 ? " at entry " + IntToString(nEntry) : ""));
SetScriptParam(LIB_LIBRARY, sLibrary);
SetScriptParam(LIB_SCRIPT, sScript);
SetScriptParam(LIB_ENTRY, IntToString(nEntry));
if (ResManGetAliasFor(sLibrary, RESTYPE_NCS) == "")
{
Debug(sLibrary + ".ncs not present; running library script as chunk");
string sChunk = NssInclude(sLibrary) + NssVoidMain(nEntry ?
NssFunction("OnLibraryScript", NssQuote(sScript) + ", " + IntToString(nEntry)) :
NssFunction(sScript));
string sError = ExecuteScriptChunk(sChunk, oSelf, FALSE);
if (sError != "")
CriticalError("RunLibraryScript(" + sScript +") failed: " + sError);
}
else
ExecuteScript(sLibrary, oSelf);
}
else
{
Debug(sScript + " is not a library script; executing directly");
ExecuteScript(sScript, oSelf);
}
return GetLocalInt(oSelf, LIB_RETURN);
}
void RunLibraryScripts(string sScripts, object oSelf = OBJECT_SELF)
{
int i, nCount = CountList(sScripts);
for (i = 0; i < nCount; i++)
RunLibraryScript(GetListItem(sScripts, i), oSelf);
}
void RegisterLibraryScript(string sScript, int nEntry = 0)
{
string sLibrary = GetScriptParam(LIB_LIBRARY);
string sExist = GetScriptLibrary(sScript);
if (sLibrary != sExist && sExist != "")
Warning(sLibrary + " is overriding " + sExist + "'s implementation of " + sScript);
int nOldEntry = GetScriptEntry(sScript);
if (nOldEntry)
Warning(sLibrary + " already declared " + sScript +
" Old Entry: " + IntToString(nOldEntry) +
" New Entry: " + IntToString(nEntry));
AddLibraryScript(sLibrary, sScript, nEntry);
}
void LibraryReturn(int nValue)
{
SetLocalInt(OBJECT_SELF, LIB_RETURN, nValue);
}