TL;DR

Another changelog is out. I’ve added a unified inventory system for plugins plus tidied up a lot of code in preparation for starting on the heist-gamemode plugin next week.

This week…

Watch The Video!

  • New “Under The Hood” section in blog partner blog posts to the video covering technical details
  • New unified inventory system for plugins
  • Reworked guns to work with inventory system for ammo
  • Rebuilt character service to make things like reviving downed teammates, respawning on teammates and spectator cameras for dead/non players much easier to implement.

Under The Hood

As I said in the video it seems like a bit of a waste to have these blog posts where I just re-iterate what I’ve said in the video. Instead I’m going to try and have these “under the hood” sections where I’ll talk about the technical aspects of what I implemented and how I did it.

Event System

The event system is how the engine sends and routes messages about events. Things like:

  • EntityCreated
  • EntityDied
  • PhysicsContactCreated
  • InventoryUpdated
  • ToolEquipped

are all events that the engine publishes, along with hundreds of others. Sending events is cheap so almost everything sends out some events about things that other systems may be interested in. Sending an subscribing to events is done with a generically typed system, to get an event sender you call:

 var sender = e.GetEventSender<TypeOfEvent>(scope);

and to subscribe to events:

 e.Subscribe<TypeOfEvent>(subscriber, scope);

So this seems like a pretty good way to do things, event senders and subscribers are all strongly typed so you can never accidentally subscribe the wrong type of listener to the wrong type of event. Additionally there is the scope, which must match up for a message to get from sender to receiver.

However the scripting system had a problem with this: scripting types are never loaded into the engine domain. Which basically means you cannot use a type defined in a plugin within the engine (for sandbox/security purposes). The obvious problem, of course, is if you can’t use the types defined in plugins how can a plugin ever use an interface which depends entirely on generic types?

There are two cases we need to consider:

  1. The plugin is using an engine type
  2. The plugin is using some type defined in the plugin (which the sandbox forbids the engine from understanding)
Plugin Using Engine Types

If the plugin is using a type defined in the engine or some other primitive type then everything is fine, that type is already loaded into the engine and the event system can just be used normally. I determine this by inspecting the assembly of the type and the list of all assemblies loaded into the engine, if the engine has the type assembly loaded then it can understand this type just fine.

Plugin Using It’s Own Type

If the plugin is using some type which it defines then the plugin system exploits the scope system to fake events. An event of type PluginEvent is created, plugin event is a pretty simple type:

struct PluginEvent
{
    byte[] serialized;
}

The scope is set to the full (assembly qualified) name of the type. Any time the plugin sends an event it is serialized and sent as normal. Thanks to the scope system the only receivers which will get the message are ones looking for the same type (whereupon, of course, the type is deserialized back into the correct type).

Character Respawning

The changes to the character system are pretty simple, they’re really just a slight rearrangement of the old system. Under the old system there was a service (in the engine) which monitored the character entity and, when it died, asked the GameMode object to create a new one. The new system does away with this and does the same work within the GameMode object. Since all the work is done within the GameMode this means that more advanced character handling around death and respawning is much easier to implement.