internal partial class InnerGameData : IInnerGameData
{
private readonly ConcurrentDictionary<byte, InnerPlayerInfo> _allPlayers = new();
+ private readonly ConcurrentDictionary<int, InnerPlayerInfo> _allPlayersByClientId = new();
public int PlayerCount => _allPlayers.Count;
public IReadOnlyDictionary<byte, InnerPlayerInfo> Players => _allPlayers;
+ internal IReadOnlyDictionary<int, InnerPlayerInfo> PlayersByClientId => _allPlayersByClientId;
+
public InnerPlayerInfo? GetPlayerById(byte id)
{
if (id == byte.MaxValue)
internal bool AddPlayer(InnerPlayerInfo playerInfo)
{
- return _allPlayers.TryAdd(playerInfo.PlayerId, playerInfo);
+ return _allPlayers.TryAdd(playerInfo.PlayerId, playerInfo) &&
+ _allPlayersByClientId.TryAdd(playerInfo.ClientId, playerInfo);
}
internal void RemovePlayer(byte playerId)
{
- _allPlayers.TryRemove(playerId, out _);
+ _allPlayers.TryRemove(playerId, out var player);
+
+ if (player != null)
+ {
+ _allPlayersByClientId.TryRemove(player.ClientId, out _);
+ }
+ }
+
+ internal byte GetNextAvailablePlayerId()
+ {
+ for (byte i = 0; i < byte.MaxValue; i++)
+ {
+ if (!Players.ContainsKey(i))
+ {
+ return i;
+ }
+ }
+
+ return byte.MaxValue;
}
}
}
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Innersloth;
/// </summary>
private const int CurrentClient = -3;
+ /// <summary>
+ /// Used to list objects that are managed by the game server.
+ /// </summary>
+ private const int ServerOwned = -4;
+
+ /// <summary>
+ /// The first NetId that is considered as a server owned Network ID that the client will not allocate by default.
+ /// </summary>
+ private const int MinServerNetId = 100000;
+
private static readonly Dictionary<uint, Type> SpawnableObjects = new()
{
[0] = typeof(InnerSkeldShipStatus),
[13] = typeof(InnerFungleShipStatus),
};
+ private static readonly Dictionary<Type, uint> SpawnableObjectIds = SpawnableObjects.ToDictionary((i) => i.Value, (i) => i.Key);
+
private readonly List<InnerNetObject> _allObjects = new List<InnerNetObject>();
private readonly Dictionary<uint, InnerNetObject> _allObjectsFast = new Dictionary<uint, InnerNetObject>();
+ private uint _nextNetId = MinServerNetId;
+
public T? FindObjectByNetId<T>(uint netId)
where T : IInnerNetObject
{
sender.Scene = scene;
_logger.LogTrace("> Scene {0} to {1}", clientId, sender.Scene);
+
+ await SyncServerObjectsAsync(sender);
+ await SpawnPlayerInfoAsync(sender);
+
break;
}
}
}
+ private async ValueTask SyncServerObjectsAsync(ClientPlayer sender)
+ {
+ foreach (var obj in _allObjectsFast.Values)
+ {
+ if (obj.OwnerId == ServerOwned)
+ {
+ _logger.LogTrace("Syncing {Type} {NetId}", obj.GetType(), obj.NetId);
+ await SendObjectSpawnAsync(obj, sender.Client.Id);
+ }
+ }
+ }
+
+ private async ValueTask SpawnPlayerInfoAsync(ClientPlayer sender)
+ {
+ // Hosts spawn PlayerInfo objects if they requested authority
+ if (IsHostAuthoritive)
+ {
+ return;
+ }
+
+ // Only spawn a new PlayerInfo if one has not yet been spawned
+ if (GameNet.GameData.PlayersByClientId.ContainsKey(sender.Client.Id))
+ {
+ return;
+ }
+
+ var playerInfo = (InnerPlayerInfo)ActivatorUtilities.CreateInstance(_serviceProvider, typeof(InnerPlayerInfo), this);
+ playerInfo.SpawnFlags = SpawnFlags.None;
+ playerInfo.NetId = _nextNetId++;
+ playerInfo.OwnerId = ServerOwned;
+ playerInfo.ClientId = sender.Client.Id;
+ playerInfo.PlayerId = GameNet.GameData.GetNextAvailablePlayerId();
+
+ if (!AddNetObject(playerInfo))
+ {
+ _logger.LogError("Couldn't spawn PlayerInfo for {Name} ({ClientId})", sender.Client.Name, sender.Client.Id);
+ playerInfo.NetId = uint.MaxValue;
+ return;
+ }
+
+ _logger.LogTrace("Spawning PlayerInfo (netId {Netid})", playerInfo.NetId);
+ await OnSpawnAsync(sender, playerInfo);
+ await SendObjectSpawnAsync(playerInfo);
+ }
+
private bool AddNetObject(InnerNetObject obj)
{
if (_allObjectsFast.ContainsKey(obj.NetId))
{
Message12WaitForHostS2C.Serialize(message, clear, Code, player.Client.Id);
}
+
+ private async ValueTask SendObjectSpawnAsync(InnerNetObject obj, int? targetClientId = null)
+ {
+ using var writer = StartGameData(targetClientId);
+ writer.StartMessage(GameDataTag.SpawnFlag);
+ writer.WritePacked(SpawnableObjectIds[obj.GetType()]);
+ writer.WritePacked(obj.OwnerId);
+ writer.Write((byte)obj.SpawnFlags);
+
+ var components = obj.GetComponentsInChildren<InnerNetObject>();
+ writer.WritePacked(components.Count);
+ foreach (var component in components)
+ {
+ writer.WritePacked(obj.NetId);
+ writer.StartMessage(1);
+ await component.SerializeAsync(writer, true);
+ writer.EndMessage();
+ }
+
+ writer.EndMessage();
+ await FinishGameDataAsync(writer, targetClientId);
+ }
}
}