In 2024.6.18 Innersloth promoted PlayerInfo to its own network object, and gave it to the server. InnerGameData is now a container that holds these players. This commit implements the changes necessary to handle these separate PlayerInfo network objects.
namespace Impostor.Api.Net.Inner.Objects
{
- public interface IInnerGameData : IInnerNetObject
+ public interface IInnerGameData
{
}
}
-using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Threading.Tasks;
-using Impostor.Api;
-using Impostor.Api.Events.Managers;
-using Impostor.Api.Net;
-using Impostor.Api.Net.Custom;
-using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Inner.Objects;
-using Impostor.Api.Net.Messages.Rpcs;
-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
+ internal partial class InnerGameData : IInnerGameData
{
- private readonly ILogger<InnerGameData> _logger;
- private readonly IEventManager _eventManager;
- private readonly ConcurrentDictionary<byte, InnerPlayerInfo> _allPlayers;
-
- public InnerGameData(ICustomMessageManager<ICustomRpc> customMessageManager, Game game, ILogger<InnerGameData> logger, IEventManager eventManager, IServiceProvider serviceProvider) : base(customMessageManager, game)
- {
- _logger = logger;
- _eventManager = eventManager;
- _allPlayers = new ConcurrentDictionary<byte, InnerPlayerInfo>();
-
- Components.Add(this);
- Components.Add(ActivatorUtilities.CreateInstance<InnerVoteBanSystem>(serviceProvider, game));
- }
+ private readonly ConcurrentDictionary<byte, InnerPlayerInfo> _allPlayers = new();
public int PlayerCount => _allPlayers.Count;
return _allPlayers.TryGetValue(id, out var player) ? player : null;
}
- public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
- {
- throw new NotImplementedException();
- }
-
- public override async ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
- {
- if (!await ValidateHost(CheatContext.Deserialize, sender))
- {
- return;
- }
-
- while (reader.Position < reader.Length)
- {
- var inner = reader.ReadMessage();
- var playerInfo = this.GetPlayerById(inner.Tag);
- if (playerInfo != null)
- {
- playerInfo.Deserialize(inner);
- }
- else
- {
- playerInfo = new InnerPlayerInfo(inner.Tag);
- playerInfo.Deserialize(inner);
-
- if (!_allPlayers.TryAdd(playerInfo.PlayerId, playerInfo))
- {
- throw new ImpostorException("Failed to add player to InnerGameData.");
- }
- }
- }
- }
-
- public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
- {
- if (!await ValidateHost(call, sender))
- {
- return false;
- }
-
- switch (call)
- {
- case RpcCalls.SetTasks:
- {
- Rpc29SetTasks.Deserialize(reader, out var playerId, out var taskTypeIds);
- SetTasks(playerId, taskTypeIds);
- break;
- }
-
- default:
- return await base.HandleRpcAsync(sender, target, call, reader);
- }
-
- return true;
- }
-
- internal InnerPlayerInfo? AddPlayer(InnerPlayerControl control)
+ internal bool AddPlayer(InnerPlayerInfo playerInfo)
{
- var playerId = control.PlayerId;
- var playerInfo = new InnerPlayerInfo(control.PlayerId);
-
- if (_allPlayers.TryAdd(playerId, playerInfo))
- {
- return playerInfo;
- }
-
- return null;
+ return _allPlayers.TryAdd(playerInfo.PlayerId, playerInfo);
}
- internal void RemovePlayer(InnerPlayerControl control)
+ internal void RemovePlayer(byte playerId)
{
- _allPlayers.TryRemove(control.PlayerInfo.PlayerId, out _);
- }
-
- 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);
-
- var taskId = 0u;
- foreach (var taskTypeId in taskTypeIds.Span)
- {
- var mapTasks = Game.GameNet!.ShipStatus?.Data.Tasks;
- var taskType = (mapTasks != null && mapTasks.ContainsKey(taskTypeId)) ? mapTasks[taskTypeId] : null;
- player.Tasks.Add(new TaskInfo(player, _eventManager, taskId++, taskType));
- }
+ _allPlayers.TryRemove(playerId, out _);
}
}
}
namespace Impostor.Server.Net.Inner.Objects
{
- internal partial class InnerPlayerInfo : IInnerPlayerInfo
+ internal partial class InnerPlayerInfo : InnerNetObject, IInnerPlayerInfo
{
IEnumerable<ITaskInfo> IInnerPlayerInfo.Tasks => Tasks;
}
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using Impostor.Api.Games;
using Impostor.Api.Innersloth;
using Impostor.Api.Innersloth.Customization;
using Impostor.Api.Innersloth.GameOptions;
+using Impostor.Api.Net;
+using Impostor.Api.Net.Custom;
using Impostor.Api.Utils;
+using Impostor.Server.Net.State;
+using Microsoft.Extensions.Logging;
namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerPlayerInfo
{
- public InnerPlayerInfo(byte playerId)
+ private readonly ILogger<InnerPlayerInfo> _logger;
+
+ public InnerPlayerInfo(ICustomMessageManager<ICustomRpc> customMessageManager, Game game, ILogger<InnerPlayerInfo> logger) : base(customMessageManager, game)
{
- PlayerId = playerId;
+ Components.Add(this);
+ _logger = logger;
}
public InnerPlayerControl? Controller { get; internal set; }
- public byte PlayerId { get; }
+ public byte PlayerId { get; internal set; }
+
+ public int ClientId { get; internal set; }
public string PlayerName { get; internal set; } = string.Empty;
public RoleTypes? RoleType { get; internal set; }
+ public RoleTypes? RoleWhenAlive { get; internal set; }
+
public bool Disconnected { get; internal set; }
public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.ImpostorGhost;
}
}
- public void Serialize(IMessageWriter writer)
+ public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
- throw new NotImplementedException();
+ writer.Write(PlayerId);
+ writer.WritePacked(ClientId);
+
+ writer.Write((byte)Outfits.Count);
+ foreach (var outfit in Outfits)
+ {
+ writer.Write((byte)outfit.Key);
+ outfit.Value.Serialize(writer);
+ }
+
+ writer.WritePacked(PlayerLevel);
+
+ var flag = 0;
+ if (Disconnected)
+ {
+ flag = (byte)(flag | 1u);
+ }
+
+ if (IsDead)
+ {
+ flag = (byte)(flag | 4u);
+ }
+
+ writer.Write((byte)flag);
+
+ writer.Write((ushort)(RoleType ?? 0));
+ writer.Write(RoleWhenAlive.HasValue);
+ if (RoleWhenAlive.HasValue)
+ {
+ writer.Write((ushort)RoleWhenAlive.Value);
+ }
+
+ writer.Write((byte)Tasks.Count);
+ for (var i = 0; i < Tasks.Count; i++)
+ {
+ Tasks[i].Serialize(writer);
+ }
+
+ writer.Write(string.Empty); // FriendCode
+ writer.Write(string.Empty); // PUID
+ return new ValueTask<bool>(true);
}
- public void Deserialize(IMessageReader reader)
+ public override ValueTask DeserializeAsync(IClientPlayer sender, IClientPlayer? target, IMessageReader reader, bool initialState)
{
+ PlayerId = reader.ReadByte();
+ ClientId = reader.ReadPackedInt32();
+
Outfits.Clear();
var b = reader.ReadByte();
for (var i = 0; i < b; i++)
Disconnected = (flag & 1) != 0;
IsDead = (flag & 4) != 0;
- _ = (RoleTypes)reader.ReadUInt16(); // ignore the RoleType here and only trust the SetRole rpc
+ // Ignore the RoleType here and only trust the SetRole RPC, as
+ // RoleType is not nullable in vanilla, while Impostor checks game
+ // starts based on assigned roles.
+ _ = (RoleTypes)reader.ReadUInt16();
+
+ if (reader.ReadBoolean())
+ {
+ RoleWhenAlive = (RoleTypes)reader.ReadUInt16();
+ }
var taskCount = reader.ReadByte();
{
Tasks[i].Deserialize(reader);
}
+
+ // Impostor doesn't expose fields that aren't properly validated
+ reader.ReadString(); // FriendCode
+ reader.ReadString(); // PUID
+
+ return ValueTask.CompletedTask;
}
}
}
[0] = typeof(InnerSkeldShipStatus),
[1] = typeof(InnerMeetingHud),
[2] = typeof(InnerLobbyBehaviour),
- [3] = typeof(InnerGameData),
[4] = typeof(InnerPlayerControl),
[5] = typeof(InnerMiraShipStatus),
[6] = typeof(InnerPolusShipStatus),
[8] = typeof(InnerAirshipStatus),
[9] = typeof(InnerHideAndSeekManager),
[10] = typeof(InnerNormalGameManager),
+ [11] = typeof(InnerPlayerInfo),
+ [12] = typeof(InnerVoteBanSystem),
[13] = typeof(InnerFungleShipStatus),
};
break;
}
- case InnerGameData data:
+ case InnerPlayerInfo playerInfo:
{
- GameNet.GameData = data;
+ if (!GameNet.GameData.AddPlayer(playerInfo))
+ {
+ _logger.LogWarning(
+ "Could not add PlayerInfo for playerId {PlayerId} with NetId {newId}, already have NetId {oldNetId}",
+ playerInfo.PlayerId,
+ playerInfo.NetId,
+ GameNet.GameData.GetPlayerById(playerInfo.PlayerId)?.NetId);
+ }
+
break;
}
}
// Hook up InnerPlayerControl <-> InnerPlayerControl.PlayerInfo.
- var playerInfo = GameNet.GameData!.GetPlayerById(control.PlayerId) ?? GameNet.GameData.AddPlayer(control);
+ var playerInfo = GameNet.GameData.GetPlayerById(control.PlayerId);
if (playerInfo != null)
{
break;
}
- case InnerGameData:
- {
- GameNet.GameData = null;
- break;
- }
-
case InnerVoteBanSystem:
{
GameNet.VoteBan = null;
break;
}
- case InnerPlayerControl control:
+ case InnerPlayerInfo playerInfo:
{
if (GameState != GameStates.Started && GameState != GameStates.Starting)
{
- GameNet.GameData?.RemovePlayer(control);
+ GameNet.GameData.RemovePlayer(playerInfo.PlayerId);
}
+ break;
+ }
+
+ case InnerPlayerControl control:
+ {
// Remove InnerPlayerControl <-> IClientPlayer.
if (TryGetPlayer(control.OwnerId, out var player))
{
IInnerLobbyBehaviour? IGameNet.LobbyBehaviour => LobbyBehaviour;
- IInnerGameData? IGameNet.GameData => GameData;
+ IInnerGameData IGameNet.GameData => GameData;
IInnerVoteBanSystem? IGameNet.VoteBan => VoteBan;
public InnerLobbyBehaviour? LobbyBehaviour { get; internal set; }
- public InnerGameData? GameData { get; internal set; }
+ public InnerGameData GameData { get; internal set; } = new();
public InnerVoteBanSystem? VoteBan { get; internal set; }