From 3888bfa7ee532beb4d0a3ef252997844a5f5ef53 Mon Sep 17 00:00:00 2001
From: linmeideli <2558527373@qq.com>
Date: Wed, 7 Oct 2026 09:23:18 +0800
Subject: [PATCH] https://github.com/Impostor/Impostor/pull/734
---
.../Innersloth/Data/maps/Mira/systems.json | 2 +
.../Innersloth/Data/maps/Polus/systems.json | 2 +
.../Net/Custom/ICustomSystemType.cs | 16 ++
.../Net/MessageWriterExtensions.cs | 5 +
.../Net/Messages/Rpcs/Rpc35UpdateSystem.cs | 21 ++-
.../Objects/ShipStatus/InnerAirshipStatus.cs | 12 +-
.../ShipStatus/InnerDleksShipStatus.cs | 10 +-
.../ShipStatus/InnerFungleShipStatus.cs | 12 +-
.../Objects/ShipStatus/InnerMiraShipStatus.cs | 12 +-
.../ShipStatus/InnerPolusShipStatus.cs | 10 +-
.../Objects/ShipStatus/InnerShipStatus.cs | 108 ++++++++++---
.../ShipStatus/InnerSkeldShipStatus.cs | 10 +-
.../Net/Inner/Objects/Systems/ISystemType.cs | 6 +-
.../Inner/Objects/Systems/SequenceBuffer.cs | 75 +++++++++
.../Systems/ShipStatus/AutoDoorsSystemType.cs | 52 +++++-
.../Systems/ShipStatus/DeconSystemType.cs | 65 ++++++++
.../Systems/ShipStatus/DoorsSystemType.cs | 48 +++++-
.../Systems/ShipStatus/ElectricalDoors.cs | 22 +--
.../ShipStatus/HeliSabotageSystemType.cs | 72 +++++++--
.../Systems/ShipStatus/HqHudSystemType.cs | 100 ++++++++++++
.../ShipStatus/HudOverrideSystemType.cs | 14 +-
.../Objects/Systems/ShipStatus/IDoorSystem.cs | 7 +
.../Systems/ShipStatus/LifeSuppSystemType.cs | 49 +++++-
.../Systems/ShipStatus/MedScanSystem.cs | 33 +++-
.../ShipStatus/MovingPlatformBehaviour.cs | 48 +++++-
.../MushroomMixupSabotageSystemType.cs | 27 +++-
.../Systems/ShipStatus/ReactorSystemType.cs | 80 ++++++++-
.../Systems/ShipStatus/SabotageSystemType.cs | 51 +++++-
.../ShipStatus/SecurityCameraSystemType.cs | 37 ++++-
.../Systems/ShipStatus/SwitchSystem.cs | 26 ++-
.../ShipStatus/VentilationSystemType.cs | 152 ++++++++++++++++++
src/Empostor.Server/Program.cs | 1 +
32 files changed, 1059 insertions(+), 126 deletions(-)
create mode 100644 src/Empostor.Api/Net/Custom/ICustomSystemType.cs
create mode 100644 src/Empostor.Server/Net/Inner/Objects/Systems/SequenceBuffer.cs
create mode 100644 src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/DeconSystemType.cs
create mode 100644 src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/HqHudSystemType.cs
create mode 100644 src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/IDoorSystem.cs
create mode 100644 src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/VentilationSystemType.cs
diff --git a/src/Empostor.Api/Innersloth/Data/maps/Mira/systems.json b/src/Empostor.Api/Innersloth/Data/maps/Mira/systems.json
index e5aefe9..52d0337 100644
--- a/src/Empostor.Api/Innersloth/Data/maps/Mira/systems.json
+++ b/src/Empostor.Api/Innersloth/Data/maps/Mira/systems.json
@@ -5,5 +5,7 @@
"Reactor": "ReactorSystemType",
"LifeSupp": "LifeSuppSystemType",
"Ventilation": "VentilationSystem",
+ "Decontamination": "DeconSystem",
+ "Decontamination2": "DeconSystem",
"Sabotage": "SabotageSystemType"
}
\ No newline at end of file
diff --git a/src/Empostor.Api/Innersloth/Data/maps/Polus/systems.json b/src/Empostor.Api/Innersloth/Data/maps/Polus/systems.json
index 91d230f..cf91642 100644
--- a/src/Empostor.Api/Innersloth/Data/maps/Polus/systems.json
+++ b/src/Empostor.Api/Innersloth/Data/maps/Polus/systems.json
@@ -6,5 +6,7 @@
"Security": "SecurityCameraSystemType",
"Ventilation": "VentilationSystem",
"Laboratory": "ReactorSystemType",
+ "Decontamination": "DeconSystem",
+ "Decontamination2": "DeconSystem",
"Sabotage": "SabotageSystemType"
}
\ No newline at end of file
diff --git a/src/Empostor.Api/Net/Custom/ICustomSystemType.cs b/src/Empostor.Api/Net/Custom/ICustomSystemType.cs
new file mode 100644
index 0000000..ed5eac6
--- /dev/null
+++ b/src/Empostor.Api/Net/Custom/ICustomSystemType.cs
@@ -0,0 +1,16 @@
+using System.Threading.Tasks;
+using Empostor.Api.Net.Inner.Objects;
+using Empostor.Api.Net.Inner.Objects.ShipStatus;
+
+namespace Empostor.Api.Net.Custom
+{
+ ///
+ /// Handles custom system state and update payloads.
+ ///
+ public interface ICustomSystemType : ICustomMessage
+ {
+ ValueTask DeserializeAsync(IInnerShipStatus shipStatus, IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState);
+
+ ValueTask HandleUpdateSystemAsync(IInnerShipStatus shipStatus, IInnerPlayerControl? playerControl, IClientPlayer sender, IClientPlayer? target, IMessageReader reader);
+ }
+}
diff --git a/src/Empostor.Api/Net/MessageWriterExtensions.cs b/src/Empostor.Api/Net/MessageWriterExtensions.cs
index 9785222..8583300 100644
--- a/src/Empostor.Api/Net/MessageWriterExtensions.cs
+++ b/src/Empostor.Api/Net/MessageWriterExtensions.cs
@@ -30,6 +30,11 @@ public static class MessageWriterExtensions
}
}
+ public static void Write(this IMessageWriter writer, IMessageWriter message, bool includeHeader)
+ {
+ writer.Write(message.ToByteArray(includeHeader));
+ }
+
public static void Write(this IMessageWriter writer, Vector2 vector)
{
writer.Write((ushort)(Mathf.ReverseLerp(vector.X) * (double)ushort.MaxValue));
diff --git a/src/Empostor.Api/Net/Messages/Rpcs/Rpc35UpdateSystem.cs b/src/Empostor.Api/Net/Messages/Rpcs/Rpc35UpdateSystem.cs
index 8b0cff4..43e7137 100644
--- a/src/Empostor.Api/Net/Messages/Rpcs/Rpc35UpdateSystem.cs
+++ b/src/Empostor.Api/Net/Messages/Rpcs/Rpc35UpdateSystem.cs
@@ -6,22 +6,27 @@ namespace Empostor.Api.Net.Messages.Rpcs
{
public static class Rpc35UpdateSystem
{
- public static void Serialize(IMessageWriter writer, SystemTypes systemType, IInnerPlayerControl playerControl, ushort sequenceId, byte state, byte ventId)
+ public static void Serialize(IMessageWriter writer, SystemTypes systemType, IInnerPlayerControl playerControl, IMessageWriter payload)
{
writer.Write((byte)systemType);
writer.Write(playerControl);
- writer.Write(sequenceId);
- writer.Write(state);
- writer.Write(ventId);
+ writer.Write(payload, false);
}
- public static void Deserialize(IMessageReader reader, IGame game, out SystemTypes systemType, out IInnerPlayerControl? playerControl, out ushort sequenceId, out byte state, out byte ventId)
+ public static void Serialize(IMessageWriter writer, SystemTypes systemType, IInnerPlayerControl playerControl, byte amount)
+ {
+ writer.Write((byte)systemType);
+ writer.Write(playerControl);
+ writer.Write(amount);
+ }
+
+ public static void Deserialize(IMessageReader reader, IGame game, out SystemTypes systemType, out IInnerPlayerControl? playerControl, out IMessageReader payload)
{
systemType = (SystemTypes)reader.ReadByte();
playerControl = reader.ReadNetObject(game);
- sequenceId = reader.ReadUInt16();
- state = reader.ReadByte();
- ventId = reader.ReadByte();
+
+ // The system payload is appended directly with Write(payload, false); it is not a nested message.
+ payload = reader;
}
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerAirshipStatus.cs b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerAirshipStatus.cs
index 39a8338..d6881f4 100644
--- a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerAirshipStatus.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerAirshipStatus.cs
@@ -10,8 +10,9 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
{
internal class InnerAirshipStatus : InnerShipStatus, IInnerAirshipStatus
{
- public InnerAirshipStatus(ICustomMessageManager customMessageManager, Game game) : base(customMessageManager, game, MapTypes.Airship)
+ public InnerAirshipStatus(ICustomMessageManager customMessageManager, ICustomMessageManager customSystemManager, Game game) : base(customMessageManager, customSystemManager, game, MapTypes.Airship)
{
+ InitializeSystems();
}
public Vector2 PreSpawnLocation { get; } = new Vector2(-25f, 40f);
@@ -35,13 +36,14 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
{
base.AddSystems(systems);
- systems.Add(SystemTypes.Doors, new DoorsSystemType(Doors));
+ systems.Add(SystemTypes.Doors, new DoorsSystemType(Doors, Data.Doors));
systems.Add(SystemTypes.Comms, new HudOverrideSystemType());
systems.Add(SystemTypes.GapRoom, new MovingPlatformBehaviour());
- systems.Add(SystemTypes.Reactor, new HeliSabotageSystemType());
- systems.Add(SystemTypes.Decontamination, new ElectricalDoors(Doors));
- systems.Add(SystemTypes.Decontamination2, new AutoDoorsSystemType(Doors));
+ systems.Add(SystemTypes.HeliSabotage, new HeliSabotageSystemType());
+ systems.Add(SystemTypes.Decontamination, new ElectricalDoors());
+ systems.Add(SystemTypes.Decontamination2, new AutoDoorsSystemType(Doors, Data.Doors));
systems.Add(SystemTypes.Security, new SecurityCameraSystemType());
+ systems.Add(SystemTypes.Ventilation, new VentilationSystemType());
}
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerDleksShipStatus.cs b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerDleksShipStatus.cs
index 3377b40..397ceaa 100644
--- a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerDleksShipStatus.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerDleksShipStatus.cs
@@ -9,19 +9,21 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
{
internal class InnerDleksShipStatus : InnerShipStatus, IInnerDleksShipStatus
{
- public InnerDleksShipStatus(ICustomMessageManager customMessageManager, Game game) : base(customMessageManager, game, MapTypes.Dleks)
+ public InnerDleksShipStatus(ICustomMessageManager customMessageManager, ICustomMessageManager customSystemManager, Game game) : base(customMessageManager, customSystemManager, game, MapTypes.Dleks)
{
+ InitializeSystems();
}
protected override void AddSystems(Dictionary systems)
{
base.AddSystems(systems);
- systems.Add(SystemTypes.Doors, new AutoDoorsSystemType(Doors));
+ systems.Add(SystemTypes.Doors, new AutoDoorsSystemType(Doors, Data.Doors));
systems.Add(SystemTypes.Comms, new HudOverrideSystemType());
systems.Add(SystemTypes.Security, new SecurityCameraSystemType());
- systems.Add(SystemTypes.Reactor, new ReactorSystemType());
- systems.Add(SystemTypes.LifeSupp, new LifeSuppSystemType());
+ systems.Add(SystemTypes.Reactor, new ReactorSystemType(30f, SystemTypes.Reactor));
+ systems.Add(SystemTypes.LifeSupp, new LifeSuppSystemType(30f));
+ systems.Add(SystemTypes.Ventilation, new VentilationSystemType());
}
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerFungleShipStatus.cs b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerFungleShipStatus.cs
index 708f348..82f92ce 100644
--- a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerFungleShipStatus.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerFungleShipStatus.cs
@@ -8,17 +8,17 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
{
internal class InnerFungleShipStatus : InnerShipStatus
{
- public InnerFungleShipStatus(ICustomMessageManager customMessageManager, Game game) : base(customMessageManager, game, MapTypes.Fungle)
+ public InnerFungleShipStatus(ICustomMessageManager customMessageManager, ICustomMessageManager customSystemManager, Game game) : base(customMessageManager, customSystemManager, game, MapTypes.Fungle)
{
+ InitializeSystems();
}
protected override void AddSystems(Dictionary systems)
{
- base.AddSystems(systems);
-
- systems.Add(SystemTypes.Comms, new HudOverrideSystemType());
- systems.Add(SystemTypes.Reactor, new ReactorSystemType());
- systems.Add(SystemTypes.Doors, new DoorsSystemType(Doors));
+ systems.Add(SystemTypes.Ventilation, new VentilationSystemType());
+ systems.Add(SystemTypes.Comms, new HqHudSystemType());
+ systems.Add(SystemTypes.Reactor, new ReactorSystemType(60f, SystemTypes.Reactor));
+ systems.Add(SystemTypes.Doors, new DoorsSystemType(Doors, Data.Doors));
systems.Add(SystemTypes.MushroomMixupSabotage, new MushroomMixupSabotageSystemType());
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerMiraShipStatus.cs b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerMiraShipStatus.cs
index 49bae2d..76cd8fd 100644
--- a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerMiraShipStatus.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerMiraShipStatus.cs
@@ -9,17 +9,21 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
{
internal class InnerMiraShipStatus : InnerShipStatus, IInnerMiraShipStatus
{
- public InnerMiraShipStatus(ICustomMessageManager customMessageManager, Game game) : base(customMessageManager, game, MapTypes.MiraHQ)
+ public InnerMiraShipStatus(ICustomMessageManager customMessageManager, ICustomMessageManager customSystemManager, Game game) : base(customMessageManager, customSystemManager, game, MapTypes.MiraHQ)
{
+ InitializeSystems();
}
protected override void AddSystems(Dictionary systems)
{
base.AddSystems(systems);
- systems.Add(SystemTypes.Comms, new HudOverrideSystemType());
- systems.Add(SystemTypes.Reactor, new ReactorSystemType());
- systems.Add(SystemTypes.LifeSupp, new LifeSuppSystemType());
+ systems.Add(SystemTypes.Comms, new HqHudSystemType());
+ systems.Add(SystemTypes.Reactor, new ReactorSystemType(45f, SystemTypes.Reactor));
+ systems.Add(SystemTypes.LifeSupp, new LifeSuppSystemType(45f));
+ systems.Add(SystemTypes.Ventilation, new VentilationSystemType());
+ systems.Add(SystemTypes.Decontamination, new DeconSystemType());
+ systems.Add(SystemTypes.Decontamination2, new DeconSystemType());
}
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerPolusShipStatus.cs b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerPolusShipStatus.cs
index c389838..cc0988b 100644
--- a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerPolusShipStatus.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerPolusShipStatus.cs
@@ -10,8 +10,9 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
{
internal class InnerPolusShipStatus : InnerShipStatus, IInnerPolusShipStatus
{
- public InnerPolusShipStatus(ICustomMessageManager customMessageManager, Game game) : base(customMessageManager, game, MapTypes.Polus)
+ public InnerPolusShipStatus(ICustomMessageManager customMessageManager, ICustomMessageManager customSystemManager, Game game) : base(customMessageManager, customSystemManager, game, MapTypes.Polus)
{
+ InitializeSystems();
}
public override Vector2 GetSpawnLocation(InnerPlayerControl player, int numPlayers, bool initialSpawn)
@@ -37,10 +38,13 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
{
base.AddSystems(systems);
- systems.Add(SystemTypes.Doors, new DoorsSystemType(Doors));
+ systems.Add(SystemTypes.Doors, new DoorsSystemType(Doors, Data.Doors));
systems.Add(SystemTypes.Comms, new HudOverrideSystemType());
systems.Add(SystemTypes.Security, new SecurityCameraSystemType());
- systems.Add(SystemTypes.Laboratory, new ReactorSystemType());
+ systems.Add(SystemTypes.Ventilation, new VentilationSystemType());
+ systems.Add(SystemTypes.Laboratory, new ReactorSystemType(60f, SystemTypes.Laboratory));
+ systems.Add(SystemTypes.Decontamination, new DeconSystemType());
+ systems.Add(SystemTypes.Decontamination2, new DeconSystemType());
}
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerShipStatus.cs b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerShipStatus.cs
index f54089e..e099147 100644
--- a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerShipStatus.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerShipStatus.cs
@@ -8,6 +8,7 @@ using Empostor.Api.Innersloth.Maps;
using Empostor.Api.Net;
using Empostor.Api.Net.Custom;
using Empostor.Api.Net.Inner;
+using Empostor.Api.Net.Inner.Objects;
using Empostor.Api.Net.Inner.Objects.ShipStatus;
using Empostor.Api.Net.Messages.Rpcs;
using Empostor.Server.Net.Inner.Objects.Systems;
@@ -18,10 +19,13 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
{
internal abstract class InnerShipStatus : InnerNetObject, IInnerShipStatus
{
+ private readonly ICustomMessageManager _customSystemManager;
private readonly Dictionary _systems = new Dictionary();
- protected InnerShipStatus(ICustomMessageManager customMessageManager, Game game, MapTypes mapType) : base(customMessageManager, game)
+ protected InnerShipStatus(ICustomMessageManager customMessageManager, ICustomMessageManager customSystemManager, Game game, MapTypes mapType) : base(customMessageManager, game)
{
+ _customSystemManager = customSystemManager;
+
Components.Add(this);
MapType = mapType;
@@ -35,22 +39,21 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
public Dictionary Doors { get; }
- internal override ValueTask OnSpawnAsync()
- {
- for (var i = 0; i < Doors.Count; i++)
- {
- Doors.Add(i, false);
- }
-
- AddSystems(_systems);
- _systems.Add(SystemTypes.Sabotage, new SabotageSystemType(_systems.Values.OfType().ToArray()));
-
- return base.OnSpawnAsync();
- }
-
public override ValueTask SerializeAsync(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ var result = false;
+ foreach (var systemType in SystemTypeHelpers.AllTypes)
+ {
+ if (_systems.TryGetValue(systemType, out var value))
+ {
+ result = true;
+ writer.StartMessage((byte)systemType);
+ value.Serialize(writer, initialState);
+ writer.EndMessage();
+ }
+ }
+
+ return new ValueTask(result);
}
public override async ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
@@ -62,12 +65,16 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
while (reader.Position < reader.Length)
{
- var messageReader = reader.ReadMessage();
+ using var messageReader = reader.ReadMessage();
var type = (SystemTypes)messageReader.Tag;
if (_systems.TryGetValue(type, out var value))
{
value.Deserialize(messageReader, initialState);
}
+ else if (_customSystemManager.TryGet(messageReader.Tag, out var customSystem))
+ {
+ await customSystem.DeserializeAsync(this, sender, target, messageReader, initialState);
+ }
}
}
@@ -84,6 +91,7 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
}
Rpc27CloseDoorsOfType.Deserialize(reader, out var systemType);
+ CloseDoorsOfType(systemType);
break;
}
@@ -94,8 +102,20 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
return false;
}
- // TODO: properly deserialize this RPC
- // Rpc35UpdateSystem.Deserialize(reader, Game, out var systemType, out var playerControl, out var sequenceId, out var state, out var ventId);
+ Rpc35UpdateSystem.Deserialize(reader, Game, out var systemType, out var playerControl, out var payload);
+ if (_systems.TryGetValue(systemType, out var system))
+ {
+ system.UpdateSystem(playerControl, payload);
+ }
+ else if (_customSystemManager.TryGet((byte)systemType, out var customSystem))
+ {
+ return await customSystem.HandleUpdateSystemAsync(this, playerControl, sender, target, payload);
+ }
+ else if (await sender.Client.ReportCheatAsync(call, CheatCategory.ProtocolExtension, $"Client sent unregistered system type {(byte)systemType}"))
+ {
+ return false;
+ }
+
break;
}
@@ -143,6 +163,58 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
systems.Add(SystemTypes.MedBay, new MedScanSystem());
}
+ protected void InitializeSystems()
+ {
+ for (var i = 0; i < Data.Doors.Count; i++)
+ {
+ Doors[i] = true;
+ }
+
+ AddSystems(_systems);
+ _systems.Add(SystemTypes.Sabotage, new SabotageSystemType(_systems.Values.OfType().ToArray(), UpdateSystem));
+ }
+
+ protected void UpdateSystem(IInnerPlayerControl? playerControl, SystemTypes systemType, byte amount)
+ {
+ if (!_systems.TryGetValue(systemType, out var system))
+ {
+ return;
+ }
+
+ switch (system)
+ {
+ case SwitchSystem switchSystem:
+ switchSystem.UpdateSystem(playerControl, amount);
+ break;
+ case ReactorSystemType reactorSystem:
+ reactorSystem.UpdateSystem(playerControl, amount);
+ break;
+ case LifeSuppSystemType lifeSuppSystem:
+ lifeSuppSystem.UpdateSystem(playerControl, amount);
+ break;
+ case HudOverrideSystemType hudOverrideSystem:
+ hudOverrideSystem.UpdateSystem(playerControl, amount);
+ break;
+ case HqHudSystemType hqHudSystem:
+ hqHudSystem.UpdateSystem(playerControl, amount);
+ break;
+ case HeliSabotageSystemType heliSabotageSystem:
+ heliSabotageSystem.UpdateSystem(playerControl, amount);
+ break;
+ case MushroomMixupSabotageSystemType mushroomMixupSabotageSystem:
+ mushroomMixupSabotageSystem.UpdateSystem(playerControl, amount);
+ break;
+ }
+ }
+
+ protected void CloseDoorsOfType(SystemTypes room)
+ {
+ if (_systems.TryGetValue(SystemTypes.Doors, out var system) && system is IDoorSystem doorSystem)
+ {
+ doorSystem.CloseDoorsOfType(room);
+ }
+ }
+
private static Vector2 Rotate(Vector2 self, float degrees)
{
var f = 0.017453292f * degrees;
diff --git a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerSkeldShipStatus.cs b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerSkeldShipStatus.cs
index cb14010..fc74aa9 100644
--- a/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerSkeldShipStatus.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/ShipStatus/InnerSkeldShipStatus.cs
@@ -9,19 +9,21 @@ namespace Empostor.Server.Net.Inner.Objects.ShipStatus
{
internal class InnerSkeldShipStatus : InnerShipStatus, IInnerSkeldShipStatus
{
- public InnerSkeldShipStatus(ICustomMessageManager customMessageManager, Game game) : base(customMessageManager, game, MapTypes.Skeld)
+ public InnerSkeldShipStatus(ICustomMessageManager customMessageManager, ICustomMessageManager customSystemManager, Game game) : base(customMessageManager, customSystemManager, game, MapTypes.Skeld)
{
+ InitializeSystems();
}
protected override void AddSystems(Dictionary systems)
{
base.AddSystems(systems);
- systems.Add(SystemTypes.Doors, new AutoDoorsSystemType(Doors));
+ systems.Add(SystemTypes.Doors, new AutoDoorsSystemType(Doors, Data.Doors));
systems.Add(SystemTypes.Comms, new HudOverrideSystemType());
systems.Add(SystemTypes.Security, new SecurityCameraSystemType());
- systems.Add(SystemTypes.Reactor, new ReactorSystemType());
- systems.Add(SystemTypes.LifeSupp, new LifeSuppSystemType());
+ systems.Add(SystemTypes.Reactor, new ReactorSystemType(30f, SystemTypes.Reactor));
+ systems.Add(SystemTypes.LifeSupp, new LifeSuppSystemType(30f));
+ systems.Add(SystemTypes.Ventilation, new VentilationSystemType());
}
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ISystemType.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ISystemType.cs
index 3830b49..436911d 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ISystemType.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ISystemType.cs
@@ -1,9 +1,13 @@
-namespace Empostor.Server.Net.Inner.Objects.Systems
+using Empostor.Api.Net.Inner.Objects;
+
+namespace Empostor.Server.Net.Inner.Objects.Systems
{
public interface ISystemType
{
void Serialize(IMessageWriter writer, bool initialState);
void Deserialize(IMessageReader reader, bool initialState);
+
+ void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader);
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/SequenceBuffer.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/SequenceBuffer.cs
new file mode 100644
index 0000000..0acf3c3
--- /dev/null
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/SequenceBuffer.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+
+namespace Empostor.Server.Net.Inner.Objects.Systems
+{
+ internal sealed class SequenceBuffer
+ {
+ private readonly List _buffer = new();
+
+ public SequenceBuffer(ushort sidStart = 0)
+ {
+ LastSid = sidStart;
+ }
+
+ public ushort LastSid { get; set; }
+
+ public void Add(ushort sid, T info)
+ {
+ _buffer.Add(new SequencedData(sid, info));
+ }
+
+ public void BumpSid()
+ {
+ LastSid++;
+ }
+
+ public bool IsInvalidSid(ushort sid)
+ {
+ return !SidGreaterThan(sid, LastSid);
+ }
+
+ public bool IsNextSid(ushort sid)
+ {
+ return sid == (ushort)(LastSid + 1);
+ }
+
+ public IEnumerable SubsequentObjs()
+ {
+ _buffer.Sort();
+ while (_buffer.Count > 0 && IsNextSid(_buffer[0].Sid))
+ {
+ var result = _buffer[0].Data;
+ _buffer.RemoveAt(0);
+ yield return result;
+ }
+ }
+
+ private static bool SidGreaterThan(ushort newSid, ushort prevSid)
+ {
+ var num = (ushort)(prevSid + (uint)short.MaxValue);
+
+ return (int)prevSid < (int)num
+ ? newSid > prevSid && newSid <= num
+ : newSid > prevSid || newSid <= num;
+ }
+
+ private readonly struct SequencedData : IComparable
+ {
+ public SequencedData(ushort sid, T data)
+ {
+ Sid = sid;
+ Data = data;
+ }
+
+ public ushort Sid { get; }
+
+ public T Data { get; }
+
+ public int CompareTo(SequencedData other)
+ {
+ return Sid.CompareTo(other.Sid);
+ }
+ }
+ }
+}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/AutoDoorsSystemType.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/AutoDoorsSystemType.cs
index 1eba826..51efbf3 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/AutoDoorsSystemType.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/AutoDoorsSystemType.cs
@@ -1,20 +1,44 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
+using System.Linq;
+using Empostor.Api.Innersloth.Maps;
+using Empostor.Api.Net.Inner.Objects;
namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
{
- public class AutoDoorsSystemType : ISystemType
+ public class AutoDoorsSystemType : ISystemType, IDoorSystem
{
private readonly Dictionary _doors;
+ private readonly IReadOnlyDictionary _doorData;
+ private uint _dirtyBits;
- public AutoDoorsSystemType(Dictionary doors)
+ public AutoDoorsSystemType(Dictionary doors, IReadOnlyDictionary doorData)
{
_doors = doors;
+ _doorData = doorData;
}
+ public bool IsActive => _doors.Values.Any(open => !open);
+
public void Serialize(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ if (initialState)
+ {
+ for (var i = 0; i < _doors.Count; i++)
+ {
+ writer.Write(_doors[i]);
+ }
+
+ return;
+ }
+
+ writer.WritePacked(_dirtyBits);
+ for (var i = 0; i < _doors.Count; i++)
+ {
+ if ((_dirtyBits & (uint)(1 << i)) != 0)
+ {
+ writer.Write(_doors[i]);
+ }
+ }
}
public void Deserialize(IMessageReader reader, bool initialState)
@@ -32,12 +56,28 @@ namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
for (var i = 0; i < _doors.Count; i++)
{
- if ((num & 1 << i) != 0)
+ if ((num & (uint)(1 << i)) != 0)
{
_doors[i] = reader.ReadBoolean();
}
}
}
}
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ }
+
+ public void CloseDoorsOfType(SystemTypes room)
+ {
+ for (var i = 0; i < _doors.Count; i++)
+ {
+ if (_doorData.TryGetValue(i, out var door) && door.Room == room)
+ {
+ _doors[i] = false;
+ _dirtyBits |= (uint)(1 << i);
+ }
+ }
+ }
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/DeconSystemType.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/DeconSystemType.cs
new file mode 100644
index 0000000..7516644
--- /dev/null
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/DeconSystemType.cs
@@ -0,0 +1,65 @@
+using Empostor.Api.Net.Inner.Objects;
+
+namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
+{
+ public class DeconSystemType : ISystemType
+ {
+ private const byte HeadUpCommand = 1;
+ private const byte HeadDownCommand = 2;
+ private const byte HeadUpInsideCommand = 3;
+ private const byte HeadDownInsideCommand = 4;
+
+ public enum States : byte
+ {
+ Idle = 0,
+ Enter = 1,
+ Closed = 2,
+ Exit = 4,
+ HeadingUp = 8,
+ }
+
+ public byte Timer { get; private set; }
+
+ public States CurState { get; private set; }
+
+ public void Serialize(IMessageWriter writer, bool initialState)
+ {
+ writer.Write(Timer);
+ writer.Write((byte)CurState);
+ }
+
+ public void Deserialize(IMessageReader reader, bool initialState)
+ {
+ Timer = reader.ReadByte();
+ CurState = (States)reader.ReadByte();
+ }
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ if (CurState != States.Idle)
+ {
+ return;
+ }
+
+ switch (reader.ReadByte())
+ {
+ case HeadUpCommand:
+ CurState = States.Enter | States.HeadingUp;
+ Timer = 3;
+ break;
+ case HeadDownCommand:
+ CurState = States.Enter;
+ Timer = 3;
+ break;
+ case HeadUpInsideCommand:
+ CurState = States.Exit | States.HeadingUp;
+ Timer = 3;
+ break;
+ case HeadDownInsideCommand:
+ CurState = States.Exit;
+ Timer = 3;
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs
index 4f117e4..51127f8 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs
@@ -1,21 +1,36 @@
-using System;
using System.Collections.Generic;
+using Empostor.Api.Innersloth.Maps;
+using Empostor.Api.Net.Inner.Objects;
namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
{
- public class DoorsSystemType : ISystemType
+ public class DoorsSystemType : ISystemType, IDoorSystem
{
- private readonly Dictionary _timers = new Dictionary();
+ private readonly Dictionary _timers = new();
private readonly Dictionary _doors;
+ private readonly IReadOnlyDictionary _doorData;
- public DoorsSystemType(Dictionary doors)
+ public DoorsSystemType(Dictionary doors, IReadOnlyDictionary doorData)
{
_doors = doors;
+ _doorData = doorData;
}
+ public bool IsActive => false;
+
public void Serialize(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ writer.Write((byte)_timers.Count);
+ foreach (var timer in _timers)
+ {
+ writer.Write((byte)timer.Key);
+ writer.Write(timer.Value);
+ }
+
+ for (var i = 0; i < _doors.Count; i++)
+ {
+ writer.Write(_doors[i]);
+ }
}
public void Deserialize(IMessageReader reader, bool initialState)
@@ -34,5 +49,28 @@ namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
_doors[j] = reader.ReadBoolean();
}
}
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ var value = reader.ReadByte();
+ var doorId = value & 0x1f;
+ if ((value & 0xc0) == 0x40 && _doors.ContainsKey(doorId))
+ {
+ _doors[doorId] = true;
+ }
+ }
+
+ public void CloseDoorsOfType(SystemTypes room)
+ {
+ _timers[room] = 30f;
+
+ for (var i = 0; i < _doors.Count; i++)
+ {
+ if (_doorData.TryGetValue(i, out var door) && door.Room == room)
+ {
+ _doors[i] = false;
+ }
+ }
+ }
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/ElectricalDoors.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/ElectricalDoors.cs
index 1c45177..ca2e767 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/ElectricalDoors.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/ElectricalDoors.cs
@@ -1,29 +1,23 @@
-using System;
-using System.Collections.Generic;
+using Empostor.Api.Net.Inner.Objects;
namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
{
public class ElectricalDoors : ISystemType
{
- private readonly Dictionary _doors;
-
- public ElectricalDoors(Dictionary doors)
- {
- _doors = doors;
- }
+ private uint _state;
public void Serialize(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ writer.Write(_state);
}
public void Deserialize(IMessageReader reader, bool initialState)
{
- var num = reader.ReadUInt32();
- for (var i = 0; i < _doors.Count; i++)
- {
- _doors[i] = (num & (ulong)(1L << (i & 31))) > 0UL;
- }
+ _state = reader.ReadUInt32();
+ }
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
}
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/HeliSabotageSystemType.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/HeliSabotageSystemType.cs
index c5da18f..5368c8d 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/HeliSabotageSystemType.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/HeliSabotageSystemType.cs
@@ -1,52 +1,98 @@
-using System;
using System.Collections.Generic;
+using Empostor.Api.Net.Inner.Objects;
namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
{
public class HeliSabotageSystemType : ISystemType, IActivatable
{
+ private readonly HashSet _activeConsoles = new();
+ private readonly HashSet _completedConsoles = new();
+
public HeliSabotageSystemType()
{
Countdown = 10000f;
- ActiveConsoles = new HashSet>();
- CompletedConsoles = new HashSet();
+ _completedConsoles.Add(0);
+ _completedConsoles.Add(1);
}
public float Countdown { get; private set; }
public float Timer { get; private set; }
- public HashSet> ActiveConsoles { get; }
-
- public HashSet CompletedConsoles { get; }
-
- public bool IsActive => Countdown < 10000.0;
+ public bool IsActive => _completedConsoles.Count < 2;
public void Serialize(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ writer.Write(Countdown);
+ writer.Write(Timer);
+
+ writer.WritePacked(_activeConsoles.Count);
+ foreach (var activeConsole in _activeConsoles)
+ {
+ writer.Write(activeConsole.PlayerId);
+ writer.Write(activeConsole.ConsoleId);
+ }
+
+ writer.WritePacked(_completedConsoles.Count);
+ foreach (var completedConsole in _completedConsoles)
+ {
+ writer.Write(completedConsole);
+ }
}
public void Deserialize(IMessageReader reader, bool initialState)
{
Countdown = reader.ReadSingle();
Timer = reader.ReadSingle();
- ActiveConsoles.Clear(); // TODO: Thread safety
- CompletedConsoles.Clear(); // TODO: Thread safety
+ _activeConsoles.Clear();
+ _completedConsoles.Clear();
var activeCount = reader.ReadPackedUInt32();
for (var i = 0; i < activeCount; i++)
{
- ActiveConsoles.Add(new Tuple(reader.ReadByte(), reader.ReadByte()));
+ _activeConsoles.Add(new ActiveConsoleData(reader.ReadByte(), reader.ReadByte()));
}
var completedCount = reader.ReadPackedUInt32();
for (var i = 0; i < completedCount; i++)
{
- CompletedConsoles.Add(reader.ReadByte());
+ _completedConsoles.Add(reader.ReadByte());
}
}
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ UpdateSystem(playerControl, reader.ReadByte());
+ }
+
+ internal void UpdateSystem(IInnerPlayerControl? playerControl, byte amount)
+ {
+ var value = amount;
+ var consoleId = (byte)(value & 0x0f);
+
+ switch (value & 0xf0)
+ {
+ case 0x80:
+ Timer = -1f;
+ Countdown = 90f;
+ _completedConsoles.Clear();
+ _activeConsoles.Clear();
+ break;
+ case 0x40 when playerControl != null:
+ _activeConsoles.Add(new ActiveConsoleData(playerControl.PlayerId, consoleId));
+ break;
+ case 0x20 when playerControl != null:
+ _activeConsoles.Remove(new ActiveConsoleData(playerControl.PlayerId, consoleId));
+ break;
+ case 0x10:
+ Timer = 10f;
+ _completedConsoles.Add(consoleId);
+ break;
+ }
+ }
+
+ private readonly record struct ActiveConsoleData(byte PlayerId, byte ConsoleId);
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/HqHudSystemType.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/HqHudSystemType.cs
new file mode 100644
index 0000000..f75df45
--- /dev/null
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/HqHudSystemType.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Empostor.Api.Net.Inner.Objects;
+
+namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
+{
+ public class HqHudSystemType : ISystemType, IActivatable
+ {
+ private readonly HashSet> _activeConsoles = new();
+ private readonly HashSet _completedConsoles = new();
+
+ public HqHudSystemType()
+ {
+ _completedConsoles.Add(0);
+ _completedConsoles.Add(1);
+ }
+
+ public float Timer { get; private set; }
+
+ public bool IsActive => _completedConsoles.Count < 2;
+
+ public float NumComplete => _completedConsoles.Count;
+
+ public float PercentActive => Timer / 10f;
+
+ public void Serialize(IMessageWriter writer, bool initialState)
+ {
+ writer.WritePacked(_activeConsoles.Count);
+ foreach (var activeConsole in _activeConsoles)
+ {
+ writer.Write(activeConsole.Item1);
+ writer.Write(activeConsole.Item2);
+ }
+
+ writer.WritePacked(_completedConsoles.Count);
+ foreach (var completedConsole in _completedConsoles)
+ {
+ writer.Write(completedConsole);
+ }
+ }
+
+ public void Deserialize(IMessageReader reader, bool initialState)
+ {
+ _activeConsoles.Clear();
+ var activeCount = reader.ReadPackedInt32();
+ for (var i = 0; i < activeCount; i++)
+ {
+ _activeConsoles.Add(new Tuple(reader.ReadByte(), reader.ReadByte()));
+ }
+
+ _completedConsoles.Clear();
+ var completedCount = reader.ReadPackedInt32();
+ for (var i = 0; i < completedCount; i++)
+ {
+ _completedConsoles.Add(reader.ReadByte());
+ }
+ }
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ UpdateSystem(playerControl, reader.ReadByte());
+ }
+
+ internal void UpdateSystem(IInnerPlayerControl? playerControl, byte amount)
+ {
+ var value = amount;
+ var consoleId = (byte)(value & 0x0f);
+
+ switch (value & 0xf0)
+ {
+ case 0x80:
+ Timer = -1f;
+ _completedConsoles.Clear();
+ _activeConsoles.Clear();
+ break;
+ case 0x40 when playerControl != null:
+ _activeConsoles.Add(new Tuple(playerControl.PlayerId, consoleId));
+ break;
+ case 0x20 when playerControl != null:
+ _activeConsoles.Remove(new Tuple(playerControl.PlayerId, consoleId));
+ break;
+ case 0x10:
+ Timer = 10f;
+ _completedConsoles.Add(consoleId);
+ break;
+ }
+ }
+
+ internal bool IsConsoleActive(int consoleId)
+ {
+ return _activeConsoles.Any(x => x.Item2 == (byte)consoleId);
+ }
+
+ internal bool IsConsoleOkay(int consoleId)
+ {
+ return _completedConsoles.Contains((byte)consoleId);
+ }
+ }
+}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs
index 10fb8f8..42fea50 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs
@@ -1,4 +1,4 @@
-using System;
+using Empostor.Api.Net.Inner.Objects;
namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
{
@@ -8,12 +8,22 @@ namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
public void Serialize(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ writer.Write(IsActive);
}
public void Deserialize(IMessageReader reader, bool initialState)
{
IsActive = reader.ReadBoolean();
}
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ UpdateSystem(playerControl, reader.ReadByte());
+ }
+
+ internal void UpdateSystem(IInnerPlayerControl? playerControl, byte amount)
+ {
+ IsActive = (amount & 0x80) != 0;
+ }
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/IDoorSystem.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/IDoorSystem.cs
new file mode 100644
index 0000000..7f8ee05
--- /dev/null
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/IDoorSystem.cs
@@ -0,0 +1,7 @@
+namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
+{
+ public interface IDoorSystem : IActivatable
+ {
+ void CloseDoorsOfType(SystemTypes room);
+ }
+}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs
index 4c3884f..9349a21 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs
@@ -1,12 +1,15 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
+using Empostor.Api.Net.Inner.Objects;
namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
{
public class LifeSuppSystemType : ISystemType, IActivatable
{
- public LifeSuppSystemType()
+ private readonly float _lifeSuppDuration;
+
+ public LifeSuppSystemType(float lifeSuppDuration = 30f)
{
+ _lifeSuppDuration = lifeSuppDuration;
Countdown = 10000f;
CompletedConsoles = new HashSet();
}
@@ -15,11 +18,16 @@ namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
public HashSet CompletedConsoles { get; }
- public bool IsActive => Countdown < 10000.0;
+ public bool IsActive => Countdown < 10000.0f;
public void Serialize(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ writer.Write(Countdown);
+ writer.WritePacked(CompletedConsoles.Count);
+ foreach (var consoleId in CompletedConsoles)
+ {
+ writer.WritePacked(consoleId);
+ }
}
public void Deserialize(IMessageReader reader, bool initialState)
@@ -31,7 +39,7 @@ namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
return;
}
- CompletedConsoles.Clear(); // TODO: Thread safety
+ CompletedConsoles.Clear();
var num = reader.ReadPackedInt32();
@@ -40,5 +48,34 @@ namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
CompletedConsoles.Add(reader.ReadPackedInt32());
}
}
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ UpdateSystem(playerControl, reader.ReadByte());
+ }
+
+ internal void UpdateSystem(IInnerPlayerControl? playerControl, byte amount)
+ {
+ var value = amount;
+ var consoleId = value & 3;
+
+ if (value == 0x80 && !IsActive)
+ {
+ Countdown = _lifeSuppDuration;
+ CompletedConsoles.Clear();
+ }
+ else if (value == 0x10)
+ {
+ Countdown = 10000f;
+ }
+ else if ((value & 0x40) != 0)
+ {
+ CompletedConsoles.Add(consoleId);
+ if (CompletedConsoles.Count >= 2)
+ {
+ Countdown = 10000f;
+ }
+ }
+ }
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs
index c8d894a..ffff72c 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs
@@ -1,5 +1,5 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
+using Empostor.Api.Net.Inner.Objects;
namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
{
@@ -12,9 +12,15 @@ namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
public List UsersList { get; }
+ public byte CurrentUser { get; private set; } = byte.MaxValue;
+
public void Serialize(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ writer.WritePacked(UsersList.Count);
+ foreach (var user in UsersList)
+ {
+ writer.Write(user);
+ }
}
public void Deserialize(IMessageReader reader, bool initialState)
@@ -28,5 +34,26 @@ namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
UsersList.Add(reader.ReadByte());
}
}
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ var value = reader.ReadByte();
+ var playerId = (byte)(value & 0x1f);
+ if ((value & 0x80) != 0)
+ {
+ if (!UsersList.Contains(playerId))
+ {
+ UsersList.Add(playerId);
+ }
+ }
+ else if ((value & 0x40) != 0)
+ {
+ UsersList.RemoveAll(x => x == playerId);
+ if (CurrentUser == playerId)
+ {
+ CurrentUser = byte.MaxValue;
+ }
+ }
+ }
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/MovingPlatformBehaviour.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/MovingPlatformBehaviour.cs
index eb2f82c..8e0eada 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/MovingPlatformBehaviour.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/MovingPlatformBehaviour.cs
@@ -1,21 +1,55 @@
-using System;
+using Empostor.Api.Net.Inner.Objects;
namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
{
- public class MovingPlatformBehaviour : ISystemType, IActivatable
+ public class MovingPlatformBehaviour : ISystemType
{
- public bool IsActive { get; private set; }
+ private byte _useId;
+
+ public uint TargetNetId { get; private set; } = uint.MaxValue;
+
+ public bool IsLeft { get; private set; } = true;
+
+ public bool InUse => TargetNetId != uint.MaxValue;
public void Serialize(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ _useId++;
+ writer.Write(_useId);
+ writer.Write(TargetNetId);
+ writer.Write(IsLeft);
}
public void Deserialize(IMessageReader reader, bool initialState)
{
- var sid = reader.ReadByte();
- var targetId = reader.ReadUInt32();
- var isLeft = reader.ReadBoolean();
+ if (initialState)
+ {
+ _useId = reader.ReadByte();
+ TargetNetId = reader.ReadUInt32();
+ IsLeft = reader.ReadBoolean();
+ return;
+ }
+
+ var newSid = reader.ReadByte();
+ if (SidGreaterThan(newSid, _useId))
+ {
+ _useId = newSid;
+ TargetNetId = reader.ReadUInt32();
+ IsLeft = reader.ReadBoolean();
+ }
+ }
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ }
+
+ private static bool SidGreaterThan(byte newSid, byte prevSid)
+ {
+ var num = (byte)(prevSid + sbyte.MaxValue);
+
+ return prevSid < num
+ ? newSid > prevSid && newSid <= num
+ : newSid > prevSid || newSid <= num;
}
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/MushroomMixupSabotageSystemType.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/MushroomMixupSabotageSystemType.cs
index 0ef5512..bc4f234 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/MushroomMixupSabotageSystemType.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/MushroomMixupSabotageSystemType.cs
@@ -1,8 +1,9 @@
using System.Collections.Generic;
+using Empostor.Api.Net.Inner.Objects;
namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus;
-public class MushroomMixupSabotageSystemType : ISystemType
+public class MushroomMixupSabotageSystemType : ISystemType, IActivatable
{
private readonly Dictionary _currentMixups = new();
private State _currentState;
@@ -15,6 +16,8 @@ public class MushroomMixupSabotageSystemType : ISystemType
IdleButMixedUp,
}
+ public bool IsActive => _currentSecondsUntilHeal > 0f;
+
public void Serialize(IMessageWriter writer, bool initialState)
{
writer.Write((byte)_currentState);
@@ -26,6 +29,11 @@ public class MushroomMixupSabotageSystemType : ISystemType
writer.Write(playerId);
outfit.Serialize(writer);
}
+
+ if (_currentState == State.JustTriggered)
+ {
+ _currentState = State.IdleButMixedUp;
+ }
}
public void Deserialize(IMessageReader reader, bool initialState)
@@ -40,7 +48,22 @@ public class MushroomMixupSabotageSystemType : ISystemType
var playerId = reader.ReadByte();
var outfit = CondensedOutfit.Deserialize(reader);
- _currentMixups.Add(playerId, outfit);
+ _currentMixups[playerId] = outfit;
+ }
+ }
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ UpdateSystem(playerControl, reader.ReadByte());
+ }
+
+ internal void UpdateSystem(IInnerPlayerControl? playerControl, byte amount)
+ {
+ var operation = amount;
+ if (operation == 1)
+ {
+ _currentMixups.Clear();
+ _currentState = State.JustTriggered;
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs
index 00ee799..6ce1799 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs
@@ -1,31 +1,65 @@
using System;
using System.Collections.Generic;
+using System.Linq;
+using Empostor.Api.Net.Inner.Objects;
namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
{
public class ReactorSystemType : ISystemType, IActivatable
{
- public ReactorSystemType()
+ private readonly float _reactorDuration;
+
+ public ReactorSystemType(float reactorDuration = 30f, SystemTypes system = SystemTypes.Reactor)
{
+ _reactorDuration = reactorDuration;
+ System = system;
Countdown = 10000f;
UserConsolePairs = new HashSet>();
}
+ public SystemTypes System { get; }
+
public float Countdown { get; private set; }
public HashSet> UserConsolePairs { get; }
- public bool IsActive => Countdown < 10000.0;
+ public bool IsActive => Countdown < 10000.0f;
+
+ public int UserCount
+ {
+ get
+ {
+ var result = 0;
+ var consoles = 0;
+ foreach (var pair in UserConsolePairs)
+ {
+ var bit = 1 << pair.Item2;
+ if ((bit & consoles) == 0)
+ {
+ result++;
+ consoles |= bit;
+ }
+ }
+
+ return result;
+ }
+ }
public void Serialize(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ writer.Write(Countdown);
+ writer.WritePacked(UserConsolePairs.Count);
+ foreach (var pair in UserConsolePairs)
+ {
+ writer.Write(pair.Item1);
+ writer.Write(pair.Item2);
+ }
}
public void Deserialize(IMessageReader reader, bool initialState)
{
Countdown = reader.ReadSingle();
- UserConsolePairs.Clear(); // TODO: Thread safety
+ UserConsolePairs.Clear();
var count = reader.ReadPackedInt32();
@@ -34,5 +68,43 @@ namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
UserConsolePairs.Add(new Tuple(reader.ReadByte(), reader.ReadByte()));
}
}
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ UpdateSystem(playerControl, reader.ReadByte());
+ }
+
+ internal void UpdateSystem(IInnerPlayerControl? playerControl, byte amount)
+ {
+ var value = amount;
+ var consoleId = (byte)(value & 3);
+
+ if (value == 0x80 && !IsActive)
+ {
+ Countdown = _reactorDuration;
+ UserConsolePairs.Clear();
+ }
+ else if (value == 0x10)
+ {
+ Countdown = 10000f;
+ }
+ else if ((value & 0x40) != 0 && playerControl != null)
+ {
+ UserConsolePairs.Add(new Tuple(playerControl.PlayerId, consoleId));
+ if (UserCount >= 2)
+ {
+ Countdown = 10000f;
+ }
+ }
+ else if ((value & 0x20) != 0 && playerControl != null)
+ {
+ UserConsolePairs.Remove(new Tuple(playerControl.PlayerId, consoleId));
+ }
+ }
+
+ public bool GetConsoleComplete(int consoleId)
+ {
+ return UserConsolePairs.Any(x => x.Item2 == consoleId);
+ }
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs
index 0b8ec71..15dbe5c 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs
@@ -1,26 +1,69 @@
using System;
+using System.Linq;
+using Empostor.Api.Net.Inner.Objects;
namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
{
public class SabotageSystemType : ISystemType
{
private readonly IActivatable[] _specials;
+ private readonly Action _updateSystem;
- public SabotageSystemType(IActivatable[] specials)
+ public SabotageSystemType(IActivatable[] specials, Action updateSystem)
{
- _specials = specials;
+ _specials = specials.Where(x => x is not IDoorSystem).ToArray();
+ _updateSystem = updateSystem;
}
- public float Timer { get; set; }
+ public float Timer { get; private set; }
+
+ public bool AnyActive => _specials.Any(x => x.IsActive);
public void Serialize(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ writer.Write(Timer);
}
public void Deserialize(IMessageReader reader, bool initialState)
{
Timer = reader.ReadSingle();
}
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ if (Timer > 0f)
+ {
+ return;
+ }
+
+ var systemType = (SystemTypes)reader.ReadByte();
+ switch (systemType)
+ {
+ case SystemTypes.Laboratory:
+ case SystemTypes.Reactor:
+ case SystemTypes.HeliSabotage:
+ case SystemTypes.LifeSupp:
+ case SystemTypes.Comms:
+ _updateSystem(playerControl, systemType, 0x80);
+ break;
+ case SystemTypes.Electrical:
+ var value = (byte)4;
+ for (var i = 0; i < 5; i++)
+ {
+ if (Random.Shared.Next(2) == 0)
+ {
+ value |= (byte)(1 << i);
+ }
+ }
+
+ _updateSystem(playerControl, systemType, (byte)(value | 0x80));
+ break;
+ case SystemTypes.MushroomMixupSabotage:
+ _updateSystem(playerControl, systemType, 1);
+ break;
+ }
+
+ Timer = 30f;
+ }
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs
index 9f27e31..3423964 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs
@@ -1,19 +1,48 @@
-using System;
+using System.Collections.Generic;
+using Empostor.Api.Net.Inner.Objects;
namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
{
public class SecurityCameraSystemType : ISystemType
{
- public byte InUse { get; internal set; }
+ private readonly HashSet _playersUsing = new();
+
+ public bool InUse => _playersUsing.Count > 0;
public void Serialize(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ writer.WritePacked(_playersUsing.Count);
+ foreach (var playerId in _playersUsing)
+ {
+ writer.Write(playerId);
+ }
}
public void Deserialize(IMessageReader reader, bool initialState)
{
- InUse = reader.ReadByte();
+ _playersUsing.Clear();
+ var num = reader.ReadPackedInt32();
+ for (var i = 0; i < num; i++)
+ {
+ _playersUsing.Add(reader.ReadByte());
+ }
+ }
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ if (playerControl == null)
+ {
+ return;
+ }
+
+ if (reader.ReadByte() == 1)
+ {
+ _playersUsing.Add(playerControl.PlayerId);
+ }
+ else
+ {
+ _playersUsing.Remove(playerControl.PlayerId);
+ }
}
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs
index c9daf99..eeaf582 100644
--- a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs
@@ -1,4 +1,4 @@
-using System;
+using Empostor.Api.Net.Inner.Objects;
namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
{
@@ -10,11 +10,13 @@ namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
public byte Value { get; set; } = byte.MaxValue;
- public bool IsActive { get; }
+ public bool IsActive => ExpectedSwitches != ActualSwitches;
public void Serialize(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ writer.Write(ExpectedSwitches);
+ writer.Write(ActualSwitches);
+ writer.Write(Value);
}
public void Deserialize(IMessageReader reader, bool initialState)
@@ -23,5 +25,23 @@ namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
ActualSwitches = reader.ReadByte();
Value = reader.ReadByte();
}
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ UpdateSystem(playerControl, reader.ReadByte());
+ }
+
+ internal void UpdateSystem(IInnerPlayerControl? playerControl, byte amount)
+ {
+ var value = amount;
+ if ((value & 0x80) != 0)
+ {
+ ActualSwitches ^= (byte)(value & 0x1f);
+ }
+ else
+ {
+ ActualSwitches ^= (byte)(1 << value);
+ }
+ }
}
}
diff --git a/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/VentilationSystemType.cs b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/VentilationSystemType.cs
new file mode 100644
index 0000000..34f36d3
--- /dev/null
+++ b/src/Empostor.Server/Net/Inner/Objects/Systems/ShipStatus/VentilationSystemType.cs
@@ -0,0 +1,152 @@
+using System.Collections.Generic;
+using System.Linq;
+using Empostor.Api.Net.Inner.Objects;
+
+namespace Empostor.Server.Net.Inner.Objects.Systems.ShipStatus
+{
+ public class VentilationSystemType : ISystemType
+ {
+ private readonly Dictionary> _sequenceBuffers = new();
+ private readonly Dictionary _playersCleaningVents = new();
+ private readonly Dictionary _playersInsideVents = new();
+
+ public enum Operation : byte
+ {
+ StartCleaning,
+ StopCleaning,
+ Enter,
+ Exit,
+ Move,
+ BootImpostors,
+ }
+
+ public void Serialize(IMessageWriter writer, bool initialState)
+ {
+ writer.WritePacked(_playersCleaningVents.Count);
+ foreach (var pair in _playersCleaningVents)
+ {
+ writer.Write(pair.Key);
+ writer.Write(pair.Value);
+ }
+
+ writer.WritePacked(_playersInsideVents.Count);
+ foreach (var pair in _playersInsideVents)
+ {
+ writer.Write(pair.Key);
+ writer.Write(pair.Value);
+ }
+ }
+
+ public void Deserialize(IMessageReader reader, bool initialState)
+ {
+ _playersCleaningVents.Clear();
+ var cleaningCount = reader.ReadPackedInt32();
+ for (var i = 0; i < cleaningCount; i++)
+ {
+ _playersCleaningVents[reader.ReadByte()] = reader.ReadByte();
+ }
+
+ _playersInsideVents.Clear();
+ var insideCount = reader.ReadPackedInt32();
+ for (var i = 0; i < insideCount; i++)
+ {
+ _playersInsideVents[reader.ReadByte()] = reader.ReadByte();
+ }
+ }
+
+ public void UpdateSystem(IInnerPlayerControl? playerControl, IMessageReader reader)
+ {
+ if (playerControl == null)
+ {
+ return;
+ }
+
+ var opId = reader.ReadUInt16();
+ var operation = (Operation)reader.ReadByte();
+ var ventId = reader.ReadByte();
+ var playerId = playerControl.PlayerId;
+
+ if (!_sequenceBuffers.TryGetValue(playerId, out var sequenceBuffer))
+ {
+ sequenceBuffer = new SequenceBuffer((ushort)(opId - 1));
+ _sequenceBuffers[playerId] = sequenceBuffer;
+ }
+
+ if (sequenceBuffer.IsInvalidSid(opId))
+ {
+ return;
+ }
+
+ if (sequenceBuffer.IsNextSid(opId))
+ {
+ PerformVentOp(playerId, operation, ventId, sequenceBuffer);
+ }
+ else
+ {
+ sequenceBuffer.Add(opId, new VentMoveInfo(operation, ventId, playerId));
+ }
+
+ foreach (var info in sequenceBuffer.SubsequentObjs())
+ {
+ PerformVentOp(info.PlayerId, info.Op, info.VentId, sequenceBuffer);
+ }
+ }
+
+ public bool IsVentCurrentlyBeingCleaned(int ventId)
+ {
+ return _playersCleaningVents.Any(x => x.Value == ventId);
+ }
+
+ public bool IsImpostorInsideVent(int ventId)
+ {
+ return _playersInsideVents.Any(x => x.Value == ventId);
+ }
+
+ private void PerformVentOp(byte playerId, Operation operation, byte ventId, SequenceBuffer sequenceBuffer)
+ {
+ sequenceBuffer.BumpSid();
+ switch (operation)
+ {
+ case Operation.StartCleaning:
+ BootImpostorsFromVent(ventId);
+ _playersCleaningVents[playerId] = ventId;
+ break;
+ case Operation.StopCleaning:
+ foreach (var pair in _playersCleaningVents.ToArray())
+ {
+ if (pair.Value == ventId)
+ {
+ _playersCleaningVents.Remove(pair.Key);
+ }
+ }
+
+ break;
+ case Operation.Enter:
+ _playersInsideVents[playerId] = ventId;
+ break;
+ case Operation.Exit:
+ _playersInsideVents.Remove(playerId);
+ break;
+ case Operation.Move:
+ _playersInsideVents[playerId] = ventId;
+ break;
+ case Operation.BootImpostors:
+ BootImpostorsFromVent(ventId);
+ break;
+ }
+ }
+
+ private void BootImpostorsFromVent(byte ventId)
+ {
+ foreach (var pair in _playersInsideVents.ToArray())
+ {
+ if (pair.Value == ventId)
+ {
+ _playersInsideVents.Remove(pair.Key);
+ }
+ }
+ }
+
+ private readonly record struct VentMoveInfo(Operation Op, byte VentId, byte PlayerId);
+ }
+}
diff --git a/src/Empostor.Server/Program.cs b/src/Empostor.Server/Program.cs
index 85edb2a..2d75faa 100644
--- a/src/Empostor.Server/Program.cs
+++ b/src/Empostor.Server/Program.cs
@@ -200,6 +200,7 @@ namespace Empostor.Server
services.AddHazel();
services.AddSingleton, CustomMessageManager>();
services.AddSingleton, CustomMessageManager>();
+ services.AddSingleton, CustomMessageManager>();
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();