Implement server side guardian angel

This commit is contained in:
miniduikboot committed 2023-10-28 23:05:28 +02:00
1 parent b038d0e098
commit 946d7e2164
5 files changed
+124 -12

No files matched your search

@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using Impostor.Api.Innersloth;
using Impostor.Api.Innersloth.Customization;
using Impostor.Api.Net.Inner.Objects.Components;
@@ -90,7 +91,18 @@ namespace Impostor.Api.Net.Inner.Objects
ValueTask SendChatToPlayerAsync(string text, IInnerPlayerControl? player = null);
/// <summary>
/// Murder <paramref name="target" /> player.
/// Murder <paramref name="target" /> player or remove their protective shield.
/// </summary>
/// <param name="target">Target player to murder.</param>
/// <param name="result">The result of the murder operation.</param>
/// <exception cref="ImpostorProtocolException">Thrown when player is not the impostor.</exception>
/// <exception cref="ImpostorProtocolException">Thrown when player is dead.</exception>
/// <exception cref="ImpostorProtocolException">Thrown when target is dead.</exception>
/// <returns>Task that must be awaited.</returns>
ValueTask MurderPlayerAsync(IInnerPlayerControl target, MurderResultFlags result);
/// <summary>
/// Murder <paramref name="target" /> player successfully.
/// </summary>
/// <param name="target">Target player to murder.</param>
/// <exception cref="ImpostorProtocolException">Thrown when player is not the impostor.</exception>
@@ -99,6 +111,14 @@ namespace Impostor.Api.Net.Inner.Objects
/// <returns>Task that must be awaited.</returns>
ValueTask MurderPlayerAsync(IInnerPlayerControl target);
/// <summary>
/// Protect <paramref name="target" /> player.
/// </summary>
/// <param name="target">Target player to protect.</param>
/// <exception cref="ImpostorProtocolException">Thrown when target is a guardian angel.</exception>
/// <returns>Task that must be awaited.</returns>
ValueTask ProtectPlayerAsync(IInnerPlayerControl target);
/// <summary>
/// Exile the current player. This doesn't produce a body to be reported.
/// Visible to all players.
@@ -1,6 +1,6 @@
using Impostor.Api.Games;
using Impostor.Api.Net.Inner.Objects;
using Impostor.Api.Innersloth;
using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Api.Net.Messages.Rpcs
{
@@ -8,7 +8,7 @@ namespace Impostor.Api.Net.Messages.Rpcs
{
public static void Serialize(IMessageWriter writer, IInnerPlayerControl playerControl, ColorType color)
{
writer.Write(playerControl.NetId);
writer.WritePacked(playerControl.NetId);
writer.Write((byte)color);
}
@@ -82,7 +82,7 @@ namespace Impostor.Server.Net.Inner.Objects
await Game.FinishRpcAsync(writer, player.OwnerId);
}
public async ValueTask MurderPlayerAsync(IInnerPlayerControl target)
public async ValueTask MurderPlayerAsync(IInnerPlayerControl target, MurderResultFlags result = MurderResultFlags.Succeeded)
{
if (!PlayerInfo.IsImpostor)
{
@@ -99,15 +99,37 @@ namespace Impostor.Server.Net.Inner.Objects
throw new ImpostorProtocolException("Tried to murder a player, but target was not alive.");
}
((InnerPlayerControl)target).Die(DeathReason.Kill);
if (result == MurderResultFlags.Succeeded)
{
((InnerPlayerControl)target).Die(DeathReason.Kill);
}
using var writer = Game.StartRpc(NetId, RpcCalls.MurderPlayer);
Rpc12MurderPlayer.Serialize(writer, target, MurderResultFlags.Succeeded);
Rpc12MurderPlayer.Serialize(writer, target, result);
await Game.FinishRpcAsync(writer);
await _eventManager.CallAsync(new PlayerMurderEvent(Game, Game.GetClientPlayer(OwnerId)!, this, target));
}
public async ValueTask MurderPlayerAsync(IInnerPlayerControl target)
{
await MurderPlayerAsync(target, MurderResultFlags.Succeeded);
}
public async ValueTask ProtectPlayerAsync(IInnerPlayerControl target)
{
if (target.PlayerInfo.RoleType == RoleTypes.GuardianAngel)
{
throw new ImpostorProtocolException("Tried to protect another Guardian Angel");
}
((InnerPlayerControl)target).Protect(this);
using var writer = Game.StartRpc(NetId, RpcCalls.ProtectPlayer);
Rpc45ProtectPlayer.Serialize(writer, target, PlayerInfo.CurrentOutfit.Color);
await Game.FinishRpcAsync(writer);
}
public async ValueTask ExileAsync()
{
if (PlayerInfo.IsDead)
@@ -7,6 +7,8 @@ using Impostor.Api;
using Impostor.Api.Events.Managers;
using Impostor.Api.Innersloth;
using Impostor.Api.Innersloth.Customization;
using Impostor.Api.Innersloth.GameOptions;
using Impostor.Api.Innersloth.GameOptions.RoleOptions;
using Impostor.Api.Net;
using Impostor.Api.Net.Custom;
using Impostor.Api.Net.Inner;
@@ -65,6 +67,10 @@ namespace Impostor.Server.Net.Inner.Objects
/// <summary> Gets or sets target that was set by the last CheckMurder RPC. </summary>
internal IInnerPlayerControl? IsMurdering { get; set; } = null;
internal DateTimeOffset? ProtectedOn { get; set; }
internal IInnerPlayerControl? ProtectedBy { get; set; }
public override ValueTask<bool> SerializeAsync(IMessageWriter writer, bool initialState)
{
throw new NotImplementedException();
@@ -87,6 +93,7 @@ namespace Impostor.Server.Net.Inner.Objects
public override async ValueTask<bool> HandleRpcAsync(ClientPlayer sender, ClientPlayer? target, RpcCalls call, IMessageReader reader)
{
_logger.LogTrace("Got RPC {0}", call);
switch (call)
{
case RpcCalls.PlayAnimation:
@@ -404,9 +411,8 @@ namespace Impostor.Server.Net.Inner.Objects
return false;
}
Rpc48CheckProtect.Deserialize(reader, Game, out _);
break;
Rpc48CheckProtect.Deserialize(reader, Game, out var protectTarget);
return await HandleCheckProtect(sender, protectTarget);
}
default:
@@ -422,6 +428,30 @@ namespace Impostor.Server.Net.Inner.Objects
PlayerInfo.LastDeathReason = reason;
}
internal void Protect(InnerPlayerControl guardianAngel)
{
// NOTE: Vanilla dispells all GA shields when a kill is blocked, so it suffices to keep track of the last protection action
ProtectedOn = _dateTimeProvider.UtcNow;
ProtectedBy = guardianAngel;
}
internal bool IsProtected()
{
// HnS doesn't have guardian angels
if (Game.Options.GameMode == GameModes.Normal && ProtectedOn != null)
{
var opts = (NormalGameOptions)Game.Options;
var guardianAngelOpts = (GuardianAngelRoleOptions)opts.RoleOptions.Roles[RoleTypes.GuardianAngel].RoleOptions;
var duration = guardianAngelOpts.ProtectionDurationSeconds;
var protectionExpiresAt = ProtectedOn.Value.AddSeconds(duration);
return protectionExpiresAt >= _dateTimeProvider.UtcNow;
}
else
{
return false;
}
}
private async ValueTask HandleCompleteTask(ClientPlayer sender, uint taskId)
{
var task = PlayerInfo.Tasks.ElementAtOrDefault((int)taskId);
@@ -672,11 +702,12 @@ namespace Impostor.Server.Net.Inner.Objects
return true;
}
// TODO check if protected by GA
if (target != null)
{
await MurderPlayerAsync(target);
var tgt = (InnerPlayerControl)target;
var result = tgt.IsProtected() ? MurderResultFlags.FailedProtected : MurderResultFlags.Succeeded;
tgt.ProtectedOn = null; // Clear GA protection in all cases
await MurderPlayerAsync(target, result);
}
return false;
@@ -684,6 +715,14 @@ namespace Impostor.Server.Net.Inner.Objects
private async ValueTask<bool> HandleMurderPlayer(ClientPlayer sender, IInnerPlayerControl? target, MurderResultFlags result)
{
if (!_game.IsHostAuthoritive())
{
if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, "Client tried to murder directly"))
{
return false;
}
}
if (target == null || target.PlayerInfo.IsImpostor)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, "Client tried to murder invalid target"))
@@ -712,6 +751,37 @@ namespace Impostor.Server.Net.Inner.Objects
return true;
}
private async ValueTask<bool> HandleCheckProtect(ClientPlayer sender, IInnerPlayerControl? target)
{
if (target == null)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckProtect, "Client tried to protect invalid target"))
{
return false;
}
return true;
}
if (PlayerInfo.RoleType == RoleTypes.GuardianAngel)
{
if (await sender.Client.ReportCheatAsync(RpcCalls.CheckProtect, "Sender tried to protect target it couldn't protect"))
{
return false;
}
}
if (_game.IsHostAuthoritive())
{
return true;
}
else
{
await ProtectPlayerAsync(target);
return false;
}
}
private async ValueTask<bool> HandleSendChat(ClientPlayer sender, string message)
{
var @event = new PlayerChatEvent(Game, sender, this, message);