--- /dev/null
+using Impostor.Api.Innersloth;
+using Impostor.Api.Net.Inner.Objects;
+
+namespace Impostor.Api.Events.Player
+{
+ /// <summary>
+ /// Event that allows changing or canceling an upcoming player murder.
+ /// </summary>
+ /// 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 <see cref="IPlayerMurderEvent"/>.
+ public interface IPlayerCheckMurderEvent : IPlayerEvent, IEventCancelable
+ {
+ /// <summary>
+ /// Gets the player who got murdered.
+ /// </summary>
+ IInnerPlayerControl Victim { get; }
+
+ /// <summary>
+ /// Gets or sets the result of this event.
+ /// </summary>
+ /// 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; }
+ }
+}
+using Impostor.Api.Innersloth;
using Impostor.Api.Net.Inner.Objects;
namespace Impostor.Api.Events.Player
{
+ /// <summary>
+ /// Event that is called when a player is killed by another player.
+ /// </summary>
+ /// This event works regardless of server authority is enabled or not.
+ ///
+ /// If you want to cancel this kill, listen to <see cref="IPlayerCheckMurderEvent"/>
+ /// If you want to know about players that were voted out, listed to <see cref="IPlayerExileEvent"/>
public interface IPlayerMurderEvent : IPlayerEvent
{
/// <summary>
/// Gets the player who got murdered.
/// </summary>
IInnerPlayerControl Victim { get; }
+
+ /// <summary>
+ /// Gets the result of the event.
+ /// </summary>
+ /// Note that if FailedError or FailedProtected is set, the kill did not take place
+ MurderResultFlags Result { get; }
}
}
int HostId { get; }
+ /// <summary>
+ /// Gets a value indicating whether the Host of the game has requested host authority.
+ /// </summary>
+ /// Vanilla Among Us does not request this, but certain client-side mods will.
+ bool IsHostAuthoritive { get; }
+
IClientPlayer? GetClientPlayer(int clientId);
T? FindObjectByNetId<T>(uint netId)
--- /dev/null
+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; }
+ }
+}
using Impostor.Api.Events.Player;
using Impostor.Api.Games;
+using Impostor.Api.Innersloth;
using Impostor.Api.Net;
using Impostor.Api.Net.Inner.Objects;
{
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; }
public IInnerPlayerControl PlayerControl { get; }
public IInnerPlayerControl Victim { get; }
+
+ public MurderResultFlags Result { get; }
}
}
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)
{
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);
}
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)
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;
{
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;
private async ValueTask<bool> 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"))
{
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;
}
}
- if (_game.IsHostAuthoritive())
+ if (_game.IsHostAuthoritive)
{
return true;
}
public IEnumerable<IClientPlayer> 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)
.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;
- }
- }
}
}