This page describes how to handle various things related to client-server communications. This includes sending messages, implementing prediction, etc.
Core Keeper is a server authoritative game. This means for an action to occur, the server must be aware of it and approve. When we write mods we must be able to create logic that allows for Client-Server communication to occur.
Note that Unity uses extensive code gen to implement features outlined here. If you ever get errors regarding CodeGen ensure your mod output Scripts folder has Generated folder. If it doesn't your Unity project might have an error. A restart usually helps
Additionally you might want to utilize Burst
Ghost Components
Ghost component is one of the simplest ways to transfer data between Client and server. In its essence Ghost Component is just a normal ECS component that is synced over network.
To make a Ghost Component first Create IComponentData deriving ECS component:
To make this component a Ghost Component all you have to do is add a GhostComponent attribute to it. Additionally you must mark every field you want to be synced with GhostField attribute:
[GhostComponent]publicstructExampleCD:IComponentData{ // This field will end up synced [GhostField] publicint importantField; // This field isn't marked, so it won't be syncedpublicfloat normalField;}
Making Ghost Components is just as easy as this. Now any data set to this component of the server will be automatically replicated to all clients. Refer to this guide to learn more
Remote Procedure Calls
Remote Procedure Calls (or RPC's) are a way to send information to the Server or clients in one-shot manner. They allow you to send arbitrary data or commands to the other side, be it requests or data
This example will focus on Client to Server communication, as that is the most common scenario, however you can send RPC from Server to Clients or even both ways
To make a RPC first add RPC command data definition:
publicenumMyModCommandType:byte{ UNDEFINED, DO_VERY_IMPORTANT_THING, ANOTHER_IMPORTANT_TASK //etc}publicstructMyModCommandRPC:IRpcCommand{ // This enum will define what kind of command this ispublicMyModCommandType commandType; // This is bundled request data. // This can be target entities, such as players // Or any other neccessary datapublicEntity entity0;publicint value; // you can add more fields as needed}
Now define two systems. One for Clients:
[UpdateInGroup(typeof(RunSimulationSystemGroup))][WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]publicpartialclassClientModCommandSystem:PugSimulationSystemBase{privateNativeQueue<MyModCommandRPC> rpcQueue;privateEntityArchetype rpcArchetype;protectedoverridevoidOnCreate() {UpdatesInRunGroup(); rpcQueue =newNativeQueue<MyModCommandRPC>(Allocator.Persistent); rpcArchetype =EntityManager.CreateArchetype(typeof(MyModCommandRPC),typeof(SendRpcCommandRequest)); base.OnCreate(); }#regionCommands // This is the most important section // Here you can make send functions to have // easy interface with the rest of your codepublicvoidDoVeryImportantThing(Entity target,int value) {rpcQueue.Enqueue(newMyModCommandRPC() { commandType =MyModCommandType.DO_VERY_IMPORTANT_THING, entity0 = target, value = value }); }#endregionprotectedoverridevoidOnUpdate() {EntityCommandBuffer entityCommandBuffer =CreateCommandBuffer();while (rpcQueue.TryDequeue(outMyModCommandRPC component)) {Entity e =entityCommandBuffer.CreateEntity(rpcArchetype);entityCommandBuffer.SetComponent(e, component); } }}
And one for Server:
[UpdateInGroup(typeof(SimulationSystemGroup))][WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]publicpartialclassServerModCommandSystem:PugSimulationSystemBase{protectedoverridevoidOnUpdate() {var ecb =CreateCommandBuffer();Entities.ForEach((Entity rpcEntity,inMyModCommandRPC rpc,inReceiveRpcCommandRequest req) => {switch (rpc.commandType) {caseMyModCommandType.DO_VERY_IMPORTANT_THING: // Do very important thing // This code will be executed on the serverbreak; }ecb.DestroyEntity(rpcEntity); }) .WithoutBurst() .Schedule(); base.OnUpdate(); }}
Now to use these systems you just have to add this code in your IMod class:
publicstaticClientModCommandSystem clientSystem;// You might already have this method// if so, just add method body to your codepublicvoidEarlyInit() {API.Client.OnWorldCreated+= ClientWorldReady;}privatestaticvoidClientWorldReady(){var world =API.Client.World; clientSystem =world.GetOrCreateSystemManaged<ClientModCommandSystem>();}
Now you can use MyMod.clientSystem.DoVeryImportantThing() from anywhere in your code
Prediction
This guide has not been written yet! If you know how to do this, add it!