diff options
Diffstat (limited to 'Impostor-dev/src/Impostor.Server/Net/Inner/Objects')
26 files changed, 1936 insertions, 0 deletions
diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs new file mode 100644 index 0000000..6cdd6b9 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.Api.cs @@ -0,0 +1,25 @@ +using System.Numerics; +using System.Threading.Tasks; +using Impostor.Api.Net.Inner.Objects.Components; + +namespace Impostor.Server.Net.Inner.Objects.Components +{ + internal partial class InnerCustomNetworkTransform : IInnerCustomNetworkTransform + { + public async ValueTask SnapToAsync(Vector2 position) + { + var minSid = (ushort)(_lastSequenceId + 5U); + + // Snap in the server. + SnapTo(position, minSid); + + // Broadcast to all clients. + using (var writer = _game.StartRpc(NetId, RpcCalls.SnapTo)) + { + WriteVector2(writer, position); + writer.Write(_lastSequenceId); + await _game.FinishRpcAsync(writer); + } + } + } +} diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs new file mode 100644 index 0000000..d261c11 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerCustomNetworkTransform.cs @@ -0,0 +1,148 @@ +using System.Numerics; +using System.Threading.Tasks; +using Impostor.Api; +using Impostor.Api.Innersloth; +using Impostor.Api.Net; +using Impostor.Api.Net.Messages; +using Impostor.Server.Net.State; +using Microsoft.Extensions.Logging; + +namespace Impostor.Server.Net.Inner.Objects.Components +{ + internal partial class InnerCustomNetworkTransform : InnerNetObject + { + private static readonly FloatRange XRange = new FloatRange(-40f, 40f); + private static readonly FloatRange YRange = new FloatRange(-40f, 40f); + + private readonly ILogger<InnerCustomNetworkTransform> _logger; + private readonly InnerPlayerControl _playerControl; + private readonly Game _game; + + private ushort _lastSequenceId; + private Vector2 _targetSyncPosition; + private Vector2 _targetSyncVelocity; + + public InnerCustomNetworkTransform(ILogger<InnerCustomNetworkTransform> logger, InnerPlayerControl playerControl, Game game) + { + _logger = logger; + _playerControl = playerControl; + _game = game; + } + + 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 static void WriteVector2(IMessageWriter writer, Vector2 vec) + { + writer.Write((ushort)(XRange.ReverseLerp(vec.X) * (double) ushort.MaxValue)); + writer.Write((ushort)(YRange.ReverseLerp(vec.Y) * (double) ushort.MaxValue)); + } + + private static Vector2 ReadVector2(IMessageReader reader) + { + var v1 = reader.ReadUInt16() / (float) ushort.MaxValue; + var v2 = reader.ReadUInt16() / (float) ushort.MaxValue; + + return new Vector2(XRange.Lerp(v1), YRange.Lerp(v2)); + } + + public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + { + if (call == RpcCalls.SnapTo) + { + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SnapTo)} to an unowned {nameof(InnerPlayerControl)}"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SnapTo)} to a specific player instead of broadcast"); + } + + if (!sender.Character.PlayerInfo.IsImpostor) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SnapTo)} as crewmate"); + } + + SnapTo(ReadVector2(reader), reader.ReadUInt16()); + } + else + { + _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerCustomNetworkTransform), call); + } + + return default; + } + + public override bool Serialize(IMessageWriter writer, bool initialState) + { + if (initialState) + { + writer.Write(_lastSequenceId); + WriteVector2(writer, _targetSyncPosition); + WriteVector2(writer, _targetSyncVelocity); + return true; + } + + // TODO: DirtyBits == 0 return false. + _lastSequenceId++; + + writer.Write(_lastSequenceId); + WriteVector2(writer, _targetSyncPosition); + WriteVector2(writer, _targetSyncVelocity); + return true; + } + + public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) + { + var sequenceId = reader.ReadUInt16(); + + if (initialState) + { + _lastSequenceId = sequenceId; + _targetSyncPosition = ReadVector2(reader); + _targetSyncVelocity = ReadVector2(reader); + } + else + { + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client attempted to send unowned {nameof(InnerCustomNetworkTransform)} data"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client attempted to send {nameof(InnerCustomNetworkTransform)} data to a specific player, must be broadcast"); + } + + if (!SidGreaterThan(sequenceId, _lastSequenceId)) + { + return; + } + + _lastSequenceId = sequenceId; + _targetSyncPosition = ReadVector2(reader); + _targetSyncVelocity = ReadVector2(reader); + } + } + + private void SnapTo(Vector2 position, ushort minSid) + { + if (!SidGreaterThan(minSid, _lastSequenceId)) + { + return; + } + + _lastSequenceId = minSid; + _targetSyncPosition = position; + _targetSyncVelocity = Vector2.Zero; + } + } +} diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.Api.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.Api.cs new file mode 100644 index 0000000..6af54a0 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.Api.cs @@ -0,0 +1,8 @@ +using Impostor.Api.Net.Inner.Objects.Components; + +namespace Impostor.Server.Net.Inner.Objects.Components +{ + internal partial class InnerPlayerPhysics : IInnerPlayerPhysics + { + } +} diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs new file mode 100644 index 0000000..29bc996 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerPlayerPhysics.cs @@ -0,0 +1,70 @@ +using System; +using System.Threading.Tasks; +using Impostor.Api; +using Impostor.Api.Events.Managers; +using Impostor.Api.Innersloth; +using Impostor.Api.Net; +using Impostor.Api.Net.Messages; +using Impostor.Server.Events.Player; +using Impostor.Server.Net.State; +using Microsoft.Extensions.Logging; + +namespace Impostor.Server.Net.Inner.Objects.Components +{ + internal partial class InnerPlayerPhysics : InnerNetObject + { + private readonly ILogger<InnerPlayerPhysics> _logger; + private readonly InnerPlayerControl _playerControl; + private readonly IEventManager _eventManager; + private readonly Game _game; + + public InnerPlayerPhysics(ILogger<InnerPlayerPhysics> logger, InnerPlayerControl playerControl, IEventManager eventManager, Game game) + { + _logger = logger; + _playerControl = playerControl; + _eventManager = eventManager; + _game = game; + } + + public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + { + if (call != RpcCalls.EnterVent && call != RpcCalls.ExitVent) + { + _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerPlayerPhysics), call); + return; + } + + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client sent {call} to an unowned {nameof(InnerPlayerControl)}"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {call} to a specific player instead of broadcast"); + } + + if (!sender.Character.PlayerInfo.IsImpostor) + { + throw new ImpostorCheatException($"Client sent {call} as crewmate"); + } + + var ventId = reader.ReadPackedUInt32(); + var ventEnter = call == RpcCalls.EnterVent; + + await _eventManager.CallAsync(new PlayerVentEvent(_game, sender, _playerControl, (VentLocation)ventId, ventEnter)); + + return; + } + + public override bool Serialize(IMessageWriter writer, bool initialState) + { + throw new NotImplementedException(); + } + + public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) + { + throw new NotImplementedException(); + } + } +} diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs new file mode 100644 index 0000000..58b9b54 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Components/InnerVoteBanSystem.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Impostor.Api; +using Impostor.Api.Net; +using Impostor.Api.Net.Inner.Objects; +using Impostor.Api.Net.Messages; +using Impostor.Server.Net.State; +using Microsoft.Extensions.Logging; + +namespace Impostor.Server.Net.Inner.Objects.Components +{ + internal class InnerVoteBanSystem : InnerNetObject, IInnerVoteBanSystem + { + private readonly ILogger<InnerVoteBanSystem> _logger; + private readonly Dictionary<int, int[]> _votes; + + public InnerVoteBanSystem(ILogger<InnerVoteBanSystem> logger) + { + _logger = logger; + _votes = new Dictionary<int, int[]>(); + } + + public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + { + if (call != RpcCalls.AddVote) + { + _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerVoteBanSystem), call); + return default; + } + + var clientId = reader.ReadInt32(); + if (clientId != sender.Client.Id) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.AddVote)} as other client"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CastVote)} to wrong destinition, must be broadcast"); + } + + var targetClientId = reader.ReadInt32(); + + // TODO: Use. + + return default; + } + + public override bool Serialize(IMessageWriter writer, bool initialState) + { + throw new NotImplementedException(); + } + + public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerShipStatus)} as non-host"); + } + + var votes = _votes; + var unknown = reader.ReadByte(); + if (unknown != 0) + { + for (var i = 0; i < unknown; i++) + { + var v4 = reader.ReadInt32(); + if (v4 == 0) + { + break; + } + + if (!votes.TryGetValue(v4, out var v12)) + { + v12 = new int[3]; + votes[v4] = v12; + } + + for (var j = 0; j < 3; j++) + { + v12[j] = reader.ReadPackedInt32(); + } + } + } + } + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.cs new file mode 100644 index 0000000..29ca50a --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.TaskInfo.cs @@ -0,0 +1,30 @@ +using Impostor.Api.Innersloth; +using Impostor.Api.Net.Inner.Objects; +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects +{ + internal partial class InnerGameData + { + public class TaskInfo : ITaskInfo + { + public uint Id { get; internal set; } + + public bool Complete { get; internal set; } + + public TaskTypes Type { get; internal set; } + + public void Serialize(IMessageWriter writer) + { + writer.WritePacked((uint)Id); + writer.Write(Complete); + } + + public void Deserialize(IMessageReader reader) + { + Id = reader.ReadPackedUInt32(); + Complete = reader.ReadBoolean(); + } + } + } +} diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs new file mode 100644 index 0000000..ed68038 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerGameData.cs @@ -0,0 +1,187 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading.Tasks; +using Impostor.Api; +using Impostor.Api.Innersloth; +using Impostor.Api.Net; +using Impostor.Api.Net.Inner.Objects; +using Impostor.Api.Net.Messages; +using Impostor.Server.Net.Inner.Objects.Components; +using Impostor.Server.Net.State; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Impostor.Server.Net.Inner.Objects +{ + internal partial class InnerGameData : InnerNetObject, IInnerGameData + { + private readonly ILogger<InnerGameData> _logger; + private readonly Game _game; + private readonly ConcurrentDictionary<byte, InnerPlayerInfo> _allPlayers; + + public InnerGameData(ILogger<InnerGameData> logger, Game game, IServiceProvider serviceProvider) + { + _logger = logger; + _game = game; + _allPlayers = new ConcurrentDictionary<byte, InnerPlayerInfo>(); + + Components.Add(this); + Components.Add(ActivatorUtilities.CreateInstance<InnerVoteBanSystem>(serviceProvider)); + } + + public int PlayerCount => _allPlayers.Count; + + public IReadOnlyDictionary<byte, InnerPlayerInfo> Players => _allPlayers; + + public InnerPlayerInfo? GetPlayerById(byte id) + { + if (id == byte.MaxValue) + { + return null; + } + + return _allPlayers.TryGetValue(id, out var player) ? player : null; + } + + public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + { + switch (call) + { + case RpcCalls.SetTasks: + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetTasks)} but was not a host"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetTasks)} to a specific player instead of broadcast"); + } + + var playerId = reader.ReadByte(); + var taskTypeIds = reader.ReadBytesAndSize(); + + SetTasks(playerId, taskTypeIds); + break; + } + + case RpcCalls.UpdateGameData: + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetTasks)} but was not a host"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetTasks)} to a specific player instead of broadcast"); + } + + while (reader.Position < reader.Length) + { + using var message = reader.ReadMessage(); + var player = GetPlayerById(message.Tag); + if (player != null) + { + player.Deserialize(message); + } + else + { + var playerInfo = new InnerPlayerInfo(message.Tag); + + playerInfo.Deserialize(reader); + + if (!_allPlayers.TryAdd(playerInfo.PlayerId, playerInfo)) + { + throw new ImpostorException("Failed to add player to InnerGameData."); + } + } + } + + break; + } + + default: + { + _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerGameData), call); + break; + } + } + + return default; + } + + public override bool Serialize(IMessageWriter writer, bool initialState) + { + throw new NotImplementedException(); + } + + public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerGameData)} as non-host"); + } + + if (initialState) + { + var num = reader.ReadPackedInt32(); + + for (var i = 0; i < num; i++) + { + var playerId = reader.ReadByte(); + var playerInfo = new InnerPlayerInfo(playerId); + + playerInfo.Deserialize(reader); + + if (!_allPlayers.TryAdd(playerInfo.PlayerId, playerInfo)) + { + throw new ImpostorException("Failed to add player to InnerGameData."); + } + } + } + else + { + throw new NotImplementedException("This shouldn't happen, according to Among Us disassembly."); + } + } + + internal void AddPlayer(InnerPlayerControl control) + { + var playerId = control.PlayerId; + var playerInfo = new InnerPlayerInfo(control.PlayerId); + + if (_allPlayers.TryAdd(playerId, playerInfo)) + { + control.PlayerInfo = playerInfo; + } + } + + private void SetTasks(byte playerId, ReadOnlyMemory<byte> taskTypeIds) + { + var player = GetPlayerById(playerId); + if (player == null) + { + _logger.LogTrace("Could not set tasks for playerId {0}.", playerId); + return; + } + + if (player.Disconnected) + { + return; + } + + player.Tasks = new List<TaskInfo>(taskTypeIds.Length); + + foreach (var taskId in taskTypeIds.ToArray()) + { + player.Tasks.Add(new TaskInfo + { + Id = taskId, + }); + } + } + } +} diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs new file mode 100644 index 0000000..63448ed --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerLobbyBehaviour.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using Impostor.Api.Games; +using Impostor.Api.Net; +using Impostor.Api.Net.Inner.Objects; +using Impostor.Api.Net.Messages; +using Impostor.Server.Net.State; + +namespace Impostor.Server.Net.Inner.Objects +{ + internal class InnerLobbyBehaviour : InnerNetObject, IInnerLobbyBehaviour + { + private readonly IGame _game; + + public InnerLobbyBehaviour(IGame game) + { + _game = game; + + Components.Add(this); + } + + public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + { + throw new System.NotImplementedException(); + } + + public override bool Serialize(IMessageWriter writer, bool initialState) + { + throw new System.NotImplementedException(); + } + + public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) + { + throw new System.NotImplementedException(); + } + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.Api.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.Api.cs new file mode 100644 index 0000000..5d120c5 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.Api.cs @@ -0,0 +1,9 @@ +using Impostor.Api.Net.Inner.Objects; + +namespace Impostor.Server.Net.Inner.Objects +{ + internal partial class InnerMeetingHud : IInnerMeetingHud + { + + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.PlayerVoteArea.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.PlayerVoteArea.cs new file mode 100644 index 0000000..fbf2510 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.PlayerVoteArea.cs @@ -0,0 +1,49 @@ +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects +{ + internal partial class InnerMeetingHud + { + public class PlayerVoteArea + { + private const byte VoteMask = 15; + private const byte ReportedBit = 32; + private const byte VotedBit = 64; + private const byte DeadBit = 128; + + public PlayerVoteArea(InnerMeetingHud parent, byte targetPlayerId) + { + Parent = parent; + TargetPlayerId = targetPlayerId; + } + + public InnerMeetingHud Parent { get; } + + public byte TargetPlayerId { get; } + + public bool IsDead { get; private set; } + + public bool DidVote { get; private set; } + + public bool DidReport { get; private set; } + + public sbyte VotedFor { get; private set; } + + internal void SetDead(bool didReport, bool isDead) + { + DidReport = didReport; + IsDead = isDead; + } + + public void Deserialize(IMessageReader reader) + { + var num = reader.ReadByte(); + + VotedFor = (sbyte)((num & VoteMask) - 1); + IsDead = (num & DeadBit) > 0; + DidVote = (num & VotedBit) > 0; + DidReport = (num & ReportedBit) > 0; + } + } + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs new file mode 100644 index 0000000..c2bcb9d --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerMeetingHud.cs @@ -0,0 +1,176 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Impostor.Api; +using Impostor.Api.Events.Managers; +using Impostor.Api.Innersloth; +using Impostor.Api.Net; +using Impostor.Api.Net.Messages; +using Impostor.Server.Events.Meeting; +using Impostor.Server.Events.Player; +using Impostor.Server.Net.State; +using Microsoft.Extensions.Logging; + +namespace Impostor.Server.Net.Inner.Objects +{ + internal partial class InnerMeetingHud : InnerNetObject + { + private readonly ILogger<InnerMeetingHud> _logger; + private readonly IEventManager _eventManager; + private readonly Game _game; + private readonly GameNet _gameNet; + private PlayerVoteArea[] _playerStates; + + public InnerMeetingHud(ILogger<InnerMeetingHud> logger, IEventManager eventManager, Game game) + { + _logger = logger; + _eventManager = eventManager; + _game = game; + _gameNet = game.GameNet; + _playerStates = null; + + Components.Add(this); + } + + public byte ReporterId { get; private set; } + + private void PopulateButtons(byte reporter) + { + _playerStates = _gameNet.GameData.Players + .Select(x => + { + var area = new PlayerVoteArea(this, x.Key); + area.SetDead(x.Value.PlayerId == reporter, x.Value.Disconnected || x.Value.IsDead); + return area; + }) + .ToArray(); + } + + public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + { + switch (call) + { + case RpcCalls.Close: + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.Close)} but was not a host"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.Close)} to a specific player instead of broadcast"); + } + + break; + } + + case RpcCalls.VotingComplete: + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.VotingComplete)} but was not a host"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.VotingComplete)} to a specific player instead of broadcast"); + } + + var states = reader.ReadBytesAndSize(); + var playerId = reader.ReadByte(); + var tie = reader.ReadBoolean(); + + if (playerId != byte.MaxValue) + { + var player = _game.GameNet.GameData.GetPlayerById(playerId); + if (player != null) + { + player.Controller.Die(DeathReason.Exile); + await _eventManager.CallAsync(new PlayerExileEvent(_game, sender, player.Controller)); + } + } + + await _eventManager.CallAsync(new MeetingEndedEvent(_game, this)); + + break; + } + + case RpcCalls.CastVote: + { + var srcPlayerId = reader.ReadByte(); + if (srcPlayerId != sender.Character.PlayerId) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CastVote)} to an unowned {nameof(InnerPlayerControl)}"); + } + + // Host broadcasts vote to others. + if (sender.IsHost && target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CastVote)} to a specific player instead of broadcast"); + } + + // Player sends vote to host. + if (target == null || !target.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CastVote)} to wrong destinition, must be host"); + } + + var targetPlayerId = reader.ReadByte(); + break; + } + + default: + { + _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerMeetingHud), call); + break; + } + } + } + + public override bool Serialize(IMessageWriter writer, bool initialState) + { + throw new NotImplementedException(); + } + + public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerMeetingHud)} as non-host"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client attempted to send {nameof(InnerMeetingHud)} data to a specific player, must be broadcast"); + } + + if (initialState) + { + PopulateButtons(0); + + foreach (var playerState in _playerStates) + { + playerState.Deserialize(reader); + + if (playerState.DidReport) + { + ReporterId = playerState.TargetPlayerId; + } + } + } + else + { + var num = reader.ReadPackedUInt32(); + + for (var i = 0; i < _playerStates.Length; i++) + { + if ((num & 1 << i) != 0) + { + _playerStates[i].Deserialize(reader); + } + } + } + } + } +} diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs new file mode 100644 index 0000000..0a7997d --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs @@ -0,0 +1,138 @@ +using System.Threading.Tasks; +using Impostor.Api; +using Impostor.Api.Innersloth; +using Impostor.Api.Innersloth.Customization; +using Impostor.Api.Net; +using Impostor.Api.Net.Inner.Objects; +using Impostor.Api.Net.Inner.Objects.Components; +using Impostor.Server.Events.Player; + +namespace Impostor.Server.Net.Inner.Objects +{ + internal partial class InnerPlayerControl : IInnerPlayerControl + { + IInnerPlayerPhysics IInnerPlayerControl.Physics => Physics; + + IInnerCustomNetworkTransform IInnerPlayerControl.NetworkTransform => NetworkTransform; + + IInnerPlayerInfo IInnerPlayerControl.PlayerInfo => PlayerInfo; + + public async ValueTask SetNameAsync(string name) + { + PlayerInfo.PlayerName = name; + + using var writer = _game.StartRpc(NetId, RpcCalls.SetName); + writer.Write(name); + await _game.FinishRpcAsync(writer); + } + + public async ValueTask SetColorAsync(byte colorId) + { + PlayerInfo.ColorId = colorId; + + using var writer = _game.StartRpc(NetId, RpcCalls.SetColor); + writer.Write(colorId); + await _game.FinishRpcAsync(writer); + } + + public ValueTask SetColorAsync(ColorType colorType) + { + return SetColorAsync((byte)colorType); + } + + public async ValueTask SetHatAsync(uint hatId) + { + PlayerInfo.HatId = hatId; + + using var writer = _game.StartRpc(NetId, RpcCalls.SetHat); + writer.WritePacked(hatId); + await _game.FinishRpcAsync(writer); + } + + public ValueTask SetHatAsync(HatType hatType) + { + return SetHatAsync((uint)hatType); + } + + public async ValueTask SetPetAsync(uint petId) + { + PlayerInfo.PetId = petId; + + using var writer = _game.StartRpc(NetId, RpcCalls.SetPet); + writer.WritePacked(petId); + await _game.FinishRpcAsync(writer); + } + + public ValueTask SetPetAsync(PetType petType) + { + return SetPetAsync((uint)petType); + } + + public async ValueTask SetSkinAsync(uint skinId) + { + PlayerInfo.SkinId = skinId; + + using var writer = _game.StartRpc(NetId, RpcCalls.SetSkin); + writer.WritePacked(skinId); + await _game.FinishRpcAsync(writer); + } + + public ValueTask SetSkinAsync(SkinType skinType) + { + return SetSkinAsync((uint)skinType); + } + + public async ValueTask SendChatAsync(string text) + { + using var writer = _game.StartRpc(NetId, RpcCalls.SendChat); + writer.Write(text); + await _game.FinishRpcAsync(writer); + } + + public async ValueTask SendChatToPlayerAsync(string text, IInnerPlayerControl? player = null) + { + if (player == null) + { + player = this; + } + + using var writer = _game.StartRpc(NetId, RpcCalls.SendChat); + writer.Write(text); + await _game.FinishRpcAsync(writer, player.OwnerId); + } + + public async ValueTask SetMurderedByAsync(IClientPlayer impostor) + { + if (impostor.Character == null) + { + throw new ImpostorException("Character is null."); + } + + if (!impostor.Character.PlayerInfo.IsImpostor) + { + throw new ImpostorProtocolException("Plugin tried to murder a player while the impostor specified was not an impostor."); + } + + if (impostor.Character.PlayerInfo.IsDead) + { + throw new ImpostorProtocolException("Plugin tried to murder a player while the impostor specified was dead."); + } + + if (PlayerInfo.IsDead) + { + return; + } + + // Update player. + Die(DeathReason.Kill); + + // Send RPC. + using var writer = _game.StartRpc(impostor.Character.NetId, RpcCalls.MurderPlayer); + writer.WritePacked(NetId); + await _game.FinishRpcAsync(writer); + + // Notify plugins. + await _eventManager.CallAsync(new PlayerMurderEvent(_game, impostor, impostor.Character, this)); + } + } +} diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs new file mode 100644 index 0000000..c71fcf7 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -0,0 +1,455 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Impostor.Api; +using Impostor.Api.Events.Managers; +using Impostor.Api.Innersloth; +using Impostor.Api.Net; +using Impostor.Api.Net.Messages; +using Impostor.Server.Events.Player; +using Impostor.Server.Net.Inner.Objects.Components; +using Impostor.Server.Net.State; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Impostor.Server.Net.Inner.Objects +{ + internal partial class InnerPlayerControl : InnerNetObject + { + private readonly ILogger<InnerPlayerControl> _logger; + private readonly IEventManager _eventManager; + private readonly Game _game; + + public InnerPlayerControl(ILogger<InnerPlayerControl> logger, IServiceProvider serviceProvider, IEventManager eventManager, Game game) + { + _logger = logger; + _eventManager = eventManager; + _game = game; + + Physics = ActivatorUtilities.CreateInstance<InnerPlayerPhysics>(serviceProvider, this, _eventManager, _game); + NetworkTransform = ActivatorUtilities.CreateInstance<InnerCustomNetworkTransform>(serviceProvider, this, _game); + + Components.Add(this); + Components.Add(Physics); + Components.Add(NetworkTransform); + + PlayerId = byte.MaxValue; + } + + public bool IsNew { get; private set; } + + public byte PlayerId { get; private set; } + + public InnerPlayerPhysics Physics { get; } + + public InnerCustomNetworkTransform NetworkTransform { get; } + + public InnerPlayerInfo PlayerInfo { get; internal set; } + + public override async ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader) + { + switch (call) + { + // Play an animation. + case RpcCalls.PlayAnimation: + { + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.PlayAnimation)} to an unowned {nameof(InnerPlayerControl)}"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.PlayAnimation)} to a specific player instead of broadcast"); + } + + var animation = reader.ReadByte(); + break; + } + + // Complete a task. + case RpcCalls.CompleteTask: + { + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CompleteTask)} to an unowned {nameof(InnerPlayerControl)}"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CompleteTask)} to a specific player instead of broadcast"); + } + + var taskId = reader.ReadPackedUInt32(); + var task = PlayerInfo.Tasks[(int)taskId]; + if (task == null) + { + _logger.LogWarning($"Client sent {nameof(RpcCalls.CompleteTask)} with a taskIndex that is not in their {nameof(InnerPlayerInfo)}"); + } + else + { + task.Complete = true; + await _eventManager.CallAsync(new PlayerCompletedTaskEvent(_game, sender, this, task)); + } + + break; + } + + // Update GameOptions. + case RpcCalls.SyncSettings: + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SyncSettings)} but was not a host"); + } + + _game.Options.Deserialize(reader.ReadBytesAndSize()); + break; + } + + // Set Impostors. + case RpcCalls.SetInfected: + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetInfected)} but was not a host"); + } + + var length = reader.ReadPackedInt32(); + + for (var i = 0; i < length; i++) + { + var playerId = reader.ReadByte(); + var player = _game.GameNet.GameData.GetPlayerById(playerId); + if (player != null) + { + player.IsImpostor = true; + } + } + + if (_game.GameState == GameStates.Starting) + { + await _game.StartedAsync(); + } + + break; + } + + // Player was voted out. + case RpcCalls.Exiled: + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.Exiled)} but was not a host"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.Exiled)} to a specific player instead of broadcast"); + } + + // TODO: Not hit? + Die(DeathReason.Exile); + + await _eventManager.CallAsync(new PlayerExileEvent(_game, sender, this)); + break; + } + + // Validates the player name at the host. + case RpcCalls.CheckName: + { + if (target == null || !target.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckName)} to the wrong player"); + } + + var name = reader.ReadString(); + break; + } + + // Update the name of a player. + case RpcCalls.SetName: + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} but was not a host"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} to a specific player instead of broadcast"); + } + + PlayerInfo.PlayerName = reader.ReadString(); + break; + } + + // Validates the color at the host. + case RpcCalls.CheckColor: + { + if (target == null || !target.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckColor)} to the wrong player"); + } + + var color = reader.ReadByte(); + break; + } + + // Update the color of a player. + case RpcCalls.SetColor: + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} but was not a host"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} to a specific player instead of broadcast"); + } + + PlayerInfo.ColorId = reader.ReadByte(); + break; + } + + // Update the hat of a player. + case RpcCalls.SetHat: + { + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetHat)} to an unowned {nameof(InnerPlayerControl)}"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetHat)} to a specific player instead of broadcast"); + } + + PlayerInfo.HatId = reader.ReadPackedUInt32(); + break; + } + + case RpcCalls.SetSkin: + { + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetSkin)} to an unowned {nameof(InnerPlayerControl)}"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetHat)} to a specific player instead of broadcast"); + } + + PlayerInfo.SkinId = reader.ReadPackedUInt32(); + break; + } + + // TODO: (ANTICHEAT) Location check? + // only called by a non-host player on to start meeting + case RpcCalls.ReportDeadBody: + { + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.ReportDeadBody)} to an unowned {nameof(InnerPlayerControl)}"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.ReportDeadBody)} to a specific player instead of broadcast"); + } + + + var deadBodyPlayerId = reader.ReadByte(); + // deadBodyPlayerId == byte.MaxValue -- means emergency call by button + + break; + } + + // TODO: (ANTICHEAT) Cooldown check? + case RpcCalls.MurderPlayer: + { + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} to an unowned {nameof(InnerPlayerControl)}"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} to a specific player instead of broadcast"); + } + + if (!sender.Character.PlayerInfo.IsImpostor) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} as crewmate"); + } + + if (!sender.Character.PlayerInfo.CanMurder(_game)) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.MurderPlayer)} too fast"); + } + + sender.Character.PlayerInfo.LastMurder = DateTimeOffset.UtcNow; + + var player = reader.ReadNetObject<InnerPlayerControl>(_game); + if (!player.PlayerInfo.IsDead) + { + player.Die(DeathReason.Kill); + await _eventManager.CallAsync(new PlayerMurderEvent(_game, sender, this, player)); + } + + break; + } + + case RpcCalls.SendChat: + { + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SendChat)} to an unowned {nameof(InnerPlayerControl)}"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SendChat)} to a specific player instead of broadcast"); + } + + var chat = reader.ReadString(); + + await _eventManager.CallAsync(new PlayerChatEvent(_game, sender, this, chat)); + break; + } + + case RpcCalls.StartMeeting: + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.StartMeeting)} but was not a host"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.StartMeeting)} to a specific player instead of broadcast"); + } + + // deadBodyPlayerId == byte.MaxValue -- means emergency call by button + var deadBodyPlayerId = reader.ReadByte(); + var deadPlayer = deadBodyPlayerId != byte.MaxValue + ? _game.GameNet.GameData.GetPlayerById(deadBodyPlayerId)?.Controller + : null; + + await _eventManager.CallAsync(new PlayerStartMeetingEvent(_game, _game.GetClientPlayer(this.OwnerId), this, deadPlayer)); + break; + } + + case RpcCalls.SetScanner: + { + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetScanner)} to an unowned {nameof(InnerPlayerControl)}"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetScanner)} to a specific player instead of broadcast"); + } + + var on = reader.ReadBoolean(); + var count = reader.ReadByte(); + break; + } + + case RpcCalls.SendChatNote: + { + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SendChatNote)} to an unowned {nameof(InnerPlayerControl)}"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SendChatNote)} to a specific player instead of broadcast"); + } + + var playerId = reader.ReadByte(); + var chatNote = (ChatNoteType)reader.ReadByte(); + break; + } + + case RpcCalls.SetPet: + { + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetPet)} to an unowned {nameof(InnerPlayerControl)}"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetPet)} to a specific player instead of broadcast"); + } + + PlayerInfo.PetId = reader.ReadPackedUInt32(); + break; + } + + // TODO: Understand this RPC + case RpcCalls.SetStartCounter: + { + if (!sender.IsOwner(this)) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetStartCounter)} to an unowned {nameof(InnerPlayerControl)}"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetStartCounter)} to a specific player instead of broadcast"); + } + + // Used to compare with LastStartCounter. + var startCounter = reader.ReadPackedUInt32(); + + // Is either start countdown or byte.MaxValue + var secondsLeft = reader.ReadByte(); + if (secondsLeft < byte.MaxValue) + { + await _eventManager.CallAsync(new PlayerSetStartCounterEvent(_game, sender, this, secondsLeft)); + } + + break; + } + + default: + { + _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerPlayerControl), call); + break; + } + } + } + + public override bool Serialize(IMessageWriter writer, bool initialState) + { + throw new NotImplementedException(); + } + + public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerPlayerControl)} as non-host"); + } + + if (initialState) + { + IsNew = reader.ReadBoolean(); + } + + PlayerId = reader.ReadByte(); + } + + internal void Die(DeathReason reason) + { + PlayerInfo.IsDead = true; + PlayerInfo.LastDeathReason = reason; + } + } +} diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.Api.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.Api.cs new file mode 100644 index 0000000..512a4f1 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.Api.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Impostor.Api.Net.Inner.Objects; + +namespace Impostor.Server.Net.Inner.Objects +{ + internal partial class InnerPlayerInfo : IInnerPlayerInfo + { + IEnumerable<ITaskInfo> IInnerPlayerInfo.Tasks => Tasks; + } +} diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs new file mode 100644 index 0000000..f248994 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerPlayerInfo.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using Impostor.Api.Games; +using Impostor.Api.Innersloth; +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects +{ + internal partial class InnerPlayerInfo + { + public InnerPlayerInfo(byte playerId) + { + PlayerId = playerId; + } + + public InnerPlayerControl Controller { get; internal set; } + + public byte PlayerId { get; } + + public string PlayerName { get; internal set; } + + public byte ColorId { get; internal set; } + + public uint HatId { get; internal set; } + + public uint PetId { get; internal set; } + + public uint SkinId { get; internal set; } + + public bool Disconnected { get; internal set; } + + public bool IsImpostor { get; internal set; } + + public bool IsDead { get; internal set; } + + public DeathReason LastDeathReason { get; internal set; } + + public List<InnerGameData.TaskInfo> Tasks { get; internal set; } + + public DateTimeOffset LastMurder { get; set; } + + public bool CanMurder(IGame game) + { + if (!IsImpostor) + { + return false; + } + + return DateTimeOffset.UtcNow.Subtract(LastMurder).TotalSeconds >= game.Options.KillCooldown; + } + + public void Serialize(IMessageWriter writer) + { + throw new NotImplementedException(); + } + + public void Deserialize(IMessageReader reader) + { + PlayerName = reader.ReadString(); + ColorId = reader.ReadByte(); + HatId = reader.ReadPackedUInt32(); + PetId = reader.ReadPackedUInt32(); + SkinId = reader.ReadPackedUInt32(); + var flag = reader.ReadByte(); + Disconnected = (flag & 1) > 0; + IsImpostor = (flag & 2) > 0; + IsDead = (flag & 4) > 0; + var taskCount = reader.ReadByte(); + for (var i = 0; i < taskCount; i++) + { + Tasks[i] ??= new InnerGameData.TaskInfo(); + Tasks[i].Deserialize(reader); + } + } + } +} diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs new file mode 100644 index 0000000..b1a3f18 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/InnerShipStatus.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Impostor.Api; +using Impostor.Api.Innersloth; +using Impostor.Api.Net; +using Impostor.Api.Net.Inner.Objects; +using Impostor.Api.Net.Messages; +using Impostor.Server.Net.Inner.Objects.Systems; +using Impostor.Server.Net.Inner.Objects.Systems.ShipStatus; +using Impostor.Server.Net.State; +using Microsoft.Extensions.Logging; + +namespace Impostor.Server.Net.Inner.Objects +{ + internal class InnerShipStatus : InnerNetObject, IInnerShipStatus + { + private readonly ILogger<InnerShipStatus> _logger; + private readonly Game _game; + private readonly Dictionary<SystemTypes, ISystemType> _systems; + + public InnerShipStatus(ILogger<InnerShipStatus> logger, Game game) + { + _logger = logger; + _game = game; + + _systems = new Dictionary<SystemTypes, ISystemType> + { + [SystemTypes.Electrical] = new SwitchSystem(), + [SystemTypes.MedBay] = new MedScanSystem(), + [SystemTypes.Reactor] = new ReactorSystemType(), + [SystemTypes.LifeSupp] = new LifeSuppSystemType(), + [SystemTypes.Security] = new SecurityCameraSystemType(), + [SystemTypes.Comms] = new HudOverrideSystemType(), + [SystemTypes.Doors] = new DoorsSystemType(_game), + }; + + _systems.Add(SystemTypes.Sabotage, new SabotageSystemType(new[] + { + (IActivatable)_systems[SystemTypes.Comms], + (IActivatable)_systems[SystemTypes.Reactor], + (IActivatable)_systems[SystemTypes.LifeSupp], + (IActivatable)_systems[SystemTypes.Electrical], + })); + + Components.Add(this); + } + + public override ValueTask HandleRpc(ClientPlayer sender, ClientPlayer? target, RpcCalls call, + IMessageReader reader) + { + switch (call) + { + case RpcCalls.CloseDoorsOfType: + { + if (target == null || !target.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CloseDoorsOfType)} to wrong destinition, must be host"); + } + + if (!sender.Character.PlayerInfo.IsImpostor) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CloseDoorsOfType)} as crewmate"); + } + + var systemType = (SystemTypes)reader.ReadByte(); + + break; + } + + case RpcCalls.RepairSystem: + { + if (target == null || !target.IsHost) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.RepairSystem)} to wrong destinition, must be host"); + } + + var systemType = (SystemTypes)reader.ReadByte(); + if (systemType == SystemTypes.Sabotage && !sender.Character.PlayerInfo.IsImpostor) + { + throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.RepairSystem)} for {systemType} as crewmate"); + } + + var player = reader.ReadNetObject<InnerPlayerControl>(_game); + var amount = reader.ReadByte(); + + // TODO: Modify data (?) + break; + } + + default: + { + _logger.LogWarning("{0}: Unknown rpc call {1}", nameof(InnerShipStatus), call); + break; + } + } + + return default; + } + + public override bool Serialize(IMessageWriter writer, bool initialState) + { + throw new NotImplementedException(); + } + + public override void Deserialize(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState) + { + if (!sender.IsHost) + { + throw new ImpostorCheatException($"Client attempted to send data for {nameof(InnerShipStatus)} as non-host"); + } + + if (target != null) + { + throw new ImpostorCheatException($"Client attempted to send {nameof(InnerShipStatus)} data to a specific player, must be broadcast"); + } + + if (initialState) + { + // TODO: (_systems[SystemTypes.Doors] as DoorsSystemType).SetDoors(); + foreach (var systemType in SystemTypeHelpers.AllTypes) + { + if (_systems.TryGetValue(systemType, out var system)) + { + system.Deserialize(reader, true); + } + } + } + else + { + var count = reader.ReadPackedUInt32(); + + foreach (var systemType in SystemTypeHelpers.AllTypes) + { + // TODO: Not sure what is going on here, check. + if ((count & 1 << (int)(systemType & (SystemTypes.ShipTasks | SystemTypes.Doors))) != 0L) + { + if (_systems.TryGetValue(systemType, out var system)) + { + system.Deserialize(reader, false); + } + } + } + } + } + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/IActivatable.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/IActivatable.cs new file mode 100644 index 0000000..e595b25 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/IActivatable.cs @@ -0,0 +1,7 @@ +namespace Impostor.Server.Net.Inner.Objects.Systems +{ + public interface IActivatable + { + bool IsActive { get; } + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ISystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ISystemType.cs new file mode 100644 index 0000000..a6ef88e --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ISystemType.cs @@ -0,0 +1,11 @@ +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects.Systems +{ + public interface ISystemType + { + void Serialize(IMessageWriter writer, bool initialState); + + void Deserialize(IMessageReader reader, bool initialState); + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs new file mode 100644 index 0000000..64b1f5f --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/DoorsSystemType.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using Impostor.Api.Games; +using Impostor.Api.Innersloth; +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus +{ + public class DoorsSystemType : ISystemType + { + // TODO: AutoDoors + private readonly Dictionary<int, bool> _doors; + + public DoorsSystemType(IGame game) + { + var doorCount = game.Options.Map switch + { + MapTypes.Skeld => 13, + MapTypes.MiraHQ => 2, + MapTypes.Polus => 12, + _ => throw new ArgumentOutOfRangeException() + }; + + _doors = new Dictionary<int, bool>(doorCount); + + for (var i = 0; i < doorCount; i++) + { + _doors.Add(i, false); + } + } + + public void Serialize(IMessageWriter writer, bool initialState) + { + throw new NotImplementedException(); + } + + public void Deserialize(IMessageReader reader, bool initialState) + { + if (initialState) + { + for (var i = 0; i < _doors.Count; i++) + { + _doors[i] = reader.ReadBoolean(); + } + } + else + { + var num = reader.ReadPackedUInt32(); + + for (var i = 0; i < _doors.Count; i++) + { + if ((num & 1 << i) != 0) + { + _doors[i] = reader.ReadBoolean(); + } + } + } + } + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs new file mode 100644 index 0000000..42aa8d3 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/HudOverrideSystemType.cs @@ -0,0 +1,19 @@ +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus +{ + public class HudOverrideSystemType : ISystemType, IActivatable + { + public bool IsActive { get; private set; } + + public void Serialize(IMessageWriter writer, bool initialState) + { + throw new System.NotImplementedException(); + } + + public void Deserialize(IMessageReader reader, bool initialState) + { + IsActive = reader.ReadBoolean(); + } + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs new file mode 100644 index 0000000..f644024 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/LifeSuppSystemType.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus +{ + public class LifeSuppSystemType : ISystemType, IActivatable + { + public LifeSuppSystemType() + { + Countdown = 10000f; + CompletedConsoles = new HashSet<int>(); + } + + public float Countdown { get; private set; } + + public HashSet<int> CompletedConsoles { get; } + + public bool IsActive => Countdown < 10000.0; + + public void Serialize(IMessageWriter writer, bool initialState) + { + throw new System.NotImplementedException(); + } + + public void Deserialize(IMessageReader reader, bool initialState) + { + Countdown = reader.ReadSingle(); + + if (reader.Position >= reader.Length) + { + return; + } + + CompletedConsoles.Clear(); // TODO: Thread safety + + var num = reader.ReadPackedInt32(); + + for (var i = 0; i < num; i++) + { + CompletedConsoles.Add(reader.ReadPackedInt32()); + } + } + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs new file mode 100644 index 0000000..007b9d0 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/MedScanSystem.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus +{ + public class MedScanSystem : ISystemType + { + public MedScanSystem() + { + UsersList = new List<byte>(); + } + + public List<byte> UsersList { get; } + + public void Serialize(IMessageWriter writer, bool initialState) + { + throw new System.NotImplementedException(); + } + + public void Deserialize(IMessageReader reader, bool initialState) + { + UsersList.Clear(); + + var num = reader.ReadPackedInt32(); + + for (var i = 0; i < num; i++) + { + UsersList.Add(reader.ReadByte()); + } + } + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs new file mode 100644 index 0000000..4380918 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/ReactorSystemType.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus +{ + public class ReactorSystemType : ISystemType, IActivatable + { + public ReactorSystemType() + { + Countdown = 10000f; + UserConsolePairs = new HashSet<Tuple<byte, byte>>(); + } + + public float Countdown { get; private set; } + + public HashSet<Tuple<byte, byte>> UserConsolePairs { get; } + + public bool IsActive => Countdown < 10000.0; + + public void Serialize(IMessageWriter writer, bool initialState) + { + throw new System.NotImplementedException(); + } + + public void Deserialize(IMessageReader reader, bool initialState) + { + Countdown = reader.ReadSingle(); + UserConsolePairs.Clear(); // TODO: Thread safety + + var count = reader.ReadPackedInt32(); + + for (var i = 0; i < count; i++) + { + UserConsolePairs.Add(new Tuple<byte, byte>(reader.ReadByte(), reader.ReadByte())); + } + } + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs new file mode 100644 index 0000000..193cfe8 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SabotageSystemType.cs @@ -0,0 +1,26 @@ +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus +{ + public class SabotageSystemType : ISystemType + { + private readonly IActivatable[] _specials; + + public SabotageSystemType(IActivatable[] specials) + { + _specials = specials; + } + + public float Timer { get; set; } + + public void Serialize(IMessageWriter writer, bool initialState) + { + throw new System.NotImplementedException(); + } + + public void Deserialize(IMessageReader reader, bool initialState) + { + Timer = reader.ReadSingle(); + } + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs new file mode 100644 index 0000000..df41b68 --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SecurityCameraSystemType.cs @@ -0,0 +1,19 @@ +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus +{ + public class SecurityCameraSystemType : ISystemType + { + public byte InUse { get; internal set; } + + public void Serialize(IMessageWriter writer, bool initialState) + { + throw new System.NotImplementedException(); + } + + public void Deserialize(IMessageReader reader, bool initialState) + { + InUse = reader.ReadByte(); + } + } +}
\ No newline at end of file diff --git a/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs new file mode 100644 index 0000000..925774a --- /dev/null +++ b/Impostor-dev/src/Impostor.Server/Net/Inner/Objects/Systems/ShipStatus/SwitchSystem.cs @@ -0,0 +1,27 @@ +using Impostor.Api.Net.Messages; + +namespace Impostor.Server.Net.Inner.Objects.Systems.ShipStatus +{ + public class SwitchSystem : ISystemType, IActivatable + { + public byte ExpectedSwitches { get; set; } + + public byte ActualSwitches { get; set; } + + public byte Value { get; set; } = byte.MaxValue; + + public bool IsActive { get; } + + public void Serialize(IMessageWriter writer, bool initialState) + { + throw new System.NotImplementedException(); + } + + public void Deserialize(IMessageReader reader, bool initialState) + { + ExpectedSwitches = reader.ReadByte(); + ActualSwitches = reader.ReadByte(); + Value = reader.ReadByte(); + } + } +}
\ No newline at end of file |