Пример #1
0
        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;

            _logger.LogTrace($"{sender.Character.NetworkTransform.Position.X}; {sender.Character.NetworkTransform.Position.Y}");
            _logger.LogTrace($"{ventId}");

            await _eventManager.CallAsync(new PlayerVentEvent(_game, sender, _playerControl, (VentLocation)ventId, ventEnter));

            return;
        }
Пример #2
0
        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);
        private async ValueTask <bool> HandleSetColor(ClientPlayer sender, ColorType color)
        {
            if (Game.GameState == GameStates.Started)
            {
                if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, "Client tried to set a color midgame"))
                {
                    return(false);
                }
            }

            if (sender.IsOwner(this))
            {
                if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.Color == color))
                {
                    if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, "Client sent a color that is already used"))
                    {
                        return(false);
                    }
                }
            }
            else
            {
                if (!RequestedColorId.Any())
                {
                    _logger.LogWarning($"Client sent {nameof(RpcCalls.SetColor)} for a player that didn't request it");
                    return(false);
                }

                ColorType expected = RequestedColorId.Dequeue();

                while (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.Color == expected))
                {
                    expected = (ColorType)(((byte)expected + 1) % ColorsCount);
                }

                if (color != expected)
                {
                    _logger.LogWarning($"Client sent {nameof(RpcCalls.SetColor)} with incorrect color");
                    await SetColorAsync(expected);

                    return(false);
                }
            }

            PlayerInfo.Color = color;

            return(true);
        }
Пример #4
0
        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;
            }
            }
        }
Пример #5
0
        protected async ValueTask <bool> TestRpc(ClientPlayer sender, ClientPlayer?target, RpcCalls call, Dictionary <RpcCalls, RpcInfo> rpcs)
        {
            if (call == RpcCalls.CustomRpc)
            {
                return(true);
            }

            if (rpcs.TryGetValue(call, out var rpc))
            {
                if (rpc.CheckOwnership && !sender.IsOwner(this))
                {
                    if (await sender.Client.ReportCheatAsync(call, $"Client sent {call} to an unowned {GetType().Name}"))
                    {
                        return(false);
                    }
                }

                if (rpc.RequireHost && !sender.IsHost)
                {
                    if (await sender.Client.ReportCheatAsync(call, $"Client attempted to send {call} as non-host"))
                    {
                        return(false);
                    }
                }

                switch (rpc.TargetType)
                {
                case RpcTargetType.Target when target == null:
                {
                    if (await sender.Client.ReportCheatAsync(call, $"Client sent {call} as a broadcast instead to specific player"))
                    {
                        return(false);
                    }

                    break;
                }

                case RpcTargetType.Broadcast when target != null:
                {
                    if (await sender.Client.ReportCheatAsync(call, $"Client sent {call} to a specific player instead of broadcast"))
                    {
                        return(false);
                    }

                    break;
                }

                case RpcTargetType.Cmd when target == null || !target.IsHost:
                {
                    if (await sender.Client.ReportCheatAsync(call, $"Client sent {call} to the wrong player"))
                    {
                        return(false);
                    }

                    break;
                }

                case RpcTargetType.Both:
                    break;
                }

                return(true);
            }

            if (await sender.Client.ReportCheatAsync(call, "Client sent unregistered call"))
            {
                return(false);
            }

            return(true);
        }
Пример #6
0
        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 (!sender.IsOwner(this))
                {
                    // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckName)} to an unowned {nameof(InnerPlayerControl)}");
                }

                if (target == null || !target.IsHost)
                {
                    // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckName)} to the wrong player");
                }

                var name = reader.ReadString();

                if (name.Length > 10)
                {
                    // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckName)} with name exceeding 10 characters");
                }

                if (string.IsNullOrWhiteSpace(name) || !name.All(TextBox.IsCharAllowed))
                {
                    // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckName)} with name containing illegal characters");
                }

                if (sender.Client.Name != name)
                {
                    // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} with name not matching his name from handshake");
                }

                PlayerInfo.RequestedPlayerName = name;
                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");
                }

                var name = reader.ReadString();

                if (sender.IsOwner(this))
                {
                    if (_game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == name))
                    {
                        // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} with a name that is already used");
                    }

                    if (sender.Client.Name != name)
                    {
                        // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} with name not matching his name from handshake");
                    }
                }
                else
                {
                    if (PlayerInfo.RequestedPlayerName == null)
                    {
                        // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} for a player that didn't request it");
                    }

                    var expected = PlayerInfo.RequestedPlayerName !;

                    if (_game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == expected))
                    {
                        var i = 1;
                        while (true)
                        {
                            string text = expected + " " + i;

                            if (_game.Players.All(x => x.Character == null || x.Character == this || x.Character.PlayerInfo.PlayerName != text))
                            {
                                expected = text;
                                break;
                            }

                            i++;
                        }
                    }

                    if (name != expected)
                    {
                        // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetName)} with incorrect name");
                    }
                }

                PlayerInfo.PlayerName          = name;
                PlayerInfo.RequestedPlayerName = null;
                break;
            }

            // Validates the color at the host.
            case RpcCalls.CheckColor:
            {
                if (!sender.IsOwner(this))
                {
                    // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckColor)} to an unowned {nameof(InnerPlayerControl)}");
                }

                if (target == null || !target.IsHost)
                {
                    // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckColor)} to the wrong player");
                }

                var color = reader.ReadByte();

                if (color > Enum.GetValues <ColorType>().Length)
                {
                    // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.CheckColor)} with invalid color");
                }

                PlayerInfo.RequestedColorId = color;
                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");
                }

                var color = reader.ReadByte();

                if (sender.IsOwner(this))
                {
                    if (_game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.ColorId == color))
                    {
                        // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} with a color that is already used");
                    }
                }
                else
                {
                    if (PlayerInfo.RequestedColorId == null)
                    {
                        // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} for a player that didn't request it");
                        break;
                    }

                    var expected = PlayerInfo.RequestedColorId !.Value;

                    while (_game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.ColorId == expected))
                    {
                        expected = (byte)((expected + 1) % Enum.GetValues <ColorType>().Length);
                    }

                    if (color != expected)
                    {
                        // throw new ImpostorCheatException($"Client sent {nameof(RpcCalls.SetColor)} with incorrect color");
                    }
                }

                PlayerInfo.ColorId          = color;
                PlayerInfo.RequestedColorId = null;
                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;
            }
            }
        }
        private async ValueTask <bool> HandleSetName(ClientPlayer sender, string name)
        {
            if (Game.GameState == GameStates.Started)
            {
                if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, "Client tried to set a name midgame"))
                {
                    return(false);
                }
            }

            if (sender.IsOwner(this))
            {
                if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == name))
                {
                    if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, "Client sent name that is already used"))
                    {
                        return(false);
                    }
                }

                if (sender.Client.Name != name)
                {
                    if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, "Client sent name not matching his name from handshake"))
                    {
                        return(false);
                    }
                }
            }
            else
            {
                if (!RequestedPlayerName.Any())
                {
                    _logger.LogWarning($"Client sent {nameof(RpcCalls.SetName)} for a player that didn't request it");
                    return(false);
                }

                var expected = RequestedPlayerName.Dequeue();

                if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == expected))
                {
                    int i = 1;
                    while (true)
                    {
                        string text = expected + " " + i;

                        if (Game.Players.All(x => x.Character == null || x.Character == this || x.Character.PlayerInfo.PlayerName != text))
                        {
                            expected = text;
                            break;
                        }

                        i++;
                    }
                }

                if (name != expected)
                {
                    _logger.LogWarning($"Client sent {nameof(RpcCalls.SetName)} with incorrect name");
                    await SetNameAsync(expected);

                    return(false);
                }
            }

            PlayerInfo.PlayerName = name;

            return(true);
        }