Raised This Month: $ Target: $400
 0% 

The Future of Zombie Plugins


Post New Thread Reply   
 
Thread Tools Display Modes
Author Message
rhelgeby
Veteran Member
Join Date: Oct 2008
Location: 0x4E6F72776179
Old 01-11-2014 , 14:53   The Future of Zombie Plugins
Reply With Quote #1

The Future of Zombie Plugins

Introduction
The Zombie:Reloaded project has come to a stage where we need to change our development strategy. In this thread I will give a summary of the current state and what I think we should do next.

This is mainly a discussion for SourceMod developers, but anyone may join the discussion.

TLDR
We need a good standard zombie API and many small plugins that do their thing, and do it well.

Warning
I will do heavy moderation in this thread to make sure it stays on topic. This means that posts asking for support on ZR will be deleted without warning. This is not the place to ask for support or feature requests on released versions. You have been warned. However, don't let that scare you from posting questions or comments regarding the subjects I discuss below.

The Problem
The problem can easily be summarized with two words: time and energy. Though, it is a bit more complex than that.

Back in about 2008, ZR was in active development. Today, development has almost stalled completely due to lack of time or energy.

I am the only one who maintains ZR, except for Jargon who does great work on maintaining the unofficial version for CS: GO. Even though I have lots of ideas, development is going really slow. Everyone depends on me to fix a bug or implement a new feature. Strictly speaking they don't, since it's open source, but nobody wants to do it themself.

The result is one inactive developer and a plugin with very low maintenance. We need to fix this.

The Solution
The probably most obvious solution is to get a few additional developers to work on the ZR project. This doesn't solve the actual problem, since then everyone will depend on that team, which is just increased by a few persons.

I suggest a solution where we delegate the responsibility to multiple developers that create and maintain their own zombie game features.

In short, the solution based on this philosophy:
Do one thing and do it well.
Modularity
To achieve this goal, we need a modular plugin. In fact, we need multiple plugins.

Every feature should be its own plugin, with its own responsibilities. They should also react to changes in the environment so that server admins can hot plug features while the server is running (and possibly in the middle of a game/round)!

This will force developers to create robust plugins that are ready to handle any event at any time.

Feature plugins may need to communicate with eachother, or share data. That can be solved with a common extendable API (plugins that create natives in SourceMod). More about this later.

Security
First we need to solve another issue: security.

At the moment ZR is one big plugin binary. We have pretty good control of what we want to expose to other third party plugins (even though ZR's current API is quite small).

With a modular architecture, many features may depend on data from other features. We want to keep this communication restricted to only a sub set of the loaded SourceMod plugins in order to prevent potential malicious plugins messing up internal states in the features.

This is solved by an authentication manager that keeps track of which features each plugin has access to.

Feature plugins will then ask this manager whether a plugin has access to a certain native/feature. It's entirely optional for feature modules to use this manager. If all natvies should be public, that's just fine.

The implementaion of the authentication manager will obviously need a cache to avoid performance issues. Feature plugins could cache the result too.

What Happens to Zombie:Reloaded?

The ZR 3.x branch will continue to exist, as as long as the new solution is in development (4.0). New features will most likely not be added, and only critical game breaking bugs will be fixed (due to my time/energy issue). I keep reminding that everyone are welcome to work on ZR 3.x themself, though I'll have to review the code. Even then there's no one stopping anyone from publishing their own version as long as the source is available and credits preserved.

ZR already use an internal modular architecture. My idea is to move all features into individual plugins (or let other developers do that work).

The plan is to focus on a specification for an API that describes core parts and features in a zombie game. This is a pure specification and does not include any coding. I have some basic ideas on how this should look like, but everyone are welcome to contribute. This thread is for discussing that API.

The Ultimate Zombie Plugin API
We (you and me) are going to define a standard API for zombie games in SourceMod, simply called The Zombie API!

The Zombie API will define how zombie games can be created on SourceMod. How the zombie games themself behave is undefined. It depends entirely on which feature plugins that exist and which ones server admins choose to install.

We already have a lot of code, and we're going to design the API in a way we can reuse our earlier work. Some modifications must be done, but not anything that would need a major rewrite of all features. Luckily ZR is somewhat loosely coupled and modular (mostly 4.0, but 3.x is usable too), so this isn't impossible.

The API will get a lot of inspiration from the SourceMod Project Base by Greyscale. Many of our issues are solved in that framework.

We also need to be careful when designing the API, since this should be a persistent API that many plugins may use. Newer versions has to be backwards compatible so that we don't break plugins during new releases.

The goal is that everyone can create a simple (or complex) feature for a zombie game, and by using this API their plugin can cooperate with other plugins. There's no need to modify and recompile a big plugin each time you add a new feature.

Extendable API
The most important feature of this API is that it should be extendable. We allow third party developers to declare new natives, events and forwards so that they can be shared between plugins. If someone make a new feature, they may also make an API for it.

This doesn't require any special code, it's just a rule or a convention. Basically we just define a folder structure to organize include files. We cover the core parts of the API that solves the technical stuff. Other features share their APIs in other files.

Example folder structure:
Spoiler


Module Manager
All these individual feature plugins need some coordination and a manager. This is essentially the same as the module manager in the project base framework.

Feature plugins register themself with the module manager plugin. They may also declare dependencies on other feature plugins.

In addition to the modules it also manages a list of named individual features. We need this so that modules can ask the module manager whether a certain feature exist before attempting to use it. This is almost identical to SourceMod's GetFeatureStatus except that our version will check for zombie related features.

In contrast to the project base framework, this module manager doesn't do anything itself. It's just a storage for declaring which features that exists. However, it should watch for when plugins are unloaded and unregister those features.

Event Manager
The event manager allows modules to declare and handle abstract events such as OnPlayerInfected or OnZombiesWin. These are pure forwards. Feature modules may create their own forwards by exposing their own API.

Note that these events should not be confused with game events such as player_spawn. Though, the event manager (or possibly a game specific adapter plugin) may hook events like player_team and player_spawn and convert them into abstract events. This will solve some issues with multimod support.

Forwards are global and an efficient way for sending notifications to plugins. Although there might be some cases where we want to filter events so that only certain plugins receive them. In that case it might be solved by using event hooks and callbacks. That also solves the security issue where unauthorized plugins won't be able to hook the event.

It's important to get type safety on events by using functags or wrappers. Bad code should crash as early as possible. The optimal case is to catch this at compile time.

Hot-plugging Modules
Modules should be prepared for any event at any time (where suitable). A module may be enabled or disabled in the middle of the game and should be ready to handle this, as explained earlier.

For instance, players may suddenly no longer have the teleport feature when the server admin disabled it, but now have new freeze grenades added by the server admin.

Multimod Support
Zombie games can be played on other games than just CS. Instead of creating a feature plugin for each game, we create one plugin that will run on all supported games (such as CS: S, CS: GO, TF2, HL2: DM, etc).

Most of the games have the same event names and similar features, but the parameters are different. Using abstract events we can hide these differences and provide a consistent set of events and natives. In some cases we could implement workarounds for stuff like a flashlight, or implementing rounds in a game that doesn't have it and create fake round start/end events.

Virtual Teams
Due to multimod support, and that a zombie game itself introduce new teams, we need virtual teams.

In the core API this includes zombies and humans, but it could be extended to multiple teams.

Virtual teams are mapped to actual game teams (CT/T on CS:S, Red and Blu on TF2, etc). This is handled by a team manager.

All feature modules use only virtual teams so that the developers doesn't have to update their plugin if a new game is supported.

Implementing the API
Once we have an API specification, it must be implemented. This is where the community can join and implement their own features.

Libraries
When writing the implementaions you may often discover that you're making something similar to other works. In that case it's a good idea to move that code to a library so it can be reused by multiple features.

Write generic code when possible. Examples are libraries for logging, building menus, parsing config files. Also use SMLIB for simpler stuff. If you're writing something specific to a game, make a library for that game!

Conclusion
Strictly speaking there is none, yet. That's the point of this thread.

We need to figure out how to make it easier to extend zombie games with new features, because I don't have time or energy to do it all by myself.

We need a good standard zombie API and many small plugins that do their thing, and do it well.

StartDiscussion();
__________________
Richard Helgeby

Zombie:Reloaded | PawnUnit | Object Library
(Please don't send private messages for support, they will be ignored. Use the forum.)

Last edited by rhelgeby; 01-11-2014 at 15:06.
rhelgeby is offline
Send a message via MSN to rhelgeby
KyleS
SourceMod Plugin Approver
Join Date: Jul 2009
Location: Segmentation Fault.
Old 01-11-2014 , 15:30   Re: The Future of Zombie Plugins
Reply With Quote #2

I wrote HookMan a few years ago (which is the implementation of this; for my forever WIP zombie plugin). It's about 250 lines and provides nearly all of the above (see attached). However, there's no security model that comes along with it. It wouldn't be hard to extend (albeit, the line count would double).

Protecting natives I don't think will pan out too well (unfortunately). Abstracting these calls can be dangerous in terms of performance. We're forced to incur either children storing auth states, or calling an auth plugin (doubling the initial overhead of the pawn native call).

The one basic rule I've tried to follow with my own set is a plugin should either be:
  1. An API Provider.
  2. Utilizer of an API.
  3. Middleware, utilize an API, and provide an API to others.
Here's an example of Middle-Ware.


PHP Code:
public RegisterHooks()
{
    
HM_RegisterHook(g_hOnClientKnockbackAddition"ZME_OnClientKnockbackAddition");
    
HM_RegisterHook(g_hOnClientKnockbackMultiplication"ZME_OnClientKnockbackMultiplication");
}

public 
ProcessThroughHooks(bool:bCheck, const String:sName[])
{
    
AddHookIfExists(bCheckg_iGlobalBitseTraceAttackPost"ZME_OnClientTraceAttack_Post"ZME_OnClientTraceAttack_PostsName);
}

public 
ZME_OnClientTraceAttack_Post(clientCliTeamattackerAttaTeaminflictorFloat:damagedamagetypeammotypehitboxhitgroup)
{
    if (!
attacker || CliTeam == eHuman || AttaTeam != eHuman || damage 0.1)
    {
        return;
    }
    
    new 
Float:fKnockBack 0.0;

    if (!
CallKnockBackForward(g_hOnClientKnockbackAdditionclientattackerinflictorhitgroupdamagefKnockBack) || \
    !
CallKnockBackForward(g_hOnClientKnockbackMultiplicationclientattackerinflictorhitgroupdamagefKnockBack) || fKnockBack == 0.0)
    {
        return;
    }
    
    
decl Float:fClientLoc[3], Float:fAttackerLoc[3];

    
GetEntPropVector(clientProp_Send"m_vecOrigin"fClientLoc);
    
GetEntPropVector(attackerProp_Send"m_vecOrigin"fAttackerLoc);
    
    
/* From Zombie:Reloaded */
    
MakeVectorFromPoints(fClientLocfAttackerLocfClientLoc);
    
NormalizeVector(fClientLocfClientLoc);
    
ScaleVector(fClientLoc, (fKnockBack * -1.0));
    
    
GetEntPropVector(clientProp_Data"m_vecAbsVelocity"fAttackerLoc);
    
AddVectors(fClientLocfAttackerLocfClientLoc);
    
    
TeleportEntity(clientNULL_VECTORNULL_VECTORfClientLoc);

While the plugin applies attributes to the client, the plugin has no logic (brain) that makes any game based decisions. The only thing the plugin does is abstract calls from a more specific API.

Example of an End User (2):
PHP Code:
public ProcessThroughHooks(bool:bCheck, const String:sName[])
{
    
AddHookIfExists(bCheckg_iGlobalBitseZombifyClientPost"ZME_OnZombifyClient_Post"ZME_OnZombifyClient_PostsName);
    
AddHookIfExists(bCheckg_iGlobalBitseHookClassApply"CM_OnClassUsed"CM_OnClassUsedsName);
    
AddHookIfExists(bCheckg_iGlobalBitseHookClientManager"CM_OnClassesChanged"CM_OnClassesChangedsName);
}

public 
ZME_OnZombifyClient_Post(clientattackerbool:bMother)
{
    if (
g_fShakeArray[client][2] == 0.0)
    {
        return;
    }
    
    new 
Handle:hShake StartMessageOne("Shake"client);    
    
    if (
hShake == INVALID_HANDLE)
    {
        return;
    }
    
    
BfWriteByte(hShake0);
    for (new 
= (sizeof(g_fShakeArray) - 1); >= 0; --i)
    {
        
BfWriteFloat(hShakeg_fShakeArray[client][i]);
    }
    
    
EndMessage();
}

public 
CM_OnClassUsed(iClientiClassiTeam)
{
    new 
iArraySize GetArraySize(g_hClassArray);

    if (!(
<= iClass iArraySize))
    {
        
CM_OnClassesChanged(CM_RegenerateClasses);
    }
    
    
GetArrayArray(g_hClassArrayiClassg_fShakeArray[iClient]);
}

public 
CM_OnClassesChanged(iClassNumber)
{
    
ClearArray(g_hClassArray);

    if (
iClassNumber == CM_RegenerateClasses)
    {
        
iClassNumber CM_NumberOfClasses();
    }
    
    if (!
iClassNumber)
    {
        return;
    }
    
    
decl String:sShakeString[64];
    new 
Float:fInfo[sizeof(g_fShakeArray[])];
    static const 
String:sScan[sizeof(fInfo)][] = { "ShakeDuration""ShakeFrequency""ShakeAmplitude" };
    
    for (new 
iiClassNumberi++)
    {
        for (new 
= (sizeof(sScan) - 1); >= 0; --k)
        {
            if (!
CM_GetString(isScan[k], sShakeStringsizeof(sShakeString)))
            {
                
fInfo[k] = 0.0;
            }
            else
            {
                
fInfo[k] = StringToFloat(sShakeString);
            }
        }

        
PushArrayArray(g_hClassArrayfInfo);
    }

The `Brain` plugin provides no API, only a feature (screen shake on infection). If the plugin is removed, the only thing the user notices is the screen no longer shakes on infection. If the plugin is loaded mid-round, if someone is infected they should have their screen shake.


Lastly, the API plugin (1) should hold no hooks with other plugins, rather stock SM features. The single thing they do is provide an API to other plugins.
PHP Code:
public RegisterMyForwards()
{
    if (
g_iGlobalBits eForwardsRegistered)
    {
        return;
    }
    
    
HM_RegisterHook(g_hGlobalTeams"ZME_OnClientArrayChangeTeam");
    
HM_RegisterHook(g_hSinglePlayerTeam"ZME_OnClientChangeTeam");
    
HM_RegisterHook(g_hSinglePlayerTeamPost"ZME_OnClientChangeTeam_Post");
    
g_iGlobalBits |= eForwardsRegistered;
}

public 
Native_OnChangeArrayClientTeam(Handle:pluginnumParams/* iPlayer A / iTeam A / iCount */
{
    
decl iPlayerArray[MAXPLAYERS+1];
    
GetNativeArray(1iPlayerArrayMAXPLAYERS+1);
    
    new 
iMaxRead GetNativeCell(2);
    new 
iTeam GetNativeCell(3);
    
    for (new 
iiMaxReadi++)
    {
        
g_iClientTeam[iPlayerArray[i]] = iTeam;
    }
    
    
Call_StartForward(g_hGlobalTeams);
    
Call_PushArray(iPlayerArrayiMaxRead);
    
Call_PushCell(iMaxRead);
    
Call_PushCell(iTeam);
    
Call_Finish();

Hopefully this helps, and makes sense.
Attached Files
File Type: inc HookMan.inc (1.8 KB, 308 views)
KyleS is offline
rhelgeby
Veteran Member
Join Date: Oct 2008
Location: 0x4E6F72776179
Old 01-12-2014 , 02:42   Re: The Future of Zombie Plugins
Reply With Quote #3

This is almost exactly how the Zombie API is intended to work.

The only difference is that the function callbacks in hooks are not type safe:

PHP Code:
stock bool:AddHookIfExists(bool:bHookManLoaded, &iBitArrayiBitToCheck, const String:sHookName[], Function:fFunc, const String:sPassedHook[]) 
If I pass a function with no parameters into fFunc, while the hook actually expect it to have parameters, it will crash.

This is solved with functags, which also means we'll have to create a hook native for every single type of callback. It's quite similar to adding listeners in Java, where each type of event has it's own method for registering listeners for a particular event. I think this is called the observer pattern.

A native for hooking events may accept a group (enum array) of callbacks specified with functags so that we don't have to create that many natives. But only group callbacks that makes sense, such as OnClientInfect, OnClientHuman or OnRoundStart and OnRoundEnd.

Type safety on event callbacks is absolutely essential. We must catch these errors as early as possible (which is on compile time). Otherwise the API won't be robust enough. Developers will make silly typo mistakes, or forget to update the callback function signature when the hook is changed. Although a hook callback type should not be changed after a release, that would break backwards compatibility.

MVC pattern
Otherwise I think we should go for something based on a MVC pattern so that code is loosely coupled and easier to maintain.

An example for the player class system would be something like below. It is simplified a lot because my idea for the class system is also based on event listeners and plugins for each class attribute (or a group of them).

Model:
The player class. One include file (or API) that provides operations on a single player class such as getters and setters.

Repository:
The class database. Stores all classes in memory and provides an API for inserting, and searching.

Controller/Manager:
Player class logic. API for applying a class on a player, doing stuff on infection, round start and so on.
__________________
Richard Helgeby

Zombie:Reloaded | PawnUnit | Object Library
(Please don't send private messages for support, they will be ignored. Use the forum.)

Last edited by rhelgeby; 01-13-2014 at 10:29.
rhelgeby is offline
Send a message via MSN to rhelgeby
benefitOfLaughing
Member
Join Date: Nov 2013
Old 01-18-2014 , 06:03   Re: The Future of Zombie Plugins
Reply With Quote #4

I suggest to add the RegPluginLibrary when the new version zombiereloaded starting.
It can make another plugin detect whether is zombiereloaded loaded.
benefitOfLaughing is offline
rhelgeby
Veteran Member
Join Date: Oct 2008
Location: 0x4E6F72776179
Old 01-18-2014 , 09:28   Re: The Future of Zombie Plugins
Reply With Quote #5

That's a good idea. The specification will some how need to solve dependency issues, either by itself or with help from SourceMod.

Update:
I'm considering an event system without support for priorities. Priorities adds complexity and coupling between modules.

Instead I'll focus on pre and post events like OnClientInfect and OnClientInfected. If a module depends on something to happen before something else, we should define an event for that.

There are very few cases where a module is modifying event priority in the future ZR 4.0 branch. All cases should be possible with pre and post events.
__________________
Richard Helgeby

Zombie:Reloaded | PawnUnit | Object Library
(Please don't send private messages for support, they will be ignored. Use the forum.)

Last edited by rhelgeby; 01-19-2014 at 05:05.
rhelgeby is offline
Send a message via MSN to rhelgeby
rhelgeby
Veteran Member
Join Date: Oct 2008
Location: 0x4E6F72776179
Old 01-20-2014 , 02:12   Re: The Future of Zombie Plugins
Reply With Quote #6

Late Loading

To support lade loading, modules will need to replay essential events that were fired before they were loaded. SourceMod already handles the basic stuff like OnMapStart, OnConfigsExecuted, OnClientConnected and others, but some custom zombie events must be replayed too.

This is not a responsibility for the event manager, but individual feature modules. The event manager don't know which events to forward.

I'm thinking of creating a new event in the module manager for notifying modules that a module was late loaded. The module manager itself can't detect it either (or maybe shouldn't), so it should provide a native where late loaded modules can notify the module manager themselves that they were late loaded. The module manager will then send the event so feature modules can replay events.

The tricky part is to replay essential events only for late loaded modules. This might imply that the event manager must support filtered events, so it can send an event to certain modules only. Forwards won't work for this, since they're global. We have to use callbacks and let feature modules fire individual events to a specific module. The module ID is passed as a parameter in the late load event.

Edit: Forwards may still work, since I didn't know about private forwards. The code will run a lot more efficient if we delegate most of the work to SourceMod.

Essential events to replay in the Zombie API (so far):
  • OnGameModeActivated - Lets the module initialize according to a certain game mode. More about game modes some other time.
  • OnPlayerTeam
  • OnRoundStart, OnRoundFreezeEnd - If it has started.
  • OnPlayerSpawn, OnPlayerSpawnPost
  • OnClientZombie, OnClientHuman - Tricky, some players may be dead or haven't spawned yet. Actual conditions for when these are fired are not completely defined yet.

Note that the event names are just examples.
__________________
Richard Helgeby

Zombie:Reloaded | PawnUnit | Object Library
(Please don't send private messages for support, they will be ignored. Use the forum.)

Last edited by rhelgeby; 01-25-2014 at 08:56.
rhelgeby is offline
Send a message via MSN to rhelgeby
KyleS
SourceMod Plugin Approver
Join Date: Jul 2009
Location: Segmentation Fault.
Old 01-22-2014 , 01:27   Re: The Future of Zombie Plugins
Reply With Quote #7

Quote:
Originally Posted by rhelgeby View Post
This is not a responsibility for the event manager, but individual feature modules. The event manager don't know which events to forward.
What I do is I use HM_AddNotificationHook which notifies a plugin when a hook is added for that name, then late-calling can be added, which is up to whatever plugin.
KyleS is offline
rhelgeby
Veteran Member
Join Date: Oct 2008
Location: 0x4E6F72776179
Old 01-22-2014 , 18:15   Re: The Future of Zombie Plugins
Reply With Quote #8

Yes, some kind of notification by the event manager is a good idea. The tricky part is to make late loading handling easy in feature modules. I don't want to add too much complexity, and especially avoid repeating code.

We may need some kind of general library where you register which events that should be fired on late load, and their order. Then the library should do the actual work, if that's possible.
__________________
Richard Helgeby

Zombie:Reloaded | PawnUnit | Object Library
(Please don't send private messages for support, they will be ignored. Use the forum.)
rhelgeby is offline
Send a message via MSN to rhelgeby
rhelgeby
Veteran Member
Join Date: Oct 2008
Location: 0x4E6F72776179
Old 01-25-2014 , 11:47   Re: The Future of Zombie Plugins
Reply With Quote #9

Here's an early incomplete draft of the core zombie API. Each include file is supposed to represent a single plugin. So far I've covered team management, basic modules and an event system using private forwards. Below I discuss a few new topics regarding this API.

Context Awareness
Some natives are context aware. That means if it's only supposed to do something with the caller's module, you don't have to pass the module ID. It will be looked up by the plugin handle by using the module manager.

The exceptions are when you want to do something with another module, or if there is a performance issue.

Dependencies
We need to support dependencies so that if a module that provides a service is disabled, all modules that depend on that service is notified or disabled too. And the opposite when the service is available again.

Instead of declaring dependencies on modules, I think it's better to depend on specific features. Then you avoid depending on other stuff you don't really need. And if a feature module becomes too big (player classes for instance) it can be split up into several modules without breaking other modules. The features still exists, but just moved to another module - so the dependency is satisfied.

Lookup and Cache
Most of the API is based on lookup functions. Modules need to discover what features that are available on plugin startup. I do this to force developers to use loose coupling, and to make the API dynamic. Essentially, the feature plugins doesn't know anything, they need to discover stuff themself.

One example is virtual teams. Even though there are predefined zombie and human teams, they must be looked up because the actual team ID may change if the team manager is reloaded. At runtime some module could actually create a new virtual team, but I haven't decided exactly how this should behave yet. Although the virtual teams in the zr-dev-base repository is sorted out - which is the basis of the team manager in this zombie API.

After lookup you may want to cache the results, unless the state is dynamic (such as ZM_IsClientZombie). The API should provide events to notify you when something that doesn't change that often (such as new teams), have changed.

I'm interested in what other plugin developers think about this. Are we going in the correct direction? Does the API make sense?
Attached Files
File Type: zip zombie-api-2014-01-25.zip (8.2 KB, 284 views)
__________________
Richard Helgeby

Zombie:Reloaded | PawnUnit | Object Library
(Please don't send private messages for support, they will be ignored. Use the forum.)

Last edited by rhelgeby; 01-25-2014 at 12:02.
rhelgeby is offline
Send a message via MSN to rhelgeby
Despirator
Senior Member
Join Date: Jun 2011
Location: Kazakhstan ->Shymkent
Old 01-26-2014 , 10:13   Re: The Future of Zombie Plugins
Reply With Quote #10

Makes sense for me. I think it's a good start to implement a new zombie plugin so we can make anything with everything very flexible.
Despirator is offline
Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -4. The time now is 00:08.


Powered by vBulletin®
Copyright ©2000 - 2024, vBulletin Solutions, Inc.
Theme made by Freecode