-
Notifications
You must be signed in to change notification settings - Fork 1
How to Plugin modules
Serious Sam Classics Patch implements a custom system of modular plugins that can be automatically loaded every time the patch is initialized. These plugins can be used to extend or modify many aspects of the game right on top of it, independent from traditional mods.
See
ExamplePlugin
repository for the example of a plugin library with descriptions of each event that plugins can handle.
Features and special techniques described here should be considered when developing custom user plugins.
When registering custom shell symbols to use within your plugins, be aware that you cannot define variables for the symbols within the plugin's memory space.
Plugins are released from memory automatically right before cleaning up Serious Engine, which is supposed to save symbols marked as persistent
into the Scripts/PersistentSymbols.ini
. Since plugins are getting erased from memory, values of the plugin symbols become inaccessible, leading to crashes related to memory access violation upon quitting the game or tool applications, potentially messing up your game files.
How to properly register plugin variables in console.
// DON'T do this (using local variable)
// This stores the symbol in the global scope of the plugin's memory
static INDEX _bRedScreen = TRUE;
// DO this (using plugin symbol class)
// This stores only a reference to the symbol in the global scope of the plugin's memory
static CPluginSymbol _psRedScreen(SSF_PERSISTENT | SSF_USER, INDEX(1));
// Registering inside module's entry point
MODULE_API void Module_Startup(void)
{
// DON'T do this (using Serious Engine method)
// This declares the symbol directly in the shell with the pointer to the variable
_pShell->DeclareSymbol("persistent user INDEX sam_bRedScreenOnDamage;", &_bRedScreen);
// DO this (using plugin API method)
// This registers the symbol through the API and stores it in memory space of a library/executable that initialized the Core library
_psRedScreen.Register("sam_bRedScreenOnDamage");
};
CPluginSymbol
constructor arguments are as follows:
Argument | Description | Example |
---|---|---|
1. Symbol flags | These are the flags used by the shell itself to see what this symbol is supposed to be. | The flags may only be SSF_PERSISTENT (saves values in "Scripts/PersistentSymbols.ini" upon quitting the game), SSF_USER (listed in the console) and SSF_CONSTANT (values cannot be changed via the console/shell). |
2. Default value | The default value that's going to be set to the symbol upon registering. If it's been loaded from PersistentSymbols.ini , the value won't be rewritten upon registering it again. |
It can either be an INDEX , a FLOAT or a const char * value. |
CPluginSymbol::Register()
arguments are as follows:
Argument | Description | Example |
---|---|---|
1. Symbol name | The name that will be used to access the symbol value, i.e. the console command name. | "iMySymbol" |
2. Pre-function | Shell function to call before modifying the symbol (the only argument the C++ function under that shell symbol has is a pointer to the registered symbol value). Can be left empty ("" ) or unspecified altogether (only if post-function isn't specified). |
"MyPreFunc" |
3. Post-function | Shell function to call after modifying the symbol (the only argument the C++ function under that shell symbol has is a pointer to the registered symbol value). Can be left empty ("" ) or unspecified altogether. |
"MyPostFunc" |
To retrieve a value from the symbol, use GetIndex()
, GetFloat()
or GetString()
of CPluginSymbol
, depending on which type the default value is. Calling the wrong method will cause undefined behavior!
How to properly define plugin functions for use by the shell.
// Shell function definition
static void SetHealth(SHELL_FUNC_ARGS)
{
BEGIN_SHELL_FUNC;
INDEX iHealth = NEXT_ARG(INDEX);
// Gather all local players and change their health
CDynamicContainer<CPlayerEntity> cenPlayers;
IWorld::GetLocalPlayers(cenPlayers);
FOREACHINDYNAMICCONTAINER(cenPlayers, CPlayerEntity, iten) {
iten->SetHealth(iHealth);
CPrintF(TRANS("Set %s^r health to %d\n"), iten->GetName(), iHealth);
}
};
Note the usage of SHELL_FUNC_ARGS
and BEGIN_SHELL_FUNC
macros in the function. These are platform-specific macros for properly accessing arguments that are passed into the C++ function by the game shell.
It's done this way due to how arrays of arguments are being passed depending on the compiler used. For example, in engine versions 1.05 and 1.07 (built with MSVC6.0) the arguments can be accessed directly, like void MyFunction(INDEX iInteger, FLOAT fNumber, const CTString &strString)
, while in 1.10 (built with MSVC10.0 or newer) the arguments need to be accessed from a pointer to the array of arguments, like void MyFunction(void *pArgs)
. These macros take care of compiler-dependent behavior and make it work like in 1.10 engine version.
In order to get values of a specific type one after another, use NEXT_ARG()
macro function as follows:
Desired type | Macro function usage | Description |
---|---|---|
INDEX |
INDEX i = NEXT_ARG(INDEX); |
Shell passes a raw integer value as an argument upon calling the function. |
FLOAT |
FLOAT f = NEXT_ARG(FLOAT); |
Shell passes a raw float value as an argument upon calling the function. |
CTString |
const CTString &str = *NEXT_ARG(CTString *); or CTString str = *NEXT_ARG(CTString *);
|
Shell passes a pointer to a CTString as an argument upon calling the function. |
How to properly register plugin functions in console.
// Registering inside module's entry point
MODULE_API void Module_Startup(void)
{
// DON'T do this (using Serious Engine method)
_pShell->DeclareSymbol("user void cht_SetHealth(INDEX);", &SetHealth);
// DO this (using plugin API method)
GetPluginAPI()->RegisterMethod(TRUE, "void", "cht_SetHealth", "INDEX", &SetHealth);
};
CPluginAPI::RegisterMethod()
arguments are as follows:
Argument | Description | Example |
---|---|---|
1. User visibility | Marks the symbol function as "user", making it visible in the symbol list in the console upon typing a part of its name and pressing Tab . |
TRUE or FALSE
|
2. Function return type | The value of which type will be returned from the function upon executing it. | May only be "void" , "INDEX" , "FLOAT" or "CTString" (matching the C++ types). |
3. Symbol name | The name that will be used to call the function, i.e. the console command name. | "MyCommand" |
4. Function arguments | Argument types that should be passed into the C++ function upon call. It can either be "void" for no arguments or a sequence of INDEX , FLOAT and CTString . |
"FLOAT" , "INDEX, INDEX" , "CTString, INDEX, FLOAT, FLOAT" etc. |
5. Pointer to the function | Here you specify a pointer to the function that will be called upon executing the shell command. It should have matching return type and argument types! | &MyCommand |
Designed and developed by Dreamy Cecil since 2022