Compare commits

..
Author SHA1 Message Date
miniduikboot 4452621cd3 Reset PlayerInfo on PlayerControl spawn
When (re)spawning players get a new player control netobject, which is
the perfect moment to discharge them from their former duties and reset
their PlayerInfo to a default state: no role, no tasks and not dead.
2024-08-13 21:18:22 +02:00
miniduikboot 53209aa78f Make PlayerInfo as dirty when setting role/death 2024-08-13 21:16:54 +02:00
miniduikboot c729d26ff6 Sync dirty objects 2024-08-13 21:16:54 +02:00
miniduikboot e2157b0a6f Clean up owned playerinfo's when disconnecting 2024-08-13 21:16:13 +02:00
miniduikboot ad18eda32f Bump to 2024.8.13
Network side seems unchanged and officials allow crossplay
2024-08-13 21:00:51 +02:00
miniduikboot efda5bcef2 Spawn PlayerInfo's if host is not authoritive
This PlayerInfo also needs to be synced over if it is server owned
2024-08-13 21:00:51 +02:00
miniduikboot 6a947457a5 Add NetId to SetName and SetColor
2024.6.18 adds a NetId field *in front* of the existing fields to help link back to the PlayerInfo. We don't actually need this NetId and assume it's called on the correct PlayerControl, so we ignore it.
2024-08-13 21:00:51 +02:00
miniduikboot a3c6ca77c1 Set RoleWhenAlive when set to a Ghost role 2024-08-13 21:00:51 +02:00
miniduikboot 9f3f826b17 Serialize Color -1 as 0 in PlayerOutfit
Official servers send 0 instead of -1 when the outfit is still
incomplete to prevent some client issues.
2024-08-13 21:00:51 +02:00
miniduikboot 36671ba1ba Demote InnerGameData, promote InnerPlayerInfo
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.
2024-08-13 21:00:51 +02:00
miniduikboot ace97ddc1d Add dirty tracking to PlayerOutfit
To prevent excessive modifications in the codebase, which is prone to
issues if a callsite is missed, automatically do this using properties.

Keep the access modifiers intact for now.
2024-08-13 21:00:51 +02:00
miniduikboot 4dc4c90e8b Add sequence id's to playeroutfit 2024-08-13 21:00:51 +02:00
miniduikboot 9379c05676 Add canOverrideRole param to SetRole
This is currently unused by Innersloth
2024-08-13 21:00:51 +02:00
miniduikboot ef98f4d20e Implement GameOptions V8
This adds a field for RulesPresets and SpecialGameModes, as well as
RoleOptions for the new roles.
2024-08-13 21:00:51 +02:00
miniduikboot 9f6947b484 VoteBanSystem now contains itself as a component
This makes it consistent with the other spawnables.
2024-08-13 21:00:51 +02:00
miniduikboot 76ae1a3385 Update Dumpostor to v1.0.1 and run for 6.18
Add the RulesPresets and SpecialGamemodes enums
2024-08-13 21:00:51 +02:00
miniduikboot 3c8a002d6e Prepare for 2024.6.18 update
Major changes are incoming, this doesn't yet work.
2024-08-13 21:00:51 +02:00
21 changed files with 173 additions and 443 deletions

No files matched your search

@@ -28,7 +28,6 @@ namespace Impostor.Api.Net.Messages
public const byte SetActivePodType = 21;
public const byte QueryPlatformIds = 22;
public const byte QueryLobbyInfo = 23;
public const byte EndGameHostMigration = 24;
private static readonly Dictionary<byte, string> FlagCache;
@@ -4,13 +4,15 @@ namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc29SetTasks
{
public static void Serialize(IMessageWriter writer, ReadOnlyMemory<byte> taskTypeIds)
public static void Serialize(IMessageWriter writer, byte playerId, ReadOnlyMemory<byte> taskTypeIds)
{
writer.Write(playerId);
writer.Write(taskTypeIds);
}
public static void Deserialize(IMessageReader reader, out ReadOnlyMemory<byte> taskTypeIds)
public static void Deserialize(IMessageReader reader, out byte playerId, out ReadOnlyMemory<byte> taskTypeIds)
{
playerId = reader.ReadByte();
taskTypeIds = reader.ReadBytesAndSize();
}
}
@@ -1,15 +0,0 @@
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc62CheckVanish
{
public static void Serialize(IMessageWriter writer, float maxDuration)
{
writer.Write(maxDuration);
}
public static void Deserialize(IMessageReader reader, out float maxDuration)
{
maxDuration = reader.ReadSingle();
}
}
}
@@ -1,13 +0,0 @@
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc63StartVanish
{
public static void Serialize(IMessageWriter writer)
{
}
public static void Deserialize(IMessageReader reader)
{
}
}
}
@@ -1,15 +0,0 @@
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc64CheckAppear
{
public static void Serialize(IMessageWriter writer, bool shouldAnimate)
{
writer.Write(shouldAnimate);
}
public static void Deserialize(IMessageReader reader, out bool shouldAnimate)
{
shouldAnimate = reader.ReadBoolean();
}
}
}
@@ -1,15 +0,0 @@
namespace Impostor.Api.Net.Messages.Rpcs
{
public static class Rpc65StartAppear
{
public static void Serialize(IMessageWriter writer, bool shouldAnimate)
{
writer.Write(shouldAnimate);
}
public static void Deserialize(IMessageReader reader, out bool shouldAnimate)
{
shouldAnimate = reader.ReadBoolean();
}
}
}
@@ -1,21 +0,0 @@
using Impostor.Api.Games;
namespace Impostor.Api.Net.Messages.S2C
{
public class Message24EndGameHostMigrationS2C
{
public static void Serialize(IMessageWriter writer, GameCode gameCode, int hostClientId)
{
writer.StartMessage(MessageFlags.EndGameHostMigration);
gameCode.Serialize(writer);
writer.Write(hostClientId);
writer.EndMessage();
}
public static void Deserialize(IMessageReader reader, out GameCode gameCode, out int hostClientId)
{
gameCode = reader.ReadInt32();
hostClientId = reader.ReadInt32();
}
}
}
+5 -20
View File
@@ -82,27 +82,14 @@ namespace Impostor.Server.Net
return false;
}
var supportCode = Random.Shared.Next(0, 999_999).ToString("000-000");
_logger.LogWarning("Client {Name} ({Id}) was caught cheating: [{Context}-{Category}] {Message}", Name, Id, context.Name, category, message);
_logger.LogWarning("Client {Name} ({Id}) was caught cheating: [{SupportCode}] [{Context}-{Category}] {Message}", Name, Id, supportCode, context.Name, category, message);
if (Player is { } player)
if (_antiCheatConfig.BanIpFromGame)
{
if (_antiCheatConfig.BanIpFromGame)
{
player.Game.BanIp(Connection.EndPoint.Address);
}
await player.Game.HandleRemovePlayer(Id, DisconnectReason.Hacking);
Player?.Game.BanIp(Connection.EndPoint.Address);
}
var disconnectMessage =
$"""
You have been caught cheating and were {(_antiCheatConfig.BanIpFromGame ? "banned" : "kicked")} from the lobby.
For questions, contact your server admin and share the following code: {supportCode}.
""";
await DisconnectAsync(DisconnectReason.Custom, disconnectMessage);
await DisconnectAsync(DisconnectReason.Hacking, context.Name + ": " + message);
return true;
}
@@ -361,9 +348,7 @@ namespace Impostor.Server.Net
{
if (Player != null)
{
// The client never sends over the real disconnect reason so we always assume ExitGame
var isRemote = reason == "The remote sent a disconnect request";
await Player.Game.HandleRemovePlayer(Id, isRemote ? DisconnectReason.ExitGame : DisconnectReason.Error);
await Player.Game.HandleRemovePlayer(Id, DisconnectReason.ExitGame);
}
}
catch (Exception ex)
@@ -29,6 +29,8 @@ namespace Impostor.Server.Net.Inner
public SpawnFlags SpawnFlags { get; internal set; }
public virtual bool IsDirty { get; internal set; }
public bool IsOwnedBy(IClientPlayer player)
{
return OwnerId == player.Client.Id ||
@@ -0,0 +1,39 @@
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Server.Events.Player;
namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerGameData
{
public partial class TaskInfo
{
public async ValueTask CompleteAsync()
{
if (_playerInfo.Controller == null)
{
throw new ImpostorException("Can't complete a task that doesn't have a player assigned");
}
var player = _playerInfo.Controller;
if (Complete)
{
throw new ImpostorException("Can't complete a task that is already completed");
}
Complete = true;
// Send RPC.
using var writer = player.Game.StartRpc(player.NetId, RpcCalls.CompleteTask);
Rpc01CompleteTask.Serialize(writer, Id);
await player.Game.FinishRpcAsync(writer);
// Notify plugins.
await _eventManager.CallAsync(new PlayerCompletedTaskEvent(player.Game, player.Game.GetClientPlayer(player.OwnerId)!, player, this));
}
}
}
}
@@ -0,0 +1,41 @@
using Impostor.Api.Events.Managers;
using Impostor.Api.Innersloth.Maps;
using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerGameData
{
public partial class TaskInfo : ITaskInfo
{
private readonly InnerPlayerInfo _playerInfo;
private readonly IEventManager _eventManager;
public TaskInfo(InnerPlayerInfo playerInfo, IEventManager eventManager, uint id, TaskData? task)
{
_playerInfo = playerInfo;
_eventManager = eventManager;
Id = id;
Task = task;
}
public uint Id { get; internal set; }
public TaskData? Task { get; internal set; }
public bool Complete { get; internal set; }
public void Serialize(IMessageWriter writer)
{
writer.WritePacked(Id);
writer.Write(Complete);
}
public void Deserialize(IMessageReader reader)
{
Id = reader.ReadPackedUInt32();
Complete = reader.ReadBoolean();
}
}
}
}
@@ -22,6 +22,7 @@ namespace Impostor.Server.Net.Inner.Objects
{
PlayerInfo.CurrentOutfit.PlayerName = name;
// TODO check playerinfo ownership
using var writer = Game.StartRpc(NetId, RpcCalls.SetName);
writer.Write(name);
await Game.FinishRpcAsync(writer);
@@ -148,19 +149,5 @@ namespace Impostor.Server.Net.Inner.Objects
// Notify plugins.
await _eventManager.CallAsync(new PlayerExileEvent(Game, Game.GetClientPlayer(OwnerId)!, this));
}
public async ValueTask StartVanishAsync()
{
using var writer = Game.StartRpc(NetId, RpcCalls.StartVanish);
Rpc63StartVanish.Serialize(writer);
await Game.FinishRpcAsync(writer);
}
public async ValueTask StartAppearAsync(bool shouldAnimate)
{
using var writer = Game.StartRpc(NetId, RpcCalls.StartAppear);
Rpc65StartAppear.Serialize(writer, shouldAnimate);
await Game.FinishRpcAsync(writer);
}
}
}
@@ -251,7 +251,7 @@ namespace Impostor.Server.Net.Inner.Objects
}
Rpc38SetLevel.Deserialize(reader, out var level);
return await HandleSetLevel(sender, level);
return true;
}
case RpcCalls.ReportDeadBody:
@@ -392,6 +392,7 @@ namespace Impostor.Server.Net.Inner.Objects
}
PlayerInfo.RoleType = role;
PlayerInfo.IsDirty = true;
if (Game.GameState == GameStates.Starting && Game.Players.All(clientPlayer => clientPlayer.Character?.PlayerInfo.RoleType != null))
{
@@ -511,7 +512,7 @@ namespace Impostor.Server.Net.Inner.Objects
return false;
}
Rpc55CheckShapeshift.Deserialize(reader, Game, out var playerControl, out _);
Rpc55CheckShapeshift.Deserialize(reader, Game, out var playerControl, out var shouldAnimate);
break;
}
@@ -526,56 +527,6 @@ namespace Impostor.Server.Net.Inner.Objects
break;
}
case RpcCalls.CheckVanish:
{
if (!await ValidateOwnership(call, sender) ||
!await ValidateRole(call, sender, PlayerInfo, RoleTypes.Phantom) ||
!await ValidateCmd(call, sender, target))
{
return false;
}
Rpc62CheckVanish.Deserialize(reader, out var maxDuration);
return await HandleCheckVanish(sender, maxDuration);
}
case RpcCalls.StartVanish:
{
if (!await ValidateHost(call, sender) ||
!await ValidateBroadcast(call, sender, target))
{
return false;
}
Rpc63StartVanish.Deserialize(reader);
return await HandleStartVanish(sender);
}
case RpcCalls.CheckAppear:
{
if (!await ValidateOwnership(call, sender) ||
!await ValidateRole(call, sender, PlayerInfo, RoleTypes.Phantom) ||
!await ValidateCmd(call, sender, target))
{
return false;
}
Rpc64CheckAppear.Deserialize(reader, out var shouldAnimate);
return await HandleCheckAppear(sender, shouldAnimate);
}
case RpcCalls.StartAppear:
{
if (!await ValidateHost(call, sender) ||
!await ValidateBroadcast(call, sender, target))
{
return false;
}
Rpc65StartAppear.Deserialize(reader, out var shouldAnimate);
return await HandleStartAppear(sender, shouldAnimate);
}
default:
return await base.HandleRpcAsync(sender, target, call, reader);
}
@@ -587,6 +538,7 @@ namespace Impostor.Server.Net.Inner.Objects
{
PlayerInfo.IsDead = true;
PlayerInfo.LastDeathReason = reason;
PlayerInfo.IsDirty = true;
}
internal void Protect(InnerPlayerControl guardianAngel)
@@ -700,7 +652,6 @@ namespace Impostor.Server.Net.Inner.Objects
if (RequestedPlayerName.Any())
{
var expected = RequestedPlayerName.Dequeue();
var requested = expected;
if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.PlayerName == expected))
{
@@ -721,10 +672,7 @@ namespace Impostor.Server.Net.Inner.Objects
if (name != expected)
{
if (await sender.Client.ReportCheatAsync(
RpcCalls.SetName,
CheatCategory.NameLimits,
$"Client sent SetName with incorrect name, got '{name}', requested '{requested}', expected '{expected}'"))
if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, CheatCategory.NameLimits, "Client sent SetName with incorrect name"))
{
await SetNameAsync(expected);
return false;
@@ -755,9 +703,9 @@ namespace Impostor.Server.Net.Inner.Objects
}
}
if ((byte)color >= ColorsCount)
if ((byte)color > ColorsCount)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckColor, CheatCategory.ProtocolExtension, "Client sent unknown color"))
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckColor, CheatCategory.ColorLimits, "Client sent invalid color"))
{
return false;
}
@@ -778,17 +726,9 @@ namespace Impostor.Server.Net.Inner.Objects
}
}
if ((byte)color >= ColorsCount)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ProtocolExtension, "Client sent unknown color"))
{
return false;
}
}
if (sender.IsOwner(this))
{
if (Game.IsColorUsed(color, this))
if (Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.CurrentOutfit.Color == color))
{
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client sent a color that is already used"))
{
@@ -800,56 +740,35 @@ namespace Impostor.Server.Net.Inner.Objects
{
if (RequestedColorId.Any())
{
var requested = RequestedColorId.Dequeue();
var expected = RequestedColorId.Dequeue();
if (Game.IsColorUsed(color, this))
for (var colorOffset = 0; colorOffset <= ColorsCount; colorOffset++)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client selected a color that is already used"))
var possibleColor = (ColorType)((byte)(expected + colorOffset) % ColorsCount);
if (!Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.CurrentOutfit.Color == possibleColor))
{
return false;
}
}
var startFrom = requested;
// Among Us wraps to the front if all colors between the requested and the final color are in use, check for this
if (requested > color)
{
// Among Us wrapped, so check all colors between requested and the final color
for (var c = requested; (byte)c < ColorsCount; c++)
{
if (!Game.IsColorUsed(c, this))
{
if (await sender.Client.ReportCheatAsync(
RpcCalls.SetColor,
CheatCategory.ColorLimits,
$"Client skipped color {c} that could be used, but wrapped instead. Player requested {requested} but was given {color}"))
{
await SetColorAsync(c);
return false;
}
}
expected = possibleColor;
break;
}
// Start checking from the first color, fallthrough to normal case
startFrom = ColorType.Red;
}
// Check all colors between the requested color and the assigned color
for (var c = startFrom; c < color; c++)
{
if (!Game.IsColorUsed(c, this))
if (colorOffset == ColorsCount)
{
if (await sender.Client.ReportCheatAsync(
RpcCalls.SetColor,
CheatCategory.ColorLimits,
$"Client skipped color {c} that could be used. Player requested {requested} but was given {color}"))
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client sent SetColor but all colors are already in use"))
{
await SetColorAsync(c);
await SetColorAsync(expected);
return false;
}
}
}
if (color != expected)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client sent SetColor with incorrect color"))
{
await SetColorAsync(expected);
return false;
}
}
}
else
{
@@ -917,19 +836,6 @@ namespace Impostor.Server.Net.Inner.Objects
return true;
}
private async ValueTask<bool> HandleSetLevel(ClientPlayer sender, uint level)
{
if (Game.GameState == GameStates.Started &&
await sender.Client.ReportCheatAsync(RpcCalls.SetLevel, CheatCategory.GameFlow, "Client tried to set level while not in game"))
{
return false;
}
PlayerInfo.PlayerLevel = level;
return true;
}
private async ValueTask<bool> HandleCheckMurder(ClientPlayer sender, InnerPlayerControl? target)
{
if (!PlayerInfo.CanMurder(Game, _dateTimeProvider))
@@ -1052,7 +958,7 @@ namespace Impostor.Server.Net.Inner.Objects
if (!await ValidateRole(RpcCalls.ProtectPlayer, sender, PlayerInfo, RoleTypes.GuardianAngel))
{
return false;
return false;
}
((InnerPlayerControl)target).Protect(this);
@@ -1104,61 +1010,5 @@ namespace Impostor.Server.Net.Inner.Objects
return true;
}
private async ValueTask<bool> HandleCheckVanish(ClientPlayer sender, float maxDuration)
{
// TODO: Check if operation is taking place during lobby/meetings
// TODO: Check that the player unvanished before the maximum duration is reached
// If the game is host authoritive, the RPC is handled by the host, otherwise by the server
if (_game.IsHostAuthoritive)
{
return true;
}
else
{
await StartVanishAsync();
return false;
}
}
private async ValueTask<bool> HandleStartVanish(ClientPlayer sender)
{
if (!_game.IsHostAuthoritive)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.StartVanish, CheatCategory.GameFlow, "Client tried to send StartVanish directly"))
{
return false;
}
}
return true;
}
private async ValueTask<bool> HandleCheckAppear(ClientPlayer sender, bool shouldAnimate)
{
// If the game is host authoritive, the RPC is handled by the host, otherwise by the server
if (_game.IsHostAuthoritive)
{
return true;
}
else
{
await StartAppearAsync(shouldAnimate);
return false;
}
}
private async ValueTask<bool> HandleStartAppear(ClientPlayer sender, bool shouldAnimate)
{
if (!_game.IsHostAuthoritive)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.StartAppear, CheatCategory.GameFlow, "Client tried to send StartAppear directly"))
{
return false;
}
}
return true;
}
}
}
@@ -1,15 +1,12 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Impostor.Api.Events.Managers;
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.Net.Inner;
using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Api.Utils;
using Impostor.Server.Net.State;
using Microsoft.Extensions.Logging;
@@ -18,13 +15,11 @@ namespace Impostor.Server.Net.Inner.Objects
{
internal partial class InnerPlayerInfo
{
private readonly IEventManager _eventManager;
private readonly ILogger<InnerPlayerInfo> _logger;
public InnerPlayerInfo(ICustomMessageManager<ICustomRpc> customMessageManager, IEventManager eventManager, Game game, ILogger<InnerPlayerInfo> logger) : base(customMessageManager, game)
public InnerPlayerInfo(ICustomMessageManager<ICustomRpc> customMessageManager, Game game, ILogger<InnerPlayerInfo> logger) : base(customMessageManager, game)
{
Components.Add(this);
_eventManager = eventManager;
_logger = logger;
}
@@ -51,20 +46,26 @@ namespace Impostor.Server.Net.Inner.Objects
public bool Disconnected { get; internal set; }
public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.ImpostorGhost or RoleTypes.Phantom;
public bool IsImpostor => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.ImpostorGhost;
public bool CanVent => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Phantom or RoleTypes.Engineer;
public bool CanVent => RoleType is RoleTypes.Impostor or RoleTypes.Shapeshifter or RoleTypes.Engineer;
public bool IsDead { get; internal set; }
public DeathReason LastDeathReason { get; internal set; }
public List<TaskInfo> Tasks { get; internal set; } = new List<TaskInfo>(0);
public List<InnerGameData.TaskInfo> Tasks { get; internal set; } = new List<InnerGameData.TaskInfo>(0);
public DateTimeOffset LastMurder { get; set; }
public uint PlayerLevel { get; internal set; }
public override bool IsDirty
{
get => CurrentOutfit.IsDirty;
internal set => CurrentOutfit.IsDirty = value;
}
public bool CanMurder(IGame game, IDateTimeProvider dateTimeProvider)
{
if (!IsImpostor)
@@ -181,39 +182,5 @@ namespace Impostor.Server.Net.Inner.Objects
return ValueTask.CompletedTask;
}
public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
{
switch (call)
{
case RpcCalls.SetTasks:
Rpc29SetTasks.Deserialize(reader, out var taskTypeIds);
SetTasks(taskTypeIds);
break;
default:
return await base.HandleRpcAsync(sender, target, call, reader);
}
return true;
}
private void SetTasks(ReadOnlyMemory<byte> taskTypeIds)
{
if (Disconnected)
{
return;
}
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;
Tasks.Add(new TaskInfo(this, _eventManager, taskId++, taskType));
}
}
}
}
@@ -1,68 +0,0 @@
using System.Threading.Tasks;
using Impostor.Api;
using Impostor.Api.Events.Managers;
using Impostor.Api.Innersloth.Maps;
using Impostor.Api.Net.Inner;
using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Net.Messages.Rpcs;
using Impostor.Server.Events.Player;
namespace Impostor.Server.Net.Inner.Objects
{
internal class TaskInfo : ITaskInfo
{
private readonly InnerPlayerInfo _playerInfo;
private readonly IEventManager _eventManager;
public TaskInfo(InnerPlayerInfo playerInfo, IEventManager eventManager, uint id, TaskData? task)
{
_playerInfo = playerInfo;
_eventManager = eventManager;
Id = id;
Task = task;
}
public uint Id { get; internal set; }
public TaskData? Task { get; internal set; }
public bool Complete { get; internal set; }
public void Serialize(IMessageWriter writer)
{
writer.WritePacked(Id);
writer.Write(Complete);
}
public void Deserialize(IMessageReader reader)
{
Id = reader.ReadPackedUInt32();
Complete = reader.ReadBoolean();
}
public async ValueTask CompleteAsync()
{
if (_playerInfo.Controller == null)
{
throw new ImpostorException("Can't complete a task that doesn't have a player assigned");
}
var player = _playerInfo.Controller;
if (Complete)
{
throw new ImpostorException("Can't complete a task that is already completed");
}
Complete = true;
// Send RPC.
using var writer = player.Game.StartRpc(player.NetId, RpcCalls.CompleteTask);
Rpc01CompleteTask.Serialize(writer, Id);
await player.Game.FinishRpcAsync(writer);
// Notify plugins.
await _eventManager.CallAsync(new PlayerCompletedTaskEvent(player.Game, player.Game.GetClientPlayer(player.OwnerId)!, player, this));
}
}
}
@@ -19,7 +19,6 @@ internal class CompatibilityManager : ICompatibilityManager
{
new GameVersion(2024, 3, 1), // 2024.6.18
new GameVersion(2024, 4, 1), // 2024.8.13
new GameVersion(2024, 4, 2), // 2024.9.4
},
};
+20 -15
View File
@@ -261,10 +261,9 @@ namespace Impostor.Server.Net.State
if (scene != "OnlineGame")
{
_logger.LogWarning(
"Player {PlayerName} ({ClientId}) tried to send SceneChangeFlag with disallowed scene \"{Scene}\".",
"Player {0} ({1}) tried to send SceneChangeFlag with disallowed scene.",
sender.Client.Name,
sender.Client.Id,
scene);
sender.Client.Id);
return false;
}
@@ -327,6 +326,17 @@ namespace Impostor.Server.Net.State
}
}
// Check for dirty netobjects
foreach (var netObject in _allObjectsFast.Values)
{
if (netObject.IsDirty && netObject.OwnerId == ServerOwned)
{
_logger.LogTrace("Sending over {Type} {NetId}", netObject.GetType().Name, netObject.NetId);
await SendObjectData(netObject);
netObject.IsDirty = false;
}
}
return true;
}
@@ -392,6 +402,13 @@ namespace Impostor.Server.Net.State
{
playerInfo.Controller = control;
control.PlayerInfo = playerInfo;
// Reset PlayerInfo
playerInfo.RoleType = null;
playerInfo.RoleWhenAlive = null;
playerInfo.Tasks.Clear();
playerInfo.IsDead = false;
playerInfo.IsDirty = true;
}
if (player != null)
@@ -511,18 +528,6 @@ namespace Impostor.Server.Net.State
await SendObjectSpawnAsync(playerInfo);
}
private async ValueTask DespawnPlayerInfoAsync(InnerPlayerInfo playerInfo)
{
if (playerInfo.OwnerId == ServerOwned)
{
_logger.LogDebug("Despawning PlayerInfo {nid}", playerInfo.NetId);
GameNet.GameData.RemovePlayer(playerInfo.PlayerId);
RemoveNetObject(playerInfo);
await SendObjectDespawnAsync(playerInfo);
}
}
private bool AddNetObject(InnerNetObject obj)
{
if (_allObjectsFast.ContainsKey(obj.NetId))
@@ -1,5 +1,4 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Impostor.Api.Games;
@@ -44,12 +43,6 @@ namespace Impostor.Server.Net.State
player.Value.Limbo = LimboStates.PreSpawn;
}
// Delete all PlayerInfo objects
foreach (var playerInfo in GameNet.GameData.Players.Values.ToArray())
{
await DespawnPlayerInfoAsync(playerInfo);
}
await _eventManager.CallAsync(new GameEndedEvent(this, gameOverReason));
}
+11 -1
View File
@@ -135,12 +135,22 @@ namespace Impostor.Server.Net.State
await FinishGameDataAsync(writer, targetClientId);
}
private async ValueTask SendObjectDespawnAsync(InnerNetObject obj, int? targetClientId = null)
private ValueTask SendObjectDespawn(InnerNetObject obj, int? targetClientId = null)
{
using var writer = StartGameData(targetClientId);
writer.StartMessage(GameDataTag.DespawnFlag);
writer.WritePacked(obj.NetId);
writer.EndMessage();
return FinishGameDataAsync(writer, targetClientId);
}
private async ValueTask SendObjectData(InnerNetObject obj, int? targetClientId = null)
{
using var writer = StartGameData(targetClientId);
writer.StartMessage(GameDataTag.DataFlag);
writer.WritePacked(obj.NetId);
await obj.SerializeAsync(writer, false);
writer.EndMessage();
await FinishGameDataAsync(writer, targetClientId);
}
}
+13 -4
View File
@@ -85,12 +85,21 @@ namespace Impostor.Server.Net.State
}
});
// Clean up the PlayerInfo if we own it and we're still in the lobby
if (GameState == GameStates.NotStarted)
// Clean up the PlayerInfo if we own it
foreach (var playerInfo in GameNet.GameData.Players.Values)
{
if (GameNet.GameData.PlayersByClientId.TryGetValue(playerId, out var playerInfo))
if (playerInfo.ClientId == playerId)
{
await DespawnPlayerInfoAsync(playerInfo);
if (playerInfo.OwnerId == ServerOwned)
{
_logger.LogInformation("Destroying PlayerInfo {nid}", playerInfo.NetId);
GameNet.GameData.RemovePlayer(playerInfo.PlayerId);
RemoveNetObject(playerInfo);
await SendObjectDespawn(playerInfo);
}
break;
}
}
-11
View File
@@ -9,10 +9,8 @@ using Impostor.Api.Config;
using Impostor.Api.Events.Managers;
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.Inner.Objects;
using Impostor.Api.Net.Manager;
using Impostor.Api.Net.Messages.S2C;
using Impostor.Server.Events;
@@ -133,15 +131,6 @@ namespace Impostor.Server.Net.State
}
}
/// <summary>Check if there are players using a color.</summary>
/// <param name="color">The color to check for.</param>
/// <param name="exceptBy">Exempt a player from being checked.</param>
/// <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);
}
private ValueTask BroadcastJoinMessage(IMessageWriter message, bool clear, ClientPlayer player)
{
Message01JoinGameS2C.SerializeJoin(message, clear, Code, player, HostId);