The Future of Zombie Plugins
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(); |
Re: The Future of Zombie Plugins
1 Attachment(s)
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:
PHP Code:
Example of an End User (2): PHP Code:
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:
|
Re: The Future of Zombie Plugins
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:
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. |
Re: The Future of Zombie Plugins
I suggest to add the RegPluginLibrary when the new version zombiereloaded starting.
It can make another plugin detect whether is zombiereloaded loaded. |
Re: The Future of Zombie Plugins
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. |
Re: The Future of Zombie Plugins
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):
Note that the event names are just examples. |
Re: The Future of Zombie Plugins
Quote:
|
Re: The Future of Zombie Plugins
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. |
Re: The Future of Zombie Plugins
1 Attachment(s)
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? |
Re: The Future of Zombie Plugins
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.
|
| All times are GMT -4. The time now is 12:40. |
Powered by vBulletin®
Copyright ©2000 - 2024, vBulletin Solutions, Inc.