/// Gets the <see cref="IInnerPlayerInfo" /> of the <see cref="IInnerPlayerControl" />.
/// Contains metadata about the player.
/// </summary>
- IInnerPlayerInfo PlayerInfo { get; }
+ IInnerPlayerInfo? PlayerInfo { get; }
/// <summary>
/// Sets the name of the current <see cref="IInnerPlayerControl" />.
writer.Write(hostId);
writer.Write(player.Client.Name);
player.Client.PlatformSpecificData.Serialize(writer);
- writer.WritePacked(player.Character?.PlayerInfo.PlayerLevel ?? 1);
+ writer.WritePacked(player.Character?.PlayerInfo?.PlayerLevel ?? 1);
// ProductUserId and FriendCode are not yet known, so set them to an empty string
writer.Write(string.Empty);
writer.WritePacked(ply.Client.Id);
writer.Write(ply.Client.Name);
ply.Client.PlatformSpecificData.Serialize(writer);
- writer.WritePacked(ply.Character?.PlayerInfo.PlayerLevel ?? 1);
+ writer.WritePacked(ply.Character?.PlayerInfo?.PlayerLevel ?? 1);
// ProductUserId and FriendCode are not yet known, so set them to an empty string
writer.Write(string.Empty);
return true;
}
- protected async ValueTask<bool> ValidateImpostor(CheatContext context, IClientPlayer sender, InnerPlayerInfo playerInfo, bool value = true)
+ protected async ValueTask<bool> ValidateImpostor(CheatContext context, IClientPlayer sender, InnerPlayerInfo? playerInfo, bool value = true)
{
- if (playerInfo.IsImpostor != value)
+ if (playerInfo == null)
+ {
+ if (await sender.Client.ReportCheatAsync(context, CheatCategory.InvalidObject, "Couldn't check if Impostor, playerInfo not set"))
+ {
+ return false;
+ }
+ }
+ else if (playerInfo.IsImpostor != value)
{
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Role, "Failed impostor check"))
{
return true;
}
- protected async ValueTask<bool> ValidateCanVent(CheatContext context, IClientPlayer sender, InnerPlayerInfo playerInfo, bool value = true)
+ protected async ValueTask<bool> ValidateCanVent(CheatContext context, IClientPlayer sender, InnerPlayerInfo? playerInfo, bool value = true)
{
- if (playerInfo.CanVent != value)
+ if (playerInfo == null)
+ {
+ if (await sender.Client.ReportCheatAsync(context, CheatCategory.InvalidObject, "Couldn't check if can vent, playerInfo not set"))
+ {
+ return false;
+ }
+ }
+ else if (playerInfo.CanVent != value)
{
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Role, "Failed can vent check"))
{
return true;
}
- protected async ValueTask<bool> ValidateRole(CheatContext context, IClientPlayer sender, InnerPlayerInfo playerInfo, RoleTypes role)
+ protected async ValueTask<bool> ValidateRole(CheatContext context, IClientPlayer sender, InnerPlayerInfo? playerInfo, RoleTypes role)
{
- if (playerInfo.RoleType != role)
+ if (playerInfo == null)
+ {
+ if (await sender.Client.ReportCheatAsync(context, CheatCategory.InvalidObject, "Couldn't check role, playerInfo not set"))
+ {
+ return false;
+ }
+ }
+ else if (playerInfo.RoleType != role)
{
if (await sender.Client.ReportCheatAsync(context, CheatCategory.Role, $"Failed role = {role} check"))
{
var max = MaxPair(self, out var tie);
var exiled = tie ? null : Game.GameNet.GameData!.GetPlayerById(max.Key)?.Controller;
- if (exiled != null)
+ if (exiled != null && exiled.PlayerInfo != null)
{
exiled.PlayerInfo.LastDeathReason = DeathReason.Exile;
await _eventManager.CallAsync(new PlayerExileEvent(Game, Game.GetClientPlayer(exiled!.OwnerId)!, exiled));
IInnerCustomNetworkTransform IInnerPlayerControl.NetworkTransform => NetworkTransform;
- IInnerPlayerInfo IInnerPlayerControl.PlayerInfo => PlayerInfo;
+ IInnerPlayerInfo? IInnerPlayerControl.PlayerInfo => PlayerInfo;
public async ValueTask SetNameAsync(string name)
{
+ if (PlayerInfo == null)
+ {
+ throw new ImpostorProtocolException("Cannot set name, PlayerInfo is null");
+ }
+
PlayerInfo.CurrentOutfit.PlayerName = name;
using var writer = Game.StartRpc(NetId, RpcCalls.SetName);
public async ValueTask SetColorAsync(ColorType color)
{
+ if (PlayerInfo == null)
+ {
+ throw new ImpostorProtocolException("Cannot set color, PlayerInfo is null");
+ }
+
PlayerInfo.CurrentOutfit.Color = color;
using var writer = Game.StartRpc(NetId, RpcCalls.SetColor);
public async ValueTask SetHatAsync(string hatId)
{
+ if (PlayerInfo == null)
+ {
+ throw new ImpostorProtocolException("Cannot set hat, PlayerInfo is null");
+ }
+
PlayerInfo.CurrentOutfit.HatId = hatId;
using var writer = Game.StartRpc(NetId, RpcCalls.SetHatStr);
public async ValueTask SetPetAsync(string petId)
{
+ if (PlayerInfo == null)
+ {
+ throw new ImpostorProtocolException("Cannot set pet, PlayerInfo is null");
+ }
+
PlayerInfo.CurrentOutfit.PetId = petId;
using var writer = Game.StartRpc(NetId, RpcCalls.SetPetStr);
public async ValueTask SetSkinAsync(string skinId)
{
+ if (PlayerInfo == null)
+ {
+ throw new ImpostorProtocolException("Cannot set skin, PlayerInfo is null");
+ }
+
PlayerInfo.CurrentOutfit.SkinId = skinId;
using var writer = Game.StartRpc(NetId, RpcCalls.SetSkinStr);
public async ValueTask SetVisorAsync(string visorId)
{
+ if (PlayerInfo == null)
+ {
+ throw new ImpostorProtocolException("Cannot set visor, PlayerInfo is null");
+ }
+
PlayerInfo.CurrentOutfit.VisorId = visorId;
using var writer = Game.StartRpc(NetId, RpcCalls.SetVisorStr);
public async ValueTask SetNamePlateAsync(string nameplateId)
{
+ if (PlayerInfo == null)
+ {
+ throw new ImpostorProtocolException("Cannot set nameplate, PlayerInfo is null");
+ }
+
PlayerInfo.CurrentOutfit.NamePlateId = nameplateId;
using var writer = Game.StartRpc(NetId, RpcCalls.SetNamePlateStr);
private bool ValidateMurderPlayer(IInnerPlayerControl target, MurderResultFlags result, [NotNullWhen(false)] out string? invalidReason)
{
- if (!PlayerInfo.IsImpostor)
+ if (PlayerInfo == null)
+ {
+ invalidReason = "Tried to murder a player, but the murderer didn't have a playerinfo";
+ }
+ else if (!PlayerInfo.IsImpostor)
{
invalidReason = "Tried to murder a player, but murderer was not an impostor.";
}
{
invalidReason = "Tried to murder a player, but murderer was not alive.";
}
+ else if (target.PlayerInfo == null)
+ {
+ invalidReason = "Tried to murder a player, but the murderer didn't have a playerinfo";
+ }
else if (target.PlayerInfo.IsImpostor)
{
invalidReason = "Tried to murder a player, but target is an impostor";
public async ValueTask ProtectPlayerAsync(IInnerPlayerControl target)
{
- if (target.PlayerInfo.IsDead)
+ if (target.PlayerInfo == null)
+ {
+ throw new ImpostorProtocolException("Tried to exile a player, but target didn't have a playerinfo");
+ }
+ else if (target.PlayerInfo.IsDead)
{
throw new ImpostorProtocolException("Tried to protect a player that is dead");
}
((InnerPlayerControl)target).Protect(this);
using var writer = Game.StartRpc(NetId, RpcCalls.ProtectPlayer);
- Rpc45ProtectPlayer.Serialize(writer, target, PlayerInfo.CurrentOutfit.Color);
+ Rpc45ProtectPlayer.Serialize(writer, target, PlayerInfo?.CurrentOutfit.Color ?? ColorType.Red);
await Game.FinishRpcAsync(writer);
}
public async ValueTask ExileAsync()
{
- if (PlayerInfo.IsDead)
+ if (PlayerInfo == null)
+ {
+ throw new ImpostorProtocolException("Tried to exile a player, but target didn't have a playerinfo");
+ }
+ else if (PlayerInfo.IsDead)
{
throw new ImpostorProtocolException("Tried to exile a player, but target was not alive.");
}
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using Impostor.Api;
public InnerCustomNetworkTransform NetworkTransform { get; }
- [AllowNull]
- public InnerPlayerInfo PlayerInfo { get; internal set; }
+ public InnerPlayerInfo? PlayerInfo { get; internal set; }
internal Queue<string> RequestedPlayerName { get; } = new Queue<string>();
Rpc44SetRole.Deserialize(reader, out var role, out var _);
- if (role is RoleTypes.ImpostorGhost or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel)
+ if (PlayerInfo == null)
{
- PlayerInfo.RoleWhenAlive = PlayerInfo.RoleType;
- PlayerInfo.IsDead = true;
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetRole, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
+ {
+ return false;
+ }
}
+ else
+ {
+ if (role is RoleTypes.ImpostorGhost or RoleTypes.CrewmateGhost or RoleTypes.GuardianAngel)
+ {
+ PlayerInfo.RoleWhenAlive = PlayerInfo.RoleType;
+ PlayerInfo.IsDead = true;
+ }
- PlayerInfo.RoleType = role;
+ PlayerInfo.RoleType = role;
+ }
- if (Game.GameState == GameStates.Starting && Game.Players.All(clientPlayer => clientPlayer.Character?.PlayerInfo.RoleType != null))
+ if (Game.GameState == GameStates.Starting && Game.Players.All(clientPlayer => clientPlayer.Character?.PlayerInfo?.RoleType != null))
{
await Game.StartedAsync();
}
internal void Die(DeathReason reason)
{
- PlayerInfo.IsDead = true;
- PlayerInfo.LastDeathReason = reason;
+ if (PlayerInfo == null)
+ {
+ // "Custom Net Objects" aka hacked up PlayerControl objects as popularized by host only mods may trigger this
+ if (!Game.IsHostAuthoritive)
+ {
+ _logger.LogWarning("Tried to kill player that didn't have a PlayerInfo set, this shouldn't happen");
+ }
+ }
+ else
+ {
+ PlayerInfo.IsDead = true;
+ PlayerInfo.LastDeathReason = reason;
+ }
}
internal void Protect(InnerPlayerControl guardianAngel)
private async ValueTask HandleCompleteTask(ClientPlayer sender, uint taskId)
{
- var task = PlayerInfo.Tasks.ElementAtOrDefault((int)taskId);
+ TaskInfo? task = null;
+ if (PlayerInfo == null)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.CompleteTask, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
+ {
+ return;
+ }
+ }
+ else
+ {
+ task = PlayerInfo.Tasks.ElementAtOrDefault((int)taskId);
+ }
if (task != null)
{
if (sender.IsOwner(this))
{
- if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == name))
+ if (Game.Players.Any(x => x.Character != null &&
+ x.Character != this &&
+ x.Character.PlayerInfo != null &&
+ x.Character.PlayerInfo.PlayerName == name))
{
if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, CheatCategory.NameLimits, "Client sent name that is already used"))
{
var expected = RequestedPlayerName.Dequeue();
var requested = expected;
- if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == expected))
+ if (Game.Players.Any(x => x.Character != null &&
+ x.Character != this &&
+ x.Character.PlayerInfo != null &&
+ x.Character.PlayerInfo.PlayerName == expected))
{
var i = 1;
while (true)
{
var text = expected + " " + i;
- if (Game.Players.All(x => x.Character == null || x.Character == this || x.Character.PlayerInfo.PlayerName != text))
+ if (Game.Players.All(x => x.Character == null ||
+ x.Character == this ||
+ x.Character.PlayerInfo == null ||
+ x.Character.PlayerInfo.PlayerName != text))
{
expected = text;
break;
}
}
- PlayerInfo.CurrentOutfit.PlayerName = name;
+ if (PlayerInfo == null)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ PlayerInfo.CurrentOutfit.PlayerName = name;
+ }
return true;
}
}
}
- PlayerInfo.CurrentOutfit.Color = color;
+ if (PlayerInfo == null)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ PlayerInfo.CurrentOutfit.Color = color;
+ }
// Record the color so it can be restored on the next game
if (Game.TryGetPlayer(OwnerId, out var clientPlayer))
return false;
}
- PlayerInfo.CurrentOutfit.HatId = hat;
- PlayerInfo.CurrentOutfit.HatSequenceId = nextRpcSequenceId;
+ if (PlayerInfo == null)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetHatStr, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ PlayerInfo.CurrentOutfit.HatId = hat;
+ PlayerInfo.CurrentOutfit.HatSequenceId = nextRpcSequenceId;
+ }
return true;
}
return false;
}
- PlayerInfo.CurrentOutfit.SkinId = skin;
- PlayerInfo.CurrentOutfit.SkinSequenceId = nextRpcSequenceId;
+ if (PlayerInfo == null)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetSkinStr, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ PlayerInfo.CurrentOutfit.SkinId = skin;
+ PlayerInfo.CurrentOutfit.SkinSequenceId = nextRpcSequenceId;
+ }
return true;
}
return false;
}
- PlayerInfo.CurrentOutfit.VisorId = visor;
- PlayerInfo.CurrentOutfit.VisorSequenceId = nextRpcSequenceId;
+ if (PlayerInfo == null)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetVisorStr, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ PlayerInfo.CurrentOutfit.VisorId = visor;
+ PlayerInfo.CurrentOutfit.VisorSequenceId = nextRpcSequenceId;
+ }
return true;
}
return false;
}
- PlayerInfo.CurrentOutfit.NamePlateId = namePlate;
- PlayerInfo.CurrentOutfit.NamePlateSequenceId = nextRpcSequenceId;
+ if (PlayerInfo == null)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetNamePlateStr, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ PlayerInfo.CurrentOutfit.NamePlateId = namePlate;
+ PlayerInfo.CurrentOutfit.NamePlateSequenceId = nextRpcSequenceId;
+ }
return true;
}
return false;
}
- PlayerInfo.PlayerLevel = level;
+ if (PlayerInfo == null)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetLevel, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ PlayerInfo.PlayerLevel = level;
+ }
return true;
}
private async ValueTask<bool> HandleCheckMurder(ClientPlayer sender, InnerPlayerControl? target)
{
- if (!PlayerInfo.CanMurder(Game, _dateTimeProvider))
+ if (PlayerInfo == null)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.CheckMurder, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
+ {
+ return false;
+ }
+ }
+ else if (!PlayerInfo.CanMurder(Game, _dateTimeProvider))
{
if (IsMurdering == target)
{
return false;
}
}
- }
- PlayerInfo.LastMurder = _dateTimeProvider.UtcNow - TimeSpan.FromMilliseconds(sender.Client.Connection.AveragePing);
- IsMurdering = target;
+ PlayerInfo.LastMurder = _dateTimeProvider.UtcNow - TimeSpan.FromMilliseconds(sender.Client.Connection.AveragePing);
+ IsMurdering = target;
+ }
// Check if host authority mode is on
if (_game.IsHostAuthoritive)
}
}
- if (target != null && !target.PlayerInfo.IsDead)
+ if (target != null && target.PlayerInfo != null && !target.PlayerInfo.IsDead)
{
// In host authoritive mode every client has to figure out if the kill was prevented by guardian protection on it's own
if ((result & MurderResultFlags.Succeeded) != 0 && target.IsProtected)
return false;
}
- PlayerInfo.CurrentOutfit.PetId = pet;
- PlayerInfo.CurrentOutfit.PetSequenceId = nextRpcSequenceId;
+ if (PlayerInfo == null)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetPetStr, CheatCategory.InvalidObject, "PlayerControl doesn't have PlayerInfo"))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ PlayerInfo.CurrentOutfit.PetId = pet;
+ PlayerInfo.CurrentOutfit.PetSequenceId = nextRpcSequenceId;
+ }
return true;
}
/// <returns>True if there is player other than exceptBy that uses that color.</returns>
internal bool IsColorUsed(ColorType color, IInnerPlayerControl? exceptBy = null)
{
- return Players.Any(p => p.Character != null && p.Character != exceptBy && p.Character.PlayerInfo.CurrentOutfit.Color == color);
+ return Players.Any(p => p.Character != null &&
+ p.Character != exceptBy &&
+ p.Character.PlayerInfo != null &&
+ p.Character.PlayerInfo.CurrentOutfit.Color == color);
}
private ValueTask BroadcastJoinMessage(IMessageWriter message, bool clear, ClientPlayer player)