mirror of
https://github.com/Impostor/Impostor.git
synced 2026-10-19 15:51:42 +00:00
Implement server side guardian angel
This commit is contained in:
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);
|
||||
|
||||
Reference in new issue
Block a user