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"); }