Submitted 980329, revised 980508, summary by Bernd Kreimeier.
It is already possible to implement a runtime multimod interface with the current DLL. No changes by id Software are required. However, a builtin plugin structure delivered with the shipping product would have a lot of advantages.
The following example is a sketch of a monster plugin. It is possible to do the same for weapons and items, however, it would be more efficient to treat monsters, items and waepons as one kind of game object. This plugin approach extends naturally to OOP, in fact, it works best with OOP.
The basic idea is to extend the GetGameAPI approach used the the main engine when loading the Game DLL to a GetPluginAPI used by the GameDLL when spawning entities.
In a map file, you will find entities with spawn parameters.
{ "classname" "my_monster" "origin" "56 -736 168" "angle" "0" }There could be "static" entries like:
{ "classname" "chasecam" }that would not actually create an entity in the map, but force the Game DLL to load the plugin, so that the entity can be spawned dynamically at runtime. All temporary entities, projectiles, explosions would have to be handled like that. It would be possible to add a
"dll" "plugin"name, but this will be redundant, and would require more changes in editors and BSP.
The Game DLL, when calling g_spawn.c::SpawnEntities, would check an internal LUT whether the Module for that entity class is already there. If not, it will attempt to load my_monsterx86.dll (Win32) or my_monsteri386.so (Linux). If it fails, the entity will not be spawned, and simply ignored. A warning will be displayed, notifying the user that the game is incomplete, but still enabling him to play.
The main engine uses a LoadLibrary function to load the Refresh and Game DLL on the fly. This function would have to be imported by the GameDLL, or we would have to write our own.
Darn. Now, here's the rub. By simply copying the main engine/Game DLL interface, we get:
typedef struct plugin_import_t {...}; typedef struct plugin_export_t monsterinfo_t; typedef plugin_export_t* (*GetPluginAPI_f)( plugin_import_t* );The problem is we can use the GetPluginAPI function name only once. Thus, in each Plugin there will have to be a
GetPluginAPI_f Getmy_monsterAPI;In other words, the classname in the map file, the base name of the Plugin DLL, and the interface function name have to match.
But all these will have to be known at compile time in the DLL at least by name, because the symbol is the only way to retrieve the function. So either you carry loads of "registered" mod names in your current DLL, or you need some out-of-the-way stuff to get a function pointer from a DLL without knowing the actual symbol at compile time. I do not know whether this could be done at all with ELF/Win32 DLL.
Currently, monsterinfo_t packs a couple of methods with the monster data:
void (*stand) (edict_t *self); void (*idle) (edict_t *self); void (*search) (edict_t *self); void (*walk) (edict_t *self); void (*run) (edict_t *self); void (*dodge) (edict_t *self, edict_t *other, float eta); void (*attack) (edict_t *self); void (*melee) (edict_t *self); void (*sight) (edict_t *self, edict_t *other); qboolean (*checkattack)(edict_t *self);These have to be provided by the plugin interface. It might also be necessary to add some more functions. E.g. for collision response, a
void (*touch) (edict_t *self, edict_t *other);would come handy. In short, every restructuring or extension of the current monster behaviour (or an attempt to handle triggers, items, weapons with the same plugin) will lead to a Plugin Interface redesign, and previous plugins will be incompatible.
There is a similar problem with Q2 savegames. In both cases, a decent LUT that stores:
char* function_name; char* function_signature; void* function_pointer;along with a getFunctionForName function might add the necessary flexibility. Functions n/a in a specific plugin would not be found, the pointer would be set to zero.
The same monsterinfo_t contains instance variables, some of which are accessed by the main engine. There are two different approaches to handle this:
In the former case, each variable accessed by the Game DLL would have to get its own accessor method. E.g. pausetime is used in g_ai.c::ai_stand, which would have to call
float (*pausetime)( edict_t *self );instead. This would require several changes all over the DLL; and would slow things down.
Note that the monsterinfo_t is still allocated by the GameDLL, and referenced inside the edict. To initialize, a constructor like:
void (*init)( edict_t *self );has to be provided by the Plugin, and called by the Game DLL after allocation. This solution is bsically an OOP approach with all advantages (the internal implementation is hidden, and simple state variables can transparently be replaced by more elaborated sets) and disadvantages (no inlining here, as we are using method pointers, thus performance penalty). In the simpler case, GetPluginAPI is basically this init function - it returns a properly set monsterinfo_t as needed by the Game DLL.
With respect to Q2/Q3, an edict-like approach (the struct is partially known to the Game DLL, each Plugin might append) would work, if either the Game DLL makes sure to allocate enough memory (so the size has to be in the API), or the init method actually does the allocation. In the latter case, the changes to the existing Game DLL source would be minimal.