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))
{
if (name != expected)
{
- if (await sender.Client.ReportCheatAsync(RpcCalls.SetName, CheatCategory.NameLimits, "Client sent SetName with incorrect name"))
+ if (await sender.Client.ReportCheatAsync(
+ RpcCalls.SetName,
+ CheatCategory.NameLimits,
+ $"Client sent SetName with incorrect name, got '{name}', requested '{requested}', expected '{expected}'"))
{
await SetNameAsync(expected);
return false;
}
}
- if ((byte)color > ColorsCount)
+ if ((byte)color >= ColorsCount)
{
- if (await sender.Client.ReportCheatAsync(RpcCalls.CheckColor, CheatCategory.ColorLimits, "Client sent invalid color"))
+ if (await sender.Client.ReportCheatAsync(RpcCalls.CheckColor, CheatCategory.ProtocolExtension, "Client sent unknown color"))
{
return false;
}
}
}
+ 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.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.CurrentOutfit.Color == color))
+ if (Game.IsColorUsed(color, this))
{
if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client sent a color that is already used"))
{
{
if (RequestedColorId.Any())
{
- var expected = RequestedColorId.Dequeue();
+ var requested = RequestedColorId.Dequeue();
- for (var colorOffset = 0; colorOffset <= ColorsCount; colorOffset++)
+ if (Game.IsColorUsed(color, this))
{
- var possibleColor = (ColorType)((byte)(expected + colorOffset) % ColorsCount);
- if (!Game.Players.Any(x => x.Character != null && x.Character != this && x.Character.PlayerInfo.CurrentOutfit.Color == possibleColor))
+ if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client selected a color that is already used"))
{
- expected = possibleColor;
- break;
+ return false;
}
+ }
+
+ var startFrom = requested;
- if (colorOffset == ColorsCount)
+ // 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 (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client sent SetColor but all colors are already in use"))
+ if (!Game.IsColorUsed(c, this))
{
- await SetColorAsync(expected);
- return false;
+ 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;
+ }
}
}
+
+ // Start checking from the first color, fallthrough to normal case
+ startFrom = ColorType.Red;
}
- if (color != expected)
+ // Check all colors between the requested color and the assigned color
+ for (var c = startFrom; c < color; c++)
{
- if (await sender.Client.ReportCheatAsync(RpcCalls.SetColor, CheatCategory.ColorLimits, "Client sent SetColor with incorrect color"))
+ if (!Game.IsColorUsed(c, this))
{
- await SetColorAsync(expected);
- return false;
+ if (await sender.Client.ReportCheatAsync(
+ RpcCalls.SetColor,
+ CheatCategory.ColorLimits,
+ $"Client skipped color {c} that could be used. Player requested {requested} but was given {color}"))
+ {
+ await SetColorAsync(c);
+ return false;
+ }
}
}
}
if (!await ValidateRole(RpcCalls.ProtectPlayer, sender, PlayerInfo, RoleTypes.GuardianAngel))
{
- return false;
+ return false;
}
((InnerPlayerControl)target).Protect(this);
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;
}
}
+ /// <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);