From 14ce0202087017ea5badf13147a249119d49c1d3 Mon Sep 17 00:00:00 2001
From: Atony <75191600+AtonyGit@users.noreply.github.com>
Date: Wed, 12 Aug 2026 21:14:50 -0500
Subject: [PATCH 1/5] Hide modifier spawning on non-dev builds
---
MiraAPI/Modifiers/ModifierManager.cs | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/MiraAPI/Modifiers/ModifierManager.cs b/MiraAPI/Modifiers/ModifierManager.cs
index c2688a0..28faa8b 100644
--- a/MiraAPI/Modifiers/ModifierManager.cs
+++ b/MiraAPI/Modifiers/ModifierManager.cs
@@ -136,7 +136,10 @@ public static class ModifierManager
var validPlayers = plrs.Where(x => IsGameModifierValid(x, modifier, modifier.TypeId)).ToList();
if (validPlayers.Count == 0)
{
- Warning($"No valid players for modifier {modifier.ModifierName}");
+ if (MiraApiPlugin.IsDevBuild)
+ {
+ Warning($"No valid players for modifier {modifier.ModifierName}");
+ }
continue;
}
@@ -157,15 +160,23 @@ public static class ModifierManager
if (candidates.Count == 0)
{
- Warning(
- $"No available players for modifier {modifier.ModifierName} at assignment {i + 1}");
+ if (MiraApiPlugin.IsDevBuild)
+ {
+ Warning(
+ $"No available players for modifier {modifier.ModifierName} at assignment {i + 1}");
+ }
+
break;
}
var plr = candidates.Random();
if (plr == null)
{
- Warning($"Valid player for modifier {modifier.ModifierName} disappeared");
+ if (MiraApiPlugin.IsDevBuild)
+ {
+ Warning($"Valid player for modifier {modifier.ModifierName} disappeared");
+ }
+
continue;
}
From abb6f3d161aa4ecf0caa1ce8cf2f2f08d891fbf1 Mon Sep 17 00:00:00 2001
From: Atony <75191600+AtonyGit@users.noreply.github.com>
Date: Wed, 12 Aug 2026 23:22:55 -0500
Subject: [PATCH 2/5] Add better murder event data
---
.../Vanilla/Gameplay/AfterMurderEvent.cs | 64 +++++++++++++++++--
.../Vanilla/Gameplay/BeforeMurderEvent.cs | 59 +++++++++++++++++
2 files changed, 119 insertions(+), 4 deletions(-)
diff --git a/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs b/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs
index 8b85622..8db74b9 100644
--- a/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs
+++ b/MiraAPI/Events/Vanilla/Gameplay/AfterMurderEvent.cs
@@ -16,20 +16,76 @@ public class AfterMurderEvent : MiraEvent
public PlayerControl Target { get; }
///
- /// Gets the 's , if it exists.
+ /// Gets the that was framed, if any.
+ ///
+ public PlayerControl? Framed { get; }
+
+ ///
+ /// Gets a value indicating whether the murder was done indirectly.
+ /// This is commonly used to prevent the from being killed.
+ ///
+ public bool IsIndirectAttack { get; }
+
+ ///
+ /// Gets a value indicating whether the murder was able to bypass protective roles or general defense, such as .
+ ///
+ public bool IgnoreDefense { get; }
+
+ ///
+ /// Gets the 's , if it exists.
///
public DeadBody? DeadBody { get; }
///
/// Initializes a new instance of the class.
///
- /// The killer.
- /// The killed .
- /// The 's , if it exists.
+ /// The that killed the .
+ /// The that was killed.
+ /// The 's , if it exists.
+ /// Whether the murder was caused indirectly.
+ /// Whether the murder ignored protection.
+ public AfterMurderEvent(PlayerControl source, PlayerControl target, DeadBody? deadBody, bool isIndirect, bool ignoreDefense = false)
+ {
+ Source = source;
+ Target = target;
+ Framed = null;
+ DeadBody = deadBody;
+ IsIndirectAttack = isIndirect;
+ IgnoreDefense = ignoreDefense;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The that killed the .
+ /// The that was killed.
+ /// The that was framed.
+ /// The 's , if it exists.
+ /// Whether the murder was caused indirectly.
+ /// Whether the murder ignored protection.
+ public AfterMurderEvent(PlayerControl source, PlayerControl target, PlayerControl framed, DeadBody? deadBody, bool isIndirect = true, bool ignoreDefense = false)
+ {
+ Source = source;
+ Target = target;
+ Framed = framed;
+ DeadBody = deadBody;
+ IsIndirectAttack = isIndirect;
+ IgnoreDefense = ignoreDefense;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The that killed the .
+ /// The that was killed.
+ /// The 's , if it exists.
public AfterMurderEvent(PlayerControl source, PlayerControl target, DeadBody? deadBody)
{
Source = source;
Target = target;
+ Framed = null;
DeadBody = deadBody;
+ IsIndirectAttack = false;
+ IgnoreDefense = false;
}
}
diff --git a/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs b/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs
index cc07e21..8a770dc 100644
--- a/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs
+++ b/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs
@@ -17,11 +17,64 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent
///
public PlayerControl Target { get; }
+ ///
+ /// Gets the that is being framed, if any.
+ ///
+ public PlayerControl? Framed { get; }
+
+ ///
+ /// Gets a value indicating whether the murder is done indirectly.
+ /// This is commonly used to prevent the from being killed.
+ ///
+ public bool IsIndirectAttack { get; }
+
+ ///
+ /// Gets a value indicating whether the murder should bypass protective roles or general defense, such as .
+ ///
+ public bool IgnoreDefense { get; }
+
///
/// Gets whether the murder was meant to be done in a meeting, via an enum.
///
public MeetingCheck InMeeting { get; }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The that is killing the .
+ /// The that is being killed.
+ /// Whether the murder is caused indirectly.
+ /// Whether the murder should ignore protection.
+ /// Whether the murder is intended to be triggered in a meeting.
+ public BeforeMurderEvent(PlayerControl source, PlayerControl target, bool isIndirect, bool ignoreDefense = false, MeetingCheck inMeeting = MeetingCheck.Ignore)
+ {
+ Source = source;
+ Target = target;
+ Framed = null;
+ IsIndirectAttack = isIndirect;
+ IgnoreDefense = ignoreDefense;
+ InMeeting = inMeeting;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The that is killing the .
+ /// The that is being killed.
+ /// The that is being framed.
+ /// Whether the murder is caused indirectly.
+ /// Whether the murder should ignore protection.
+ /// Whether the murder is intended to be triggered in a meeting.
+ public BeforeMurderEvent(PlayerControl source, PlayerControl target, PlayerControl framed, bool isIndirect = true, bool ignoreDefense = false, MeetingCheck inMeeting = MeetingCheck.Ignore)
+ {
+ Source = source;
+ Target = target;
+ Framed = framed;
+ IsIndirectAttack = isIndirect;
+ IgnoreDefense = ignoreDefense;
+ InMeeting = inMeeting;
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -32,6 +85,9 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent
{
Source = source;
Target = target;
+ Framed = null;
+ IsIndirectAttack = false;
+ IgnoreDefense = false;
InMeeting = inMeeting;
}
@@ -44,6 +100,9 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent
{
Source = source;
Target = target;
+ Framed = null;
+ IsIndirectAttack = false;
+ IgnoreDefense = false;
InMeeting = MeetingCheck.Ignore;
}
}
From 22716b29abe0df7e600c651b55cbc237fe674a9d Mon Sep 17 00:00:00 2001
From: Atony <75191600+AtonyGit@users.noreply.github.com>
Date: Thu, 13 Aug 2026 09:20:26 -0500
Subject: [PATCH 3/5] Add Framed and Advanced Murder RPCs
---
MiraAPI/Networking/CustomMurderRpc.cs | 410 +++++++++++++++++++++++++-
MiraAPI/Networking/MiraRpc.cs | 20 ++
2 files changed, 424 insertions(+), 6 deletions(-)
diff --git a/MiraAPI/Networking/CustomMurderRpc.cs b/MiraAPI/Networking/CustomMurderRpc.cs
index 7009819..b53fd85 100644
--- a/MiraAPI/Networking/CustomMurderRpc.cs
+++ b/MiraAPI/Networking/CustomMurderRpc.cs
@@ -164,6 +164,397 @@ public static class CustomMurderRpc
playKillSound);
}
+ ///
+ /// Networked Custom Murder method, which checks for meetings as well, ran by clients for the host to confirm kills.
+ ///
+ /// The killer.
+ /// The player to murder.
+ /// Whether the murder is intended to be triggered in a meeting.
+ /// Should the kill be indirect.
+ /// Should the kill ignore defense.
+ /// Whether the murder was successful or not.
+ /// Should the kill timer be reset.
+ /// Should a dead body be created.
+ /// Should the killer be snapped to the dead player.
+ /// Should the kill animation be shown.
+ /// Should the kill sound be played.
+ [MethodRpc((uint)MiraRpc.AdvancedCustomMurder, LocalHandling = RpcLocalHandling.Before)]
+ public static void RpcAdvancedCustomMurder(
+ this PlayerControl source,
+ PlayerControl target,
+ MeetingCheck inMeeting,
+ bool isIndirect,
+ bool ignoreDefense = false,
+ bool didSucceed = true,
+ bool resetKillTimer = true,
+ bool createDeadBody = true,
+ bool teleportMurderer = true,
+ bool showKillAnim = true,
+ bool playKillSound = true)
+ {
+ if (LobbyBehaviour.Instance)
+ {
+ return;
+ }
+ var murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError;
+
+ var beforeMurderEvent = new BeforeMurderEvent(source, target, isIndirect, ignoreDefense, inMeeting);
+ MiraEventManager.InvokeEvent(beforeMurderEvent);
+ var isMeetingActive = MeetingHud.Instance != null || ExileController.Instance != null;
+ if ((inMeeting is MeetingCheck.ForMeeting && !isMeetingActive) || (inMeeting is MeetingCheck.OutsideMeeting && isMeetingActive))
+ {
+ beforeMurderEvent.Cancel();
+ }
+
+ if (!ignoreDefense && target.ProtectedByGa())
+ {
+ beforeMurderEvent.Cancel();
+ murderResultFlags = MurderResultFlags.FailedProtected;
+ }
+ else if (beforeMurderEvent.IsCancelled)
+ {
+ murderResultFlags = MurderResultFlags.FailedError;
+ }
+
+ if (beforeMurderEvent.IsCancelled && source.AmOwner)
+ {
+ source.isKilling = true;
+ }
+
+ if (!PlayerControl.LocalPlayer.IsHost())
+ {
+ return;
+ }
+
+ RpcConfirmAdvancedCustomMurder(
+ PlayerControl.LocalPlayer,
+ source,
+ target,
+ isIndirect,
+ ignoreDefense,
+ murderResultFlags,
+ resetKillTimer,
+ createDeadBody,
+ teleportMurderer,
+ showKillAnim,
+ playKillSound);
+ }
+
+ ///
+ /// Networked Custom Murder method, which checks for meetings as well, ran by clients for the host to confirm kills.
+ ///
+ /// The game host.
+ /// The killer.
+ /// The player to murder.
+ /// Should the kill be indirect.
+ /// Should the kill ignore defense.
+ /// End result for the murder.
+ /// Should the kill timer be reset.
+ /// Should a dead body be created.
+ /// Should the killer be snapped to the dead player.
+ /// Should the kill animation be shown.
+ /// Should the kill sound be played.
+ [MethodRpc((uint)MiraRpc.ConfirmAdvancedCustomMurder, LocalHandling = RpcLocalHandling.After)]
+ public static void RpcConfirmAdvancedCustomMurder(
+ this PlayerControl host,
+ PlayerControl source,
+ PlayerControl target,
+ bool isIndirect,
+ bool ignoreDefense,
+ MurderResultFlags murderResultFlags,
+ bool resetKillTimer = true,
+ bool createDeadBody = true,
+ bool teleportMurderer = true,
+ bool showKillAnim = true,
+ bool playKillSound = true)
+ {
+ if (LobbyBehaviour.Instance || !host.IsHost() || target.Data.IsDead || target.Data.Disconnected)
+ {
+ source.isKilling = false;
+ return;
+ }
+
+ var murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags;
+
+ source.CustomMurder(
+ target,
+ null,
+ isIndirect,
+ ignoreDefense,
+ murderResultFlags2,
+ resetKillTimer,
+ createDeadBody,
+ teleportMurderer,
+ showKillAnim,
+ playKillSound);
+ }
+
+ ///
+ /// Networked Custom Murder method, which checks for meetings as well, ran by clients for the host to confirm kills.
+ ///
+ /// The killer.
+ /// The player to murder.
+ /// The player to frame.
+ /// Whether the murder is intended to be triggered in a meeting.
+ /// Should the kill be indirect.
+ /// Should the kill ignore defense.
+ /// Whether the murder was successful or not.
+ /// Should the kill timer be reset.
+ /// Should a dead body be created.
+ /// Should the framed player be snapped to the dead player.
+ /// Should the kill animation be shown.
+ /// Should the kill sound be played.
+ [MethodRpc((uint)MiraRpc.FramedCustomMurder, LocalHandling = RpcLocalHandling.Before)]
+ public static void RpcFramedCustomMurder(
+ this PlayerControl source,
+ PlayerControl target,
+ PlayerControl framed,
+ MeetingCheck inMeeting,
+ bool isIndirect = true,
+ bool ignoreDefense = false,
+ bool didSucceed = true,
+ bool resetKillTimer = true,
+ bool createDeadBody = true,
+ bool teleportMurderer = true,
+ bool showKillAnim = true,
+ bool playKillSound = true)
+ {
+ if (LobbyBehaviour.Instance)
+ {
+ return;
+ }
+ var murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError;
+
+ var beforeMurderEvent = new BeforeMurderEvent(source, target, framed, isIndirect, ignoreDefense, inMeeting);
+ MiraEventManager.InvokeEvent(beforeMurderEvent);
+ var isMeetingActive = MeetingHud.Instance != null || ExileController.Instance != null;
+ if ((inMeeting is MeetingCheck.ForMeeting && !isMeetingActive) || (inMeeting is MeetingCheck.OutsideMeeting && isMeetingActive))
+ {
+ beforeMurderEvent.Cancel();
+ }
+
+ if (!ignoreDefense && target.ProtectedByGa())
+ {
+ beforeMurderEvent.Cancel();
+ murderResultFlags = MurderResultFlags.FailedProtected;
+ }
+ else if (beforeMurderEvent.IsCancelled)
+ {
+ murderResultFlags = MurderResultFlags.FailedError;
+ }
+
+ if (beforeMurderEvent.IsCancelled && source.AmOwner)
+ {
+ source.isKilling = true;
+ }
+
+ if (!PlayerControl.LocalPlayer.IsHost())
+ {
+ return;
+ }
+
+ RpcConfirmFramedCustomMurder(
+ PlayerControl.LocalPlayer,
+ source,
+ target,
+ framed,
+ isIndirect,
+ ignoreDefense,
+ murderResultFlags,
+ resetKillTimer,
+ createDeadBody,
+ teleportMurderer,
+ showKillAnim,
+ playKillSound);
+ }
+
+ ///
+ /// Networked Custom Murder method, which checks for meetings as well, ran by clients for the host to confirm kills.
+ ///
+ /// The game host.
+ /// The killer.
+ /// The player to murder.
+ /// The player to frame.
+ /// Should the kill be indirect.
+ /// Should the kill ignore defense.
+ /// End result for the murder.
+ /// Should the kill timer be reset.
+ /// Should a dead body be created.
+ /// Should the framed player be snapped to the dead player.
+ /// Should the kill animation be shown.
+ /// Should the kill sound be played.
+ [MethodRpc((uint)MiraRpc.ConfirmFramedCustomMurder, LocalHandling = RpcLocalHandling.After)]
+ public static void RpcConfirmFramedCustomMurder(
+ this PlayerControl host,
+ PlayerControl source,
+ PlayerControl target,
+ PlayerControl framed,
+ bool isIndirect,
+ bool ignoreDefense,
+ MurderResultFlags murderResultFlags,
+ bool resetKillTimer = true,
+ bool createDeadBody = true,
+ bool teleportMurderer = true,
+ bool showKillAnim = true,
+ bool playKillSound = true)
+ {
+ if (LobbyBehaviour.Instance || !host.IsHost() || target.Data.IsDead || target.Data.Disconnected)
+ {
+ source.isKilling = false;
+ return;
+ }
+
+ var murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags;
+
+ source.CustomMurder(
+ target,
+ framed,
+ isIndirect,
+ ignoreDefense,
+ murderResultFlags2,
+ resetKillTimer,
+ createDeadBody,
+ teleportMurderer,
+ showKillAnim,
+ playKillSound);
+ }
+
+ ///
+ /// Custom Murder method without networking. If you need a networked version, use .
+ ///
+ /// The killer.
+ /// The player to murder.
+ /// The player to frame, if any.
+ /// Should the kill be indirect.
+ /// Should the kill ignore defense.
+ /// Murder result flags.
+ /// Should the kill timer be reset.
+ /// Should a dead body be created.
+ /// Should the killer be snapped to the dead player.
+ /// Should the kill animation be shown.
+ /// Should the kill sound be played.
+ public static void CustomMurder(
+ this PlayerControl source,
+ PlayerControl target,
+ PlayerControl? framed,
+ bool isIndirect,
+ bool ignoreDefense,
+ MurderResultFlags resultFlags,
+ bool resetKillTimer = true,
+ bool createDeadBody = true,
+ bool teleportMurderer = true,
+ bool showKillAnim = true,
+ bool playKillSound = true)
+ {
+ source.isKilling = false;
+ if (resultFlags.HasFlag(MurderResultFlags.FailedError) || LobbyBehaviour.Instance)
+ {
+ return;
+ }
+
+ if (!ignoreDefense && (resultFlags.HasFlag(MurderResultFlags.FailedProtected) ||
+ (resultFlags.HasFlag(MurderResultFlags.DecisionByHost) &&
+ target.protectedByGuardianId > -1)))
+ {
+ target.protectedByGuardianThisRound = true;
+ var flag = PlayerControl.LocalPlayer.Data.Role.Role == RoleTypes.GuardianAngel;
+ if (flag && PlayerControl.LocalPlayer.Data.PlayerId == target.protectedByGuardianId)
+ {
+ DataManager.Player.Stats.IncrementStat(StatID.Role_GuardianAngel_CrewmatesProtected);
+ AchievementManager.Instance.OnProtectACrewmate();
+ }
+
+ if (source.AmOwner || flag)
+ {
+ target.ShowFailedMurder();
+
+ if (resetKillTimer)
+ {
+ source.SetKillTimer(
+ GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown) / 2f);
+ }
+ }
+ else
+ {
+ target.RemoveProtection();
+ }
+
+ return;
+ }
+
+ if (!resultFlags.HasFlag(MurderResultFlags.Succeeded) &&
+ !resultFlags.HasFlag(MurderResultFlags.DecisionByHost))
+ {
+ return;
+ }
+
+ DebugAnalytics.Instance.Analytics.Kill(target.Data, source.Data);
+ if (source.AmOwner)
+ {
+ DataManager.Player.Stats.IncrementStat(
+ GameManager.Instance.IsHideAndSeek()
+ ? StatID.HideAndSeek_ImpostorKills
+ : StatID.ImpostorKills);
+
+ if (source.CurrentOutfitType == PlayerOutfitType.Shapeshifted)
+ {
+ DataManager.Player.Stats.IncrementStat(StatID.Role_Shapeshifter_ShiftedKills);
+ }
+
+ if (Constants.ShouldPlaySfx() && playKillSound)
+ {
+ SoundManager.Instance.PlaySound(source.KillSfx, false, 0.8f);
+ }
+
+ if (resetKillTimer)
+ {
+ source.SetKillTimer(GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown));
+ }
+ }
+
+ UnityTelemetry.Instance.WriteMurder();
+ target.gameObject.layer = LayerMask.NameToLayer("Ghost");
+ if (target.AmOwner)
+ {
+ DataManager.Player.Stats.IncrementStat(StatID.TimesMurdered);
+ if (Minigame.Instance)
+ {
+ try
+ {
+ Minigame.Instance.Close();
+ Minigame.Instance.Close();
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ if (showKillAnim)
+ {
+ try
+ {
+ HudManager.Instance.KillOverlay.ShowKillAnimation(source.Data, target.Data);
+ }
+ catch (Exception e)
+ {
+ Error($"Error with kill animation: {e.ToString()}");
+ }
+ }
+
+ target.cosmetics.SetNameMask(false);
+ target.RpcSetScanner(false);
+ }
+
+ AchievementManager.Instance.OnMurder(
+ source.AmOwner,
+ target.AmOwner,
+ source.CurrentOutfitType == PlayerOutfitType.Shapeshifted,
+ source.shapeshiftTargetPlayerId,
+ target.PlayerId);
+ source.MyPhysics.StartCoroutine(source.KillAnimations.RandomSnapshot().CoPerformCustomKill(source, target, framed, isIndirect, ignoreDefense, createDeadBody, teleportMurderer));
+ }
+
///
/// Custom Murder method without networking. If you need a networked version, use .
///
@@ -290,7 +681,7 @@ public static class CustomMurderRpc
source.CurrentOutfitType == PlayerOutfitType.Shapeshifted,
source.shapeshiftTargetPlayerId,
target.PlayerId);
- source.MyPhysics.StartCoroutine(source.KillAnimations.RandomSnapshot().CoPerformCustomKill(source, target, createDeadBody, teleportMurderer));
+ source.MyPhysics.StartCoroutine(source.KillAnimations.RandomSnapshot().CoPerformCustomKill(source, target, null, false, false, createDeadBody, teleportMurderer));
}
///
@@ -299,6 +690,9 @@ public static class CustomMurderRpc
/// The kill animation.
/// The murderer.
/// The murdered player.
+ /// The framed player, if any.
+ /// Was the kill indirect.
+ /// Would the kill ignore defense.
/// Should a dead body be created.
/// Should the murder be teleported.
/// Coroutine.
@@ -306,6 +700,9 @@ public static class CustomMurderRpc
this KillAnimation anim,
PlayerControl source,
PlayerControl target,
+ PlayerControl? framed,
+ bool isIndirect,
+ bool ignoreDefense,
bool createDeadBody = true,
bool teleportMurderer = true)
{
@@ -314,12 +711,13 @@ public static class CustomMurderRpc
yield break;
}
var cam = Camera.main?.GetComponent();
- var isParticipant = source.AmOwner || target.AmOwner;
- var sourcePhys = source.MyPhysics;
+ var playerToLunge = framed ?? source;
+ var isParticipant = playerToLunge.AmOwner || target.AmOwner;
+ var sourcePhys = playerToLunge.MyPhysics;
if (teleportMurderer)
{
- KillAnimation.SetMovement(source, false);
+ KillAnimation.SetMovement(playerToLunge, false);
}
KillAnimation.SetMovement(target, false);
@@ -373,7 +771,7 @@ public static class CustomMurderRpc
yield return source.MyPhysics.Animations.CoPlayCustomAnimation(anim.BlurAnim);
sourcePhys.Animations.PlayIdleAnimation();
source.NetTransform.SnapTo(target.transform.position);
- KillAnimation.SetMovement(source, true);
+ KillAnimation.SetMovement(playerToLunge, true);
}
KillAnimation.SetMovement(target, true);
@@ -383,7 +781,7 @@ public static class CustomMurderRpc
deadBody.enabled = true;
}
- var afterMurderEvent = new AfterMurderEvent(source, target, deadBody);
+ var afterMurderEvent = framed != null ? new AfterMurderEvent(source, target, framed, deadBody, isIndirect, ignoreDefense) : new AfterMurderEvent(source, target, deadBody, isIndirect, ignoreDefense);
MiraEventManager.InvokeEvent(afterMurderEvent);
if (!isParticipant)
diff --git a/MiraAPI/Networking/MiraRpc.cs b/MiraAPI/Networking/MiraRpc.cs
index edc3a94..25312e0 100644
--- a/MiraAPI/Networking/MiraRpc.cs
+++ b/MiraAPI/Networking/MiraRpc.cs
@@ -40,6 +40,26 @@ public enum MiraRpc : uint
///
ConfirmCustomMurder,
+ ///
+ /// Advanced Custom Murder RPC.
+ ///
+ AdvancedCustomMurder,
+
+ ///
+ /// Advanced Custom Murder RPC Confirmation.
+ ///
+ ConfirmAdvancedCustomMurder,
+
+ ///
+ /// Framed Custom Murder RPC.
+ ///
+ FramedCustomMurder,
+
+ ///
+ /// Framed Custom Murder RPC Confirmation.
+ ///
+ ConfirmFramedCustomMurder,
+
///
/// Custom Game Over RPC.
///
From 83d55d409b6dd5ad2fd1d2e19b6322e4c48e59b5 Mon Sep 17 00:00:00 2001
From: Atony <75191600+AtonyGit@users.noreply.github.com>
Date: Thu, 13 Aug 2026 10:07:40 -0500
Subject: [PATCH 4/5] Change indirect and defense flags to get sets
---
.../Vanilla/Gameplay/BeforeMurderEvent.cs | 8 +--
MiraAPI/Networking/CustomMurderRpc.cs | 61 ++++++++++++++-----
2 files changed, 49 insertions(+), 20 deletions(-)
diff --git a/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs b/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs
index 8a770dc..00a977e 100644
--- a/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs
+++ b/MiraAPI/Events/Vanilla/Gameplay/BeforeMurderEvent.cs
@@ -23,15 +23,15 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent
public PlayerControl? Framed { get; }
///
- /// Gets a value indicating whether the murder is done indirectly.
+ /// Gets or sets a value indicating whether the murder is done indirectly.
/// This is commonly used to prevent the from being killed.
///
- public bool IsIndirectAttack { get; }
+ public bool IsIndirectAttack { get; set; }
///
- /// Gets a value indicating whether the murder should bypass protective roles or general defense, such as .
+ /// Gets or sets a value indicating whether the murder should bypass protective roles or general defense, such as .
///
- public bool IgnoreDefense { get; }
+ public bool IgnoreDefense { get; set; }
///
/// Gets whether the murder was meant to be done in a meeting, via an enum.
diff --git a/MiraAPI/Networking/CustomMurderRpc.cs b/MiraAPI/Networking/CustomMurderRpc.cs
index b53fd85..0622492 100644
--- a/MiraAPI/Networking/CustomMurderRpc.cs
+++ b/MiraAPI/Networking/CustomMurderRpc.cs
@@ -84,13 +84,15 @@ public static class CustomMurderRpc
var beforeMurderEvent = new BeforeMurderEvent(source, target, inMeeting);
MiraEventManager.InvokeEvent(beforeMurderEvent);
+ var defenseFlag = beforeMurderEvent.IgnoreDefense;
+ var indirectFlag = beforeMurderEvent.IsIndirectAttack;
var isMeetingActive = MeetingHud.Instance != null || ExileController.Instance != null;
if ((inMeeting is MeetingCheck.ForMeeting && !isMeetingActive) || (inMeeting is MeetingCheck.OutsideMeeting && isMeetingActive))
{
beforeMurderEvent.Cancel();
}
- if (target.ProtectedByGa())
+ if (!defenseFlag && target.ProtectedByGa())
{
beforeMurderEvent.Cancel();
murderResultFlags = MurderResultFlags.FailedProtected;
@@ -114,6 +116,8 @@ public static class CustomMurderRpc
PlayerControl.LocalPlayer,
source,
target,
+ indirectFlag,
+ defenseFlag,
murderResultFlags,
resetKillTimer,
createDeadBody,
@@ -128,6 +132,8 @@ public static class CustomMurderRpc
/// The game host.
/// The killer.
/// The player to murder.
+ /// Should the kill be indirect.
+ /// Should the kill ignore defense.
/// End result for the murder.
/// Should the kill timer be reset.
/// Should a dead body be created.
@@ -139,6 +145,8 @@ public static class CustomMurderRpc
this PlayerControl host,
PlayerControl source,
PlayerControl target,
+ bool isIndirect,
+ bool ignoreDefense,
MurderResultFlags murderResultFlags,
bool resetKillTimer = true,
bool createDeadBody = true,
@@ -154,14 +162,31 @@ public static class CustomMurderRpc
var murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags;
- source.CustomMurder(
- target,
- murderResultFlags2,
- resetKillTimer,
- createDeadBody,
- teleportMurderer,
- showKillAnim,
- playKillSound);
+ if (isIndirect || ignoreDefense)
+ {
+ source.CustomMurder(
+ target,
+ null,
+ isIndirect,
+ ignoreDefense,
+ murderResultFlags2,
+ resetKillTimer,
+ createDeadBody,
+ teleportMurderer,
+ showKillAnim,
+ playKillSound);
+ }
+ else
+ {
+ source.CustomMurder(
+ target,
+ murderResultFlags2,
+ resetKillTimer,
+ createDeadBody,
+ teleportMurderer,
+ showKillAnim,
+ playKillSound);
+ }
}
///
@@ -200,13 +225,15 @@ public static class CustomMurderRpc
var beforeMurderEvent = new BeforeMurderEvent(source, target, isIndirect, ignoreDefense, inMeeting);
MiraEventManager.InvokeEvent(beforeMurderEvent);
+ var defenseFlag = beforeMurderEvent.IgnoreDefense;
+ var indirectFlag = beforeMurderEvent.IsIndirectAttack;
var isMeetingActive = MeetingHud.Instance != null || ExileController.Instance != null;
if ((inMeeting is MeetingCheck.ForMeeting && !isMeetingActive) || (inMeeting is MeetingCheck.OutsideMeeting && isMeetingActive))
{
beforeMurderEvent.Cancel();
}
- if (!ignoreDefense && target.ProtectedByGa())
+ if (!defenseFlag && target.ProtectedByGa())
{
beforeMurderEvent.Cancel();
murderResultFlags = MurderResultFlags.FailedProtected;
@@ -230,8 +257,8 @@ public static class CustomMurderRpc
PlayerControl.LocalPlayer,
source,
target,
- isIndirect,
- ignoreDefense,
+ indirectFlag,
+ defenseFlag,
murderResultFlags,
resetKillTimer,
createDeadBody,
@@ -327,13 +354,15 @@ public static class CustomMurderRpc
var beforeMurderEvent = new BeforeMurderEvent(source, target, framed, isIndirect, ignoreDefense, inMeeting);
MiraEventManager.InvokeEvent(beforeMurderEvent);
+ var defenseFlag = beforeMurderEvent.IgnoreDefense;
+ var indirectFlag = beforeMurderEvent.IsIndirectAttack;
var isMeetingActive = MeetingHud.Instance != null || ExileController.Instance != null;
if ((inMeeting is MeetingCheck.ForMeeting && !isMeetingActive) || (inMeeting is MeetingCheck.OutsideMeeting && isMeetingActive))
{
beforeMurderEvent.Cancel();
}
- if (!ignoreDefense && target.ProtectedByGa())
+ if (!defenseFlag && target.ProtectedByGa())
{
beforeMurderEvent.Cancel();
murderResultFlags = MurderResultFlags.FailedProtected;
@@ -358,8 +387,8 @@ public static class CustomMurderRpc
source,
target,
framed,
- isIndirect,
- ignoreDefense,
+ indirectFlag,
+ defenseFlag,
murderResultFlags,
resetKillTimer,
createDeadBody,
@@ -420,7 +449,7 @@ public static class CustomMurderRpc
}
///
- /// Custom Murder method without networking. If you need a networked version, use .
+ /// Custom Murder method without networking. If you need a networked version, use or .
///
/// The killer.
/// The player to murder.
From 6fd5993959fd0301c68e0c03b01cdda692e0292d Mon Sep 17 00:00:00 2001
From: Az <56699741+AlchlcDvl@users.noreply.github.com>
Date: Thu, 13 Aug 2026 23:27:07 -0400
Subject: [PATCH 5/5] pass shader caching through helper method
---
MiraAPI/Utilities/ShaderID.cs | 76 +++++++++++++++++------------------
1 file changed, 38 insertions(+), 38 deletions(-)
diff --git a/MiraAPI/Utilities/ShaderID.cs b/MiraAPI/Utilities/ShaderID.cs
index 3ef92e6..3f2855a 100644
--- a/MiraAPI/Utilities/ShaderID.cs
+++ b/MiraAPI/Utilities/ShaderID.cs
@@ -24,80 +24,80 @@ public static class ShaderID
}
// For player shader
- public static readonly int BodyColor = Shader.PropertyToID("_BodyColor");
- public static readonly int BackColor = Shader.PropertyToID("_BackColor");
- public static readonly int VisorColor = Shader.PropertyToID("_VisorColor");
+ public static readonly int BodyColor = Get("_BodyColor");
+ public static readonly int BackColor = Get("_BackColor");
+ public static readonly int VisorColor = Get("_VisorColor");
// Main texture, very obviously used in any shader with a texture
- public static readonly int MainTex = Shader.PropertyToID("_MainTex");
+ public static readonly int MainTex = Get("_MainTex");
// Any masking stuff, like MeetingHud bubbles
- public static readonly int Mask = Shader.PropertyToID("_Mask");
- public static readonly int MaskComp = Shader.PropertyToID("_MaskComp");
- public static readonly int MaskLayer = Shader.PropertyToID("_MaskLayer");
- public static readonly int Stencil = Shader.PropertyToID("_Stencil");
- public static readonly int StencilComp = Shader.PropertyToID("_StencilComp");
+ public static readonly int Mask = Get("_Mask");
+ public static readonly int MaskComp = Get("_MaskComp");
+ public static readonly int MaskLayer = Get("_MaskLayer");
+ public static readonly int Stencil = Get("_Stencil");
+ public static readonly int StencilComp = Get("_StencilComp");
// Used in many tasks
- public static readonly int Color = Shader.PropertyToID("_Color");
+ public static readonly int Color = Get("_Color");
// Has 2 uses in the game, provided for convenience
- public static readonly int Opacity = Shader.PropertyToID("_Opacity");
+ public static readonly int Opacity = Get("_Opacity");
// Used once in CooldownHelpers, once in PowerBarMining
- public static readonly int NormalizedUvs = Shader.PropertyToID("_NormalizedUvs");
+ public static readonly int NormalizedUvs = Get("_NormalizedUvs");
// Used in many consoles
- public static readonly int Outline = Shader.PropertyToID("_Outline");
- public static readonly int OutlineColor = Shader.PropertyToID("_OutlineColor");
+ public static readonly int Outline = Get("_Outline");
+ public static readonly int OutlineColor = Get("_OutlineColor");
// Used in some consoles
- public static readonly int AddColor = Shader.PropertyToID("_AddColor");
+ public static readonly int AddColor = Get("_AddColor");
// Has some uses in various locations
- public static readonly int Percent = Shader.PropertyToID("_Percent");
- public static readonly int PercentY = Shader.PropertyToID("_PercentY");
- public static readonly int Desat = Shader.PropertyToID("_Desat");
+ public static readonly int Percent = Get("_Percent");
+ public static readonly int PercentY = Get("_PercentY");
+ public static readonly int Desat = Get("_Desat");
// Used in LightSource
- public static readonly int PlayerRadius = Shader.PropertyToID("_PlayerRadius");
- public static readonly int LightRadius = Shader.PropertyToID("_LightRadius");
- public static readonly int LightOffset = Shader.PropertyToID("_LightOffset");
- public static readonly int FlashlightSize = Shader.PropertyToID("_FlashlightSize");
- public static readonly int FlashlightAngle = Shader.PropertyToID("_FlashlightAngle");
+ public static readonly int PlayerRadius = Get("_PlayerRadius");
+ public static readonly int LightRadius = Get("_LightRadius");
+ public static readonly int LightOffset = Get("_LightOffset");
+ public static readonly int FlashlightSize = Get("_FlashlightSize");
+ public static readonly int FlashlightAngle = Get("_FlashlightAngle");
// Used once for LightSourceGpuRenderer
- public static readonly int DepthCompressionValue = Shader.PropertyToID("_DepthCompressionValue");
+ public static readonly int DepthCompressionValue = Get("_DepthCompressionValue");
// Used in ProgressTracker
- public static readonly int Buckets = Shader.PropertyToID("_Buckets");
- public static readonly int FullBuckets = Shader.PropertyToID("_FullBuckets");
+ public static readonly int Buckets = Get("_Buckets");
+ public static readonly int FullBuckets = Get("_FullBuckets");
// Used once in IntroCutscene and EndGameManager
- public static readonly int Rad = Shader.PropertyToID("_Rad");
+ public static readonly int Rad = Get("_Rad");
// Used only once for Quick Chat
- public static readonly int FaceColor = Shader.PropertyToID("_FaceColor");
+ public static readonly int FaceColor = Get("_FaceColor");
// Used for NavigationMinigame
- public static readonly int CrossHair = Shader.PropertyToID("_CrossHair");
- public static readonly int CrossColor = Shader.PropertyToID("_CrossColor");
+ public static readonly int CrossHair = Get("_CrossHair");
+ public static readonly int CrossColor = Get("_CrossColor");
// Used for both SurveillanceMinigame (Planet and Normal)
- public static readonly int Center = Shader.PropertyToID("_Center");
- public static readonly int Color2 = Shader.PropertyToID("_Color2");
+ public static readonly int Center = Get("_Center");
+ public static readonly int Color2 = Get("_Color2");
// Used in ReactorShipRoom
- public static readonly int Speed = Shader.PropertyToID("_Speed");
+ public static readonly int Speed = Get("_Speed");
// Used only once in CourseMinigame
- public static readonly int AltTex = Shader.PropertyToID("_AltTex");
- public static readonly int Perc = Shader.PropertyToID("_Perc");
+ public static readonly int AltTex = Get("_AltTex");
+ public static readonly int Perc = Get("_Perc");
// Used once in TextMarquee
- public static readonly int VertexOffsetX = Shader.PropertyToID("_VertexOffsetX");
- public static readonly int VertexOffsetY = Shader.PropertyToID("_VertexOffsetY");
+ public static readonly int VertexOffsetX = Get("_VertexOffsetX");
+ public static readonly int VertexOffsetY = Get("_VertexOffsetY");
// Used in VertLineBehaviour
- public static readonly int Fade = Shader.PropertyToID("_Fade");
+ public static readonly int Fade = Get("_Fade");
}