From b67ca158ead15725b17066a0066f66921ad2e09f Mon Sep 17 00:00:00 2001 From: miniduikboot Date: Sun, 29 Oct 2023 00:37:30 +0200 Subject: [PATCH] Refresh Murder Event API - IPlayerCheckMurderEvent now exists - You can see if a kill was successful with IPlayerMurderEvent - You can now see if host authority was requested with Game.IsHostAuthoritive --- .../Game/Player/IPlayerCheckMurderEvent.cs | 27 ++++++++++++++++ .../Events/Game/Player/IPlayerMurderEvent.cs | 14 ++++++++ src/Impostor.Api/Games/IGame.cs | 6 ++++ .../Game/Player/PlayerCheckMurderEvent.cs | 32 +++++++++++++++++++ .../Events/Game/Player/PlayerMurderEvent.cs | 6 +++- .../Inner/Objects/InnerPlayerControl.Api.cs | 6 ++-- .../Net/Inner/Objects/InnerPlayerControl.cs | 25 +++++++++++---- src/Impostor.Server/Net/State/Game.cs | 27 +++++++++------- 8 files changed, 120 insertions(+), 23 deletions(-) create mode 100644 src/Impostor.Api/Events/Game/Player/IPlayerCheckMurderEvent.cs create mode 100644 src/Impostor.Server/Events/Game/Player/PlayerCheckMurderEvent.cs diff --git a/src/Impostor.Api/Events/Game/Player/IPlayerCheckMurderEvent.cs b/src/Impostor.Api/Events/Game/Player/IPlayerCheckMurderEvent.cs new file mode 100644 index 0000000..878d5a5 --- /dev/null +++ b/src/Impostor.Api/Events/Game/Player/IPlayerCheckMurderEvent.cs @@ -0,0 +1,27 @@ +using Impostor.Api.Innersloth; +using Impostor.Api.Net.Inner.Objects; + +namespace Impostor.Api.Events.Player +{ + /// + /// Event that allows changing or canceling an upcoming player murder. + /// + /// Note that this event only triggers if the Host of the Game did not disable server authority. + /// + /// If you want to get an event after the murder took place, listen to . + public interface IPlayerCheckMurderEvent : IPlayerEvent, IEventCancelable + { + /// + /// Gets the player who got murdered. + /// + IInnerPlayerControl Victim { get; } + + /// + /// Gets or sets the result of this event. + /// + /// Its initial value is what would have happened in the normal game flow. + /// - If set to Succeeded, the Victim will die. + /// - If set to FailedProtected, the Victim will be protected with a shield animation and the attacker will have a cooldown set. + MurderResultFlags Result { get; set; } + } +} diff --git a/src/Impostor.Api/Events/Game/Player/IPlayerMurderEvent.cs b/src/Impostor.Api/Events/Game/Player/IPlayerMurderEvent.cs index c47c00b..b90ea3e 100644 --- a/src/Impostor.Api/Events/Game/Player/IPlayerMurderEvent.cs +++ b/src/Impostor.Api/Events/Game/Player/IPlayerMurderEvent.cs @@ -1,12 +1,26 @@ +using Impostor.Api.Innersloth; using Impostor.Api.Net.Inner.Objects; namespace Impostor.Api.Events.Player { + /// + /// Event that is called when a player is killed by another player. + /// + /// This event works regardless of server authority is enabled or not. + /// + /// If you want to cancel this kill, listen to + /// If you want to know about players that were voted out, listed to public interface IPlayerMurderEvent : IPlayerEvent { /// /// Gets the player who got murdered. /// IInnerPlayerControl Victim { get; } + + /// + /// Gets the result of the event. + /// + /// Note that if FailedError or FailedProtected is set, the kill did not take place + MurderResultFlags Result { get; } } } diff --git a/src/Impostor.Api/Games/IGame.cs b/src/Impostor.Api/Games/IGame.cs index 636b016..8f5d29b 100644 --- a/src/Impostor.Api/Games/IGame.cs +++ b/src/Impostor.Api/Games/IGame.cs @@ -39,6 +39,12 @@ namespace Impostor.Api.Games int HostId { get; } + /// + /// Gets a value indicating whether the Host of the game has requested host authority. + /// + /// Vanilla Among Us does not request this, but certain client-side mods will. + bool IsHostAuthoritive { get; } + IClientPlayer? GetClientPlayer(int clientId); T? FindObjectByNetId(uint netId) diff --git a/src/Impostor.Server/Events/Game/Player/PlayerCheckMurderEvent.cs b/src/Impostor.Server/Events/Game/Player/PlayerCheckMurderEvent.cs new file mode 100644 index 0000000..fb0a1f0 --- /dev/null +++ b/src/Impostor.Server/Events/Game/Player/PlayerCheckMurderEvent.cs @@ -0,0 +1,32 @@ +using Impostor.Api.Events.Player; +using Impostor.Api.Games; +using Impostor.Api.Innersloth; +using Impostor.Api.Net; +using Impostor.Api.Net.Inner.Objects; + +namespace Impostor.Server.Events.Player +{ + public class PlayerCheckMurderEvent : IPlayerCheckMurderEvent + { + public PlayerCheckMurderEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl, IInnerPlayerControl victim, MurderResultFlags result) + { + Game = game; + ClientPlayer = clientPlayer; + PlayerControl = playerControl; + Victim = victim; + Result = result; + } + + public IGame Game { get; } + + public IClientPlayer ClientPlayer { get; } + + public IInnerPlayerControl PlayerControl { get; } + + public IInnerPlayerControl Victim { get; } + + public MurderResultFlags Result { get; set; } + + public bool IsCancelled { get; set; } + } +} diff --git a/src/Impostor.Server/Events/Game/Player/PlayerMurderEvent.cs b/src/Impostor.Server/Events/Game/Player/PlayerMurderEvent.cs index ca64c35..d157fd4 100644 --- a/src/Impostor.Server/Events/Game/Player/PlayerMurderEvent.cs +++ b/src/Impostor.Server/Events/Game/Player/PlayerMurderEvent.cs @@ -1,5 +1,6 @@ using Impostor.Api.Events.Player; using Impostor.Api.Games; +using Impostor.Api.Innersloth; using Impostor.Api.Net; using Impostor.Api.Net.Inner.Objects; @@ -7,12 +8,13 @@ namespace Impostor.Server.Events.Player { public class PlayerMurderEvent : IPlayerMurderEvent { - public PlayerMurderEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl, IInnerPlayerControl victim) + public PlayerMurderEvent(IGame game, IClientPlayer clientPlayer, IInnerPlayerControl playerControl, IInnerPlayerControl victim, MurderResultFlags result) { Game = game; ClientPlayer = clientPlayer; PlayerControl = playerControl; Victim = victim; + Result = result; } public IGame Game { get; } @@ -22,5 +24,7 @@ namespace Impostor.Server.Events.Player public IInnerPlayerControl PlayerControl { get; } public IInnerPlayerControl Victim { get; } + + public MurderResultFlags Result { get; } } } diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs index eeb4095..0718400 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.Api.cs @@ -82,7 +82,7 @@ namespace Impostor.Server.Net.Inner.Objects await Game.FinishRpcAsync(writer, player.OwnerId); } - public async ValueTask MurderPlayerAsync(IInnerPlayerControl target, MurderResultFlags result = MurderResultFlags.Succeeded) + public async ValueTask MurderPlayerAsync(IInnerPlayerControl target, MurderResultFlags result) { if (!PlayerInfo.IsImpostor) { @@ -99,7 +99,7 @@ namespace Impostor.Server.Net.Inner.Objects throw new ImpostorProtocolException("Tried to murder a player, but target was not alive."); } - if (result == MurderResultFlags.Succeeded) + if ((result & (MurderResultFlags.FailedError | MurderResultFlags.FailedProtected)) == 0) { ((InnerPlayerControl)target).Die(DeathReason.Kill); } @@ -108,7 +108,7 @@ namespace Impostor.Server.Net.Inner.Objects Rpc12MurderPlayer.Serialize(writer, target, result); await Game.FinishRpcAsync(writer); - await _eventManager.CallAsync(new PlayerMurderEvent(Game, Game.GetClientPlayer(OwnerId)!, this, target)); + await _eventManager.CallAsync(new PlayerMurderEvent(Game, Game.GetClientPlayer(OwnerId)!, this, target, result)); } public async ValueTask MurderPlayerAsync(IInnerPlayerControl target) diff --git a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs index a964c26..01da1f6 100644 --- a/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs +++ b/src/Impostor.Server/Net/Inner/Objects/InnerPlayerControl.cs @@ -696,7 +696,7 @@ namespace Impostor.Server.Net.Inner.Objects IsMurdering = target; // Check if host authority mode is on - if (_game.IsHostAuthoritive()) + if (_game.IsHostAuthoritive) { // Pass the RPC on unharmed, the client will handle it return true; @@ -706,8 +706,15 @@ namespace Impostor.Server.Net.Inner.Objects { 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); + + var evt = new PlayerCheckMurderEvent(Game, sender, this, target, result); + await _eventManager.CallAsync(evt); + + if (!evt.IsCancelled) + { + tgt.ProtectedOn = null; // Clear GA protection in all cases + await MurderPlayerAsync(target, evt.Result); + } } return false; @@ -715,7 +722,7 @@ namespace Impostor.Server.Net.Inner.Objects private async ValueTask HandleMurderPlayer(ClientPlayer sender, IInnerPlayerControl? target, MurderResultFlags result) { - if (!_game.IsHostAuthoritive()) + if (!_game.IsHostAuthoritive) { if (await sender.Client.ReportCheatAsync(RpcCalls.MurderPlayer, "Client tried to murder directly")) { @@ -742,8 +749,12 @@ namespace Impostor.Server.Net.Inner.Objects if (target != null && !target.PlayerInfo.IsDead) { - ((InnerPlayerControl)target).Die(DeathReason.Kill); - await _eventManager.CallAsync(new PlayerMurderEvent(Game, sender, this, target)); + if ((result & (MurderResultFlags.FailedError | MurderResultFlags.FailedProtected)) == 0) + { + ((InnerPlayerControl)target).Die(DeathReason.Kill); + } + + await _eventManager.CallAsync(new PlayerMurderEvent(Game, sender, this, target, result)); } IsMurdering = null; @@ -771,7 +782,7 @@ namespace Impostor.Server.Net.Inner.Objects } } - if (_game.IsHostAuthoritive()) + if (_game.IsHostAuthoritive) { return true; } diff --git a/src/Impostor.Server/Net/State/Game.cs b/src/Impostor.Server/Net/State/Game.cs index 8b7219f..6bb6fe5 100644 --- a/src/Impostor.Server/Net/State/Game.cs +++ b/src/Impostor.Server/Net/State/Game.cs @@ -92,6 +92,21 @@ namespace Impostor.Server.Net.State public IEnumerable Players => _players.Select(p => p.Value); + public bool IsHostAuthoritive + { + get + { + if (Host == null) + { + return false; + } + else + { + return Host.Client.GameVersion.HasDisableServerAuthorityFlag; + } + } + } + internal GameNet GameNet { get; } public bool TryGetPlayer(int id, [MaybeNullWhen(false)] out ClientPlayer player) @@ -145,17 +160,5 @@ namespace Impostor.Server.Net.State .Select(p => p.Client.Connection) .Where(c => c != null && c.IsConnected)!; } - - internal bool IsHostAuthoritive() - { - if (Host == null) - { - return false; - } - else - { - return Host.Client.GameVersion.HasDisableServerAuthorityFlag; - } - } } } -- 2.39.5