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.Events.Meeting;
using Impostor.Server.Events.Player;
private readonly ILogger<InnerMeetingHud> _logger;
private readonly IEventManager _eventManager;
-
private readonly CancellationTokenSource _timerToken;
+ private readonly List<JudgeOverrule> _judgeOverrules = new();
[AllowNull]
private PlayerVoteArea[] _playerStates;
public InnerPlayerInfo? Reporter { get; private set; }
+ public IReadOnlyCollection<IInnerMeetingHud.IJudgeOverrule> JudgeOverrules => _judgeOverrules;
+
public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
throw new NotImplementedException();
if (sender.Client.GameVersion >= JudgeMinVersion)
{
var overruleQueueListLength = reader.ReadPackedInt32();
- for (var i = 0; i < overruleQueueListLength; i++)
+ if (overruleQueueListLength > 0)
{
- var msg = reader.ReadMessage();
-
- // TODO parse and handle JudgeOverrule
+ _judgeOverrules.Clear();
+ for (var i = 0; i < overruleQueueListLength; i++)
+ {
+ _judgeOverrules.Add(JudgeOverrule.Deserialize(reader));
+ }
}
}
}
}
Rpc22Close.Deserialize(reader);
+ _judgeOverrules.Clear();
break;
}
return false;
}
- Rpc23VotingComplete.Deserialize(reader, out var states, out var playerId, out var tie);
+ var hasOverruleFields = sender.Client.GameVersion >= JudgeMinVersion;
+ Rpc23VotingComplete.Deserialize(reader, hasOverruleFields, out var states, out var playerId, out var tie, out var wasOverruled, out var overrideId);
foreach (var messageReader in states)
{
messageReader.Dispose();
case RpcCalls.QueueOverruleVotes:
{
- if (!await ValidateRole(call, sender, sender.Character!.PlayerInfo, RoleTypes.Judge) ||
- !await ValidateTarget(call, sender, target) ||
- !await ValidateHost(call, target!))
- {
- return false;
- }
-
- var judgePlayerId = reader.ReadByte();
- var targetPlayerId = reader.ReadByte();
- var overruleNonce = reader.ReadUInt16();
-
- break;
+ Rpc66QueueOverruleVotes.Deserialize(reader, out var judgePlayerId, out var targetPlayerId, out var overruleNonce);
+ return await HandleQueueOverruleVotesAsync(sender, target, judgePlayerId, targetPlayerId, overruleNonce);
}
default:
return true;
}
+ private async ValueTask<bool> HandleQueueOverruleVotesAsync(ClientPlayer sender, ClientPlayer? target, byte judgePlayerId, byte targetPlayerId, ushort overruleNonce)
+ {
+ if (!await ValidateRole(RpcCalls.QueueOverruleVotes, sender, sender.Character?.PlayerInfo, RoleTypes.Judge) ||
+ !await ValidateTarget(RpcCalls.QueueOverruleVotes, sender, target) ||
+ !await ValidateHost(RpcCalls.QueueOverruleVotes, target!))
+ {
+ return false;
+ }
+
+ // A Judge can only queue an overrule for themself.
+ if (judgePlayerId != sender.Character!.PlayerId)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.QueueOverruleVotes, CheatCategory.Ownership, "Client sent a Judge overrule for another player"))
+ {
+ return false;
+ }
+ }
+
+ // The client only generates nonces in the 1..65535 range.
+ if (overruleNonce == 0)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.QueueOverruleVotes, CheatCategory.GameFlow, "Client sent a Judge overrule with an uninitialized nonce"))
+ {
+ return false;
+ }
+ }
+
+ // A Judge can only overrule once per meeting.
+ if (_judgeOverrules.Any(overrule => overrule.JudgePlayerId == judgePlayerId))
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.QueueOverruleVotes, CheatCategory.GameFlow, "Client sent more than one Judge overrule in the same meeting"))
+ {
+ return false;
+ }
+ }
+
+ // The overruled player must exist.
+ if (Game.GameNet.GameData!.GetPlayerById(targetPlayerId) == null)
+ {
+ if (await sender.Client.ReportCheatAsync(RpcCalls.QueueOverruleVotes, CheatCategory.InvalidObject, "Client sent a Judge overrule for an unknown player"))
+ {
+ return false;
+ }
+ }
+
+ AddOrUpdateJudgeOverrule(judgePlayerId, targetPlayerId, overruleNonce);
+
+ return true;
+ }
+
+ private void AddOrUpdateJudgeOverrule(byte judgePlayerId, byte targetPlayerId, ushort overruleNonce)
+ {
+ var existing = _judgeOverrules.Find(overrule => overrule.JudgePlayerId == judgePlayerId);
+ if (existing != null)
+ {
+ existing.OverruledPlayerId = targetPlayerId;
+ existing.OverruleNonce = overruleNonce;
+ }
+ else
+ {
+ _judgeOverrules.Add(new JudgeOverrule(judgePlayerId, targetPlayerId, overruleNonce));
+ }
+ }
+
+ private JudgeOverrule? GetWinningOverrule()
+ {
+ var gameData = Game.GameNet.GameData!;
+ foreach (var overrule in _judgeOverrules)
+ {
+ var judge = gameData.GetPlayerById(overrule.JudgePlayerId);
+ var target = gameData.GetPlayerById(overrule.OverruledPlayerId);
+
+ if (judge != null && target != null && !judge.Disconnected && !target.Disconnected)
+ {
+ return overrule;
+ }
+ }
+
+ return null;
+ }
+
private async ValueTask CheckForEndVotingAsync()
{
if (_playerStates.All(ps => ps.IsDead || ps.DidVote))
var self = this.CalculateVotes();
var max = MaxPair(self, out var tie);
var exiled = tie ? null : Game.GameNet.GameData!.GetPlayerById(max.Key)?.Controller;
+
+ // exile or others?
+ var wasOverruled = false;
+ ushort overrideId = 0;
+
+ var winningOverrule = GetWinningOverrule();
+ if (winningOverrule != null)
+ {
+ wasOverruled = true;
+ overrideId = winningOverrule.OverruleNonce;
+
+ var overruled = Game.GameNet.GameData!.GetPlayerById(winningOverrule.OverruledPlayerId);
+ if (overruled != null && overruled.IsImpostor)
+ {
+ exiled = overruled.Controller;
+ }
+ else
+ {
+ var judge = Game.GameNet.GameData!.GetPlayerById(winningOverrule.JudgePlayerId);
+ exiled = judge?.Controller;
+ }
+ }
if (exiled != null && exiled.PlayerInfo != null)
{
await _eventManager.CallAsync(new PlayerExileEvent(Game, Game.GetClientPlayer(exiled!.OwnerId)!, exiled));
}
- await _eventManager.CallAsync(new MeetingEndedEvent(Game, this, exiled, tie));
+ await _eventManager.CallAsync(new MeetingEndedEvent(Game, this, exiled, tie, wasOverruled, overrideId));
}
}
}