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();