-
-
Notifications
You must be signed in to change notification settings - Fork 14
API Tasks
Each subclass of DV.Logic.Job.Task has a unique set of properties, fields and behaviours that require custom serialisation and deserialisation methods.
Multiplayer handles task synchronisation through a TaskNetworkData factory pattern, with custom serialisation routines for each DV.Logic.Job.TaskType. Additionally, the Multiplayer has built-in methods for serialising/deserialising the common fields shared by all task types.
Each serialisation/deserialisation method pair must be registered with the TaskNetworkData factory to enable automatic sync of these tasks.
Serialisation/deserialisation method pairs can be unregistered if your mod is disabled/unloaded, however, Multiplayer's serialisers/deserialisers for base-game TaskTypes can not be unregistered.
Multiplayer has implemented serialisers/deserialisers for all base-game Task, any extensions to these should be done through inheritance and a custom TaskType.
Writing a TaskNetworkData serialiser/deserialiser requires implementing the abstract class TaskNetworkData<T>.
The methods to be implemented are:
-
T FromTask(Task task)- Your implementation must call
FromTaskCommon()
- Your implementation must call
-
void Serialize(BinaryWriter writer)- Your implementation must call
SerializeCommon()
- Your implementation must call
-
void Deserialize(BinaryReader reader)- Your implementation must call
DeserializeCommon()
- Your implementation must call
-
Task ToTask(ref Dictionary<ushort, Task> netIdToTask)- Your implementation must call
ToTaskCommon() - Your implementation must add the task to the
netIdToTaskdictionary
- Your implementation must call
-
List<ushort> GetCars()- Your implementation must return a non-null
List<ushort>
- Your implementation must return a non-null
Your implementation needs to collect the unique fields to be serialised when FromTask is called, but does not need to implement code for gathering and serialising the common fields. Similarly, ToTask() needs to recreate the task and populate the unique fields, but does not need to deserialise or populate the common fields.
Common fields serialised/deserialised for you:
-
ushorttaskNetId (this is the NetId assigned to the task by Multiplayer) -
TaskStatestate -
floattaskStartTime -
floattaskFinishTime -
boolIsLastTask -
floatTimeLimit
Caution
It is critical that the serialisation and deserialisation is balanced and completed in the same order, otherwise data corruption and de-syncs are likely to occur.
This section has been provided as an example of an inbuilt serialiser/deserialiser for a base-game TaskType.
A WarehouseTask contains the following unique fields:
-
List<DV.Logic.Job.Car>cars -
DV.Logic.Job.WarehouseTaskTypewarehouseTaskType -
DV.Logic.Job.WarehouseMachinewarehouseMachine -
DV.ThingTypes.CargoTypecargoType -
floatcargoAmount -
boolreadyForMachine
public class WarehouseTaskData : TaskNetworkData<WarehouseTaskData>
{
// Fields to be serialised
public ushort[] CarNetIDs { get; set; }
public WarehouseTaskType WarehouseTaskType { get; set; }
public string WarehouseMachine { get; set; }
public CargoType CargoType { get; set; }
public float CargoAmount { get; set; }
public bool ReadyForMachine { get; set; }
public override WarehouseTaskData FromTask(Task task)
{
if (task is not WarehouseTask warehouseTask)
throw new ArgumentException("Task is not a WarehouseTask");
// Mandatory call to FromTaskCommon()
FromTaskCommon(task);
// Extract list of cars and get their netIds
CarNetIDs = warehouseTask.cars.Select
(
car =>
{
if (car == null || !MultiplayerAPI.Instance.TryGetNetId(car, out var netId))
return (ushort)0;
return netId;
}
).ToArray();
// Extract standard WarehouseTask fields
WarehouseTaskType = warehouseTask.warehouseTaskType;
WarehouseMachine = warehouseTask.warehouseMachine.ID;
CargoType = warehouseTask.cargoType;
CargoAmount = warehouseTask.cargoAmount;
ReadyForMachine = warehouseTask.readyForMachine;
return this;
}
}public class WarehouseTaskData : TaskNetworkData<WarehouseTaskData>
{
public ushort[] CarNetIDs { get; set; }
public WarehouseTaskType WarehouseTaskType { get; set; }
public string WarehouseMachine { get; set; }
public CargoType CargoType { get; set; }
public float CargoAmount { get; set; }
public bool ReadyForMachine { get; set; }
// Prior methods ...
public override void Serialize(BinaryWriter writer)
{
// Mandatory call to SerializeCommon()
SerializeCommon(writer);
// Write each field (note: WriteUShortArray() is a utility provided by Multiplayer API)
writer.WriteUShortArray(CarNetIDs);
writer.Write((byte)WarehouseTaskType);
writer.Write(WarehouseMachine ?? string.Empty);
writer.Write((int)CargoType);
writer.Write(CargoAmount);
writer.Write(ReadyForMachine);
}
}public class WarehouseTaskData : TaskNetworkData<WarehouseTaskData>
{
public ushort[] CarNetIDs { get; set; }
public WarehouseTaskType WarehouseTaskType { get; set; }
public string WarehouseMachine { get; set; }
public CargoType CargoType { get; set; }
public float CargoAmount { get; set; }
public bool ReadyForMachine { get; set; }
// Prior methods ...
public override void Deserialize(BinaryReader reader)
{
// Mandatory call to DeserializeCommon()
DeserializeCommon(reader);
// Read each field (note: ReadUShortArray() is a utility provided by Multiplayer API)
CarNetIDs = reader.ReadUShortArray();
WarehouseTaskType = (WarehouseTaskType)reader.ReadByte();
WarehouseMachine = reader.ReadString();
CargoType = (CargoType)reader.ReadInt32();
CargoAmount = reader.ReadSingle();
ReadyForMachine = reader.ReadBoolean();
}
}public class WarehouseTaskData : TaskNetworkData<WarehouseTaskData>
{
public ushort[] CarNetIDs { get; set; }
public WarehouseTaskType WarehouseTaskType { get; set; }
public string WarehouseMachine { get; set; }
public CargoType CargoType { get; set; }
public float CargoAmount { get; set; }
public bool ReadyForMachine { get; set; }
// Prior methods ...
public override Task ToTask(ref Dictionary<ushort, Task> netIdToTask)
{
// Retrieve Car objects from netIds
List<Car> cars = CarNetIDs.Select(netId => MultiplayerAPI.Instance.TryGetObjectFromNetId(netId, out Car car) ? car : null)
.OfType<Car>()
.ToList();
// Create the Task using the constructor
WarehouseTask newWarehouseTask = new
(
cars,
WarehouseTaskType,
JobSaveManager.Instance.GetWarehouseMachineWithId(WarehouseMachine),
CargoType,
CargoAmount,
(long)TimeLimit,
IsLastTask
);
// Mandatory call to ToTaskCommon()
ToTaskCommon(newWarehouseTask);
// Populate fields not set in the constructor
newWarehouseTask.readyForMachine = ReadyForMachine;
// Add the Task and its netId to the dictionary
netIdToTask.Add(TaskNetId, newWarehouseTask);
return newWarehouseTask;
}
}public class WarehouseTaskData : TaskNetworkData<WarehouseTaskData>
{
public ushort[] CarNetIDs { get; set; }
public WarehouseTaskType WarehouseTaskType { get; set; }
public string WarehouseMachine { get; set; }
public CargoType CargoType { get; set; }
public float CargoAmount { get; set; }
public bool ReadyForMachine { get; set; }
// Prior methods ...
public override List<ushort> GetCars()
{
return CarNetIDs.ToList();
}
}This section has been provided as an example of an inbuilt serialiser/deserialiser for a base-game TaskType.
A SequentialTasks contains the following unique fields:
-
LinkedList<DV.Logic.Job.Task>tasks -
LinkedListNode<DV.Logic.Job.Task>currentTask
public class SequentialTasksData : TaskNetworkData<SequentialTasksData>
{
public TaskNetworkData[] Tasks { get; set; }
public override SequentialTasksData FromTask(Task task)
{
if (task is not SequentialTasks sequentialTasks)
throw new ArgumentException("Task is not a SequentialTasks");
// Mandatory call to FromTaskCommon()
FromTaskCommon(task);
// Serialise all sub-tasks
Tasks = MultiplayerAPI.Instance.ConvertTasks(sequentialTasks.tasks);
return this;
}
}public class SequentialTasksData : TaskNetworkData<SequentialTasksData>
{
public TaskNetworkData[] Tasks { get; set; }
// Prior methods ...
public override void Serialize(BinaryWriter writer)
{
// Mandatory call to SerializeCommon()
SerializeCommon(writer);
// Write the length of the Tasks array
writer.Write((byte)Tasks.Length);
// Call Serialize() on each sub-Task
foreach (var task in Tasks)
{
writer.Write((byte)task.TaskType);
task.Serialize(writer);
}
}
}public class SequentialTasksData : TaskNetworkData<SequentialTasksData>
{
public TaskNetworkData[] Tasks { get; set; }
// Prior methods ...
public override void Deserialize(BinaryReader reader)
{
// Mandatory call to DeserializeCommon()
DeserializeCommon(reader);
// Read the length of the list of Tasks
var tasksLength = reader.ReadByte();
// Size the Tasks array and read each serialised Task
Tasks = new TaskNetworkData[tasksLength];
for (int i = 0; i < tasksLength; i++)
{
// Read the TaskType
var taskType = (TaskType)reader.ReadByte();
// Request an empty TaskNetworkData for the TaskType
Tasks[i] = MultiplayerAPI.Instance.ConvertTask(taskType);
Tasks[i].Deserialize(reader);
}
}
}public class SequentialTasksData : TaskNetworkData<SequentialTasksData>
{
public TaskNetworkData[] Tasks { get; set; }
// Prior methods ...
public override Task ToTask(ref Dictionary<ushort, Task> netIdToTask)
{
List<Task> tasks = [];
// Convert each TaskNetworkData in the sub-Tasks
foreach (var task in Tasks)
{
var taskResults = task.ToTask(ref netIdToTask);
tasks.Add(taskResults);
}
// Create the Task using the constructor
SequentialTasks newSequentialTask = new(tasks, (long)TimeLimit);
// Mandatory call to ToTaskCommon()
ToTaskCommon(newSequentialTask);
// Add the Task and its netId to the dictionary
netIdToTask.Add(TaskNetId, newSequentialTask);
// Rebuild linked list task states - this is the equivalent of SequentialTasks.OverrideTaskState()
int index = 0;
for (var currentNode = newSequentialTask.tasks.First; currentNode != null; currentNode = currentNode.Next)
{
currentNode.Value.state = tasks[index].state;
currentNode.Value.taskStartTime = tasks[index].taskStartTime;
currentNode.Value.taskFinishTime = tasks[index].taskFinishTime;
if (tasks[index].state == TaskState.Done && currentNode != newSequentialTask.tasks.Last)
newSequentialTask.currentTask = currentNode.Next;
index++;
}
return newSequentialTask;
}
}public class SequentialTasksData : TaskNetworkData<SequentialTasksData>
{
public TaskNetworkData[] Tasks { get; set; }
// Prior methods ...
public override List<ushort> GetCars()
{
List<ushort> result = [];
foreach (var task in Tasks)
{
var cars = task.GetCars();
result.AddRange(cars);
}
return result;
}
}Once you have written your serialiser/deserialiser you must register it. Registration can be done any time after the Multiplayer API has initialised, but should be done prior to any job sync occurring. The ideal time to complete the registration is when you are registering for Server and Client Started/Stopped events.
Registration requires calling RegisterTaskType<TCustomTask, TTaskNetworkData>(), using your custom Task as the first type argument, your TaskNetworkData class as the second type argument, and passing in the unique DV.Logic.Job.TaskType (you will need to extend this enum).
Example:
public static class MyMod
{
public static UnityModManager.ModEntry ModEntry;
public static bool Load(UnityModManager.ModEntry modEntry)
{
ModEntry = modEntry;
// Your mod initialisation here
// ...
if (MultiplayerAPI.IsMultiplayerLoaded)
{
MultiplayerAPI.Instance.SetModCompatibility(ModEntry.Info.Id, MultiplayerCompatibility.All);
// Register custom task types for Multiplayer serialisation and deserialisation.
MultiplayerAPI.Instance.RegisterTaskType<MyCustomTask, MyCustomTaskData>(MyCustomTask.TaskType);
MultiplayerAPI.ServerStarted += OnServerStarted;
MultiplayerAPI.ClientStarted += OnClientStarted;
}
return true;
}
}If you need to unregister a task serialiser/deserialiser, simply call UnregisterTaskType<TCustomTask>() passing in the unique DV.Logic.Job.TaskType.
Important
Prior to unregistering a task serialiser/deserialiser, ensure there are no more Jobs containing this task type in existance, otherwise de-syncs may occur. The safest time to call UnregisterTaskType() is when a game session is not in progress.
Getting Started
Guides
- The Basics
- Packet Naming Conventions
- Defining Packets
- Registering Packet Listeners
- Sending Packets