Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 133 additions & 3 deletions MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ namespace MiraAPI.Events.Vanilla.Gameplay;
/// </summary>
public sealed class BeforeMurderEvent : MiraCancelableEvent
{
private bool _didSucceed = true;
private bool _resetKillTimer = true;
private bool _createDeadBody = true;
private bool _teleportMurderer = true;
private bool _showKillAnim = true;
private bool _playKillSound = true;

/// <summary>
/// Gets the player that is killing the target.
/// </summary>
Expand All @@ -22,6 +29,97 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent
/// </summary>
public MeetingCheck InMeeting { get; }

/// <summary>
/// Gets a value indicating whether any of the custom murder options have been modified by a handler.
/// When this is <c>true</c> on a vanilla kill, the kill is performed as a custom murder using these
/// options instead of the default vanilla murder.
/// </summary>
public bool OptionsModified { get; private set; }

/// <summary>
/// Gets or sets a value indicating whether the murder was successful.
/// Setting this flags the kill to be performed as a custom murder.
/// </summary>
public bool DidSucceed
{
get => _didSucceed;
set
{
if (_didSucceed != value) OptionsModified = true;
_didSucceed = value;
}
}

/// <summary>
/// Gets or sets a value indicating whether the kill timer should be reset.
/// Setting this flags the kill to be performed as a custom murder.
/// </summary>
public bool ResetKillTimer
{
get => _resetKillTimer;
set
{
if (_resetKillTimer != value) OptionsModified = true;
_resetKillTimer = value;
}
}

/// <summary>
/// Gets or sets a value indicating whether a dead body should be created.
/// Setting this flags the kill to be performed as a custom murder.
/// </summary>
public bool CreateDeadBody
{
get => _createDeadBody;
set
{
if (_createDeadBody != value) OptionsModified = true;
_createDeadBody = value;
}
}

/// <summary>
/// Gets or sets a value indicating whether the killer should be snapped to the dead player.
/// Setting this flags the kill to be performed as a custom murder.
/// </summary>
public bool TeleportMurderer
{
get => _teleportMurderer;
set
{
if (_teleportMurderer != value) OptionsModified = true;
_teleportMurderer = value;
}
}

/// <summary>
/// Gets or sets a value indicating whether the kill animation should be shown.
/// Setting this flags the kill to be performed as a custom murder.
/// </summary>
public bool ShowKillAnim
{
get => _showKillAnim;
set
{
if (_showKillAnim != value) OptionsModified = true;
_showKillAnim = value;
}
}

/// <summary>
/// Gets or sets a value indicating whether the kill sound should be played.
/// Setting this flags the kill to be performed as a custom murder.
/// </summary>
public bool PlayKillSound
{
get => _playKillSound;
set
{
if (_playKillSound != value) OptionsModified = true;
_playKillSound = value;
}
}

/// <summary>
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
/// </summary>
Expand All @@ -41,9 +139,41 @@ public BeforeMurderEvent(PlayerControl source, PlayerControl target, MeetingChec
/// <param name="source">The player that is killing the target.</param>
/// <param name="target">The player that is being killed.</param>
public BeforeMurderEvent(PlayerControl source, PlayerControl target)
: this(source, target, MeetingCheck.Ignore)
{
Source = source;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be breaking idk im a bit special in the head

Target = target;
InMeeting = MeetingCheck.Ignore;
}

/// <summary>
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class with custom murder options.
/// </summary>
/// <param name="source">The player that is killing the target.</param>
/// <param name="target">The player that is being killed.</param>
/// <param name="inMeeting">Whether the murder is intended to be triggered in a meeting.</param>
/// <param name="didSucceed">Whether the murder was successful or not.</param>
/// <param name="resetKillTimer">Should the kill timer be reset.</param>
/// <param name="createDeadBody">Should a dead body be created.</param>
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</param>
/// <param name="showKillAnim">Should the kill animation be shown.</param>
/// <param name="playKillSound">Should the kill sound be played.</param>
public BeforeMurderEvent(
PlayerControl source,
PlayerControl target,
MeetingCheck inMeeting,
bool didSucceed,
bool resetKillTimer = true,
bool createDeadBody = true,
bool teleportMurderer = true,
bool showKillAnim = true,
bool playKillSound = true)
: this(source, target, inMeeting)
{
// Set the backing fields directly so initialization does not flag OptionsModified.
// Only a handler changing an option through a property setter should flag it.
_didSucceed = didSucceed;
_resetKillTimer = resetKillTimer;
_createDeadBody = createDeadBody;
_teleportMurderer = teleportMurderer;
_showKillAnim = showKillAnim;
_playKillSound = playKillSound;
}
}
25 changes: 18 additions & 7 deletions MiraAPI/Networking/CustomMurderRpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,21 @@ public static void RpcCustomMurder(
{
return;
}
var murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError;

var beforeMurderEvent = new BeforeMurderEvent(source, target, inMeeting);
var beforeMurderEvent = new BeforeMurderEvent(
source,
target,
inMeeting,
didSucceed,
resetKillTimer,
createDeadBody,
teleportMurderer,
showKillAnim,
playKillSound);
MiraEventManager.InvokeEvent(beforeMurderEvent);

var murderResultFlags = beforeMurderEvent.DidSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError;

var isMeetingActive = MeetingHud.Instance != null || ExileController.Instance != null;
if ((inMeeting is MeetingCheck.ForMeeting && !isMeetingActive) || (inMeeting is MeetingCheck.OutsideMeeting && isMeetingActive))
{
Expand Down Expand Up @@ -118,11 +129,11 @@ public static void RpcCustomMurder(
source,
target,
murderResultFlags,
resetKillTimer,
createDeadBody,
teleportMurderer,
showKillAnim,
playKillSound);
beforeMurderEvent.ResetKillTimer,
beforeMurderEvent.CreateDeadBody,
beforeMurderEvent.TeleportMurderer,
beforeMurderEvent.ShowKillAnim,
beforeMurderEvent.PlayKillSound);
}

/// <summary>
Expand Down
23 changes: 22 additions & 1 deletion MiraAPI/Patches/PlayerControlPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using MiraAPI.Events.Vanilla.Player;
using MiraAPI.Hud;
using MiraAPI.Modifiers;
using MiraAPI.Networking;
using MiraAPI.Utilities;
using MiraAPI.Voting;
using Reactor.Utilities;
Expand Down Expand Up @@ -114,7 +115,27 @@ public static void PlayerControlCheckMurderPrefix(PlayerControl __instance, Play
}

__instance.isKilling = true;
__instance.RpcMurderPlayer(target, true);

if (beforeMurderEvent.OptionsModified)
{
var murderResultFlags = beforeMurderEvent.DidSucceed
? MurderResultFlags.Succeeded
: MurderResultFlags.FailedError;

PlayerControl.LocalPlayer.RpcConfirmCustomMurder(
__instance,
target,
murderResultFlags,
beforeMurderEvent.ResetKillTimer,
beforeMurderEvent.CreateDeadBody,
beforeMurderEvent.TeleportMurderer,
beforeMurderEvent.ShowKillAnim,
beforeMurderEvent.PlayKillSound);
}
else
{
__instance.RpcMurderPlayer(target, true);
}
}

[HarmonyPrefix]
Expand Down
Loading