Client/Shared/Server paradigm that Robust uses. If not, you should read up on the previous documentation.
SS14 is a multiplayer game! This is a very important fact, and its a fact you’ll likely have to reckon with quite a lot when coding. Thinking through this properly is important to ensure that things go smoothly and there are no potential security oversights.
Most of the time, networking will involve the server sending some important data to the client, so that the client can do things with it, like show a user interface or show appearance visuals. This is known as replication, and in SS14 is primarily handled through component states.
Component States
A component state is simple—its just a data class inheritingComponentState. They define which data is sent to the client, and are built up from data within components.
How does the game know when to send this data? Obviously, it doesn’t just send it constantly—that’s hugely unnecessary and would be terrible for performance and bandwidth. Instead, a server system must call Dirty(EntityUid uid, Component component), which marks the entity as ‘dirty’, meaning that it will create and send a new component state for it next tick.
Two special events exist for putting data into and getting data out of component states: ComponentGetState and ComponentHandleState. GetState is always called on the server, and HandleState is called on the client. However, both event subscriptions can go in Shared, and it will still work as expected!
Auto-Component State Generation
Robust Toolbox supports using source generators to massively simplify component state networking. This is vastly preferred to trying to do it manually in most situations. This works by using a C# feature to analyze code before it is compiled, and to automatically generate boilerplate source code. First, your component, and all of the networked fields, should be inContent.Shared, and the component class should be marked with [NetworkedComponent], which enables networking in the first place.
To use the source generator to automatically replicate fields, make your component class partial, annotate it with [AutoGenerateComponentState], and mark any fields you want to be networked with [AutoNetworkedField]. Then, when you dirty the component (or when it’s first added to an entity), it should Just Work™️ and the client will have all networked fields.
If you have code in the handle state that calls some function after the field setting has been done (such as updating appearance), change the component state attribute to [AutoGenerateComponentState(true)] , and then you can subscribe by-ref to AfterAutoHandleStateEvent and do things in there!
If your field requires cloning for prediction purposes (such as a dict), you can change the field attribute to [AutoNetworkedField(true)]. If you need more complex networking, the manual method should be used.
An example of all of the networking code required for IDCardComponent now, from https://github.com/space-wizards-federation/space-station-14/pull/14845:
Manual Component State Handling
Sometimes the manual method has to be used, if the handling is more complicated than just simple setting of fields. Let’s take ambient sounds as an example (although in this instance it could easily be autogenerated):[Serializable, NetSerializable], which is required for any object being sent over the network. This class defines three variables that it wants to sync to the client—whether this sound is enabled, its range, and its volume.
Let’s see how this state is constructed on the server:
ref ComponentGetState args rather than simply ComponentGetState args. This is required for certain events, as they are value-types raised ‘by-ref’, rather than just classes inheriting EntityEventArgs. This is done for performance reasons and isn’t super important, but its good to note as it can slip you up with runtime errors that may be confusing if you forget ref.
To specify the state to send, you simply set the State field on the event with your new component state, constructed from the values on the server component. Easy!
And on the client (technically still in shared, but this code is only run on the client!)
ref.
The first line of the method just does some fancy C# Pattern Matching to cast the Current field on the event args into the state that we’re looking for, since this is a pretty generic event.
From then on, the client simply uses the values contained in the component state to sync its component, so that any client-specific ambient code (like.. you know.. playing the sounds) can run as the server intends.
Component Networking Example
As a high-level example, let’s see how atmospheric vents handle their ambient sounds.SetAmbience function, the ambience system calls Dirty on the entity, which tells the server that this entities data has been updated and the client needs to be made aware of that. Then, next tick, the server raises a ComponentGetState event on the vent, and the ambient sound state is created and sent.
Once the client receives it (after latency), it will raise ComponentHandleState on the vent, which then causes the ambient sound to be properly disabled on the client. Neat!
Network Events
The other main way for the server and client to communicate, besides replication through component states, and that’s network events and the lower-levelNetMessage.
Network events are as opposed to local events (RaiseLocalEvent or SubscribeLocalEvent ring a bell?), which are exclusively ‘local’ to the side of the network they were raised on, whereas network events are exclusively sent over the network. Network events use the equivalent RaiseNetworkEvent and SubscribeNetworkEvent.
Network events contain arbitrary data, not tied to any component or entity in specific (which means they can’t be directed), and can be sent at any time, from either the client or the server. When handling network event sent from the client on the server, you should obviously exercise caution and treat it as untrustworthy, since hackers can always send whatever data they please.
NetMessage is the low-level equivalent to network events (in fact, network events just create a NetMessage themselves). You should avoid using them unless you know what you’re doing, so I won’t cover them here besides mentioning them.
Example
Let’s look at adminhelps (also called the bwoink system) and see how that sends arbitrary non-entity-specific data to clients. Here’s how the network event is defined:NetSerializable, for the same reasons mentioned above for component states. I won’t go over the specific data here—I imagine you get the idea.
The interesting thing about this event is that its raised and handled on both the client and server. So, we’ll look at those separately.
Client to Server
Send here is called whenever the BWOINK (tm) UI input text is entered on the client:
Server Handling
Okay the handler for this is a little big so I’ll trim it down to the important bits:Client Handling
Let’s go back to the client to see what it does with this new text message sent from the server.Potentially Visible Set (PVS)
You’ve likely heard this term bandied about a bit if you’ve looked in development, as its a pretty big deal. Think for a second—there’s a lot of god damn entities in this game! And most likely, a lot of them are callingDirty constantly, and there’s going to be a lot of clients too. How do we figure out how to send these states to each client? The answer is with the Potentially Visible Set system, or PVS.
This isn’t going to be a super low-level overview of it or anything, but basically, PVS is based on chunks, and only sends component states to clients that are in chunk range of the entity. This is done for two main reasons:
- It reduces bandwidth by a lot, by not sending component states to clients that can’t even see the entity in question.
- It reduces the possibility for cheating, since hackers physically don’t receive any data about entities too far away from them.