From ac7360c081107fab9bd1ad31e9032b2f2197c8ca Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Fri, 13 Mar 2026 16:54:28 +0000 Subject: [PATCH 01/10] Necromancer Disconnection Fix --- .gitignore | 1 + NewMod/Buttons/Necromancer/ReviveButton.cs | 3 +- NewMod/Buttons/WraithCaller/CallWraith.cs | 2 +- NewMod/Components/WraithCallerNpc.cs | 108 ++++++------ NewMod/CustomRPC.cs | 2 +- NewMod/NewMod.csproj | 2 +- NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs | 6 +- NewMod/Roles/CrewmateRoles/Specialist.cs | 12 +- NewMod/Roles/ImpostorRoles/Revenant.cs | 17 +- NewMod/Utilities/CoroutinesHelper.cs | 15 +- NewMod/Utilities/Utils.cs | 148 +++++++--------- NewMod/Utilities/WraithCallerUtilities.cs | 162 ++++++++++-------- 12 files changed, 238 insertions(+), 240 deletions(-) diff --git a/.gitignore b/.gitignore index 74c2aaa..7c0fbec 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ obj/ References/ NewMod/Components/Minigames NewMod/Components/Hidden.cs +NewMod/Private /packages/ riderModule.iml .idea diff --git a/NewMod/Buttons/Necromancer/ReviveButton.cs b/NewMod/Buttons/Necromancer/ReviveButton.cs index e9b717b..9247227 100644 --- a/NewMod/Buttons/Necromancer/ReviveButton.cs +++ b/NewMod/Buttons/Necromancer/ReviveButton.cs @@ -59,10 +59,9 @@ protected override void OnClick() var closestBody = Utils.GetClosestBody(); if (closestBody != null) { - Utils.RpcRevive(closestBody); + Utils.HandleRevive(PlayerControl.LocalPlayer, closestBody.ParentId, AmongUs.GameOptions.RoleTypes.Impostor, closestBody.TruePosition.x, closestBody.TruePosition.y); } } - /// /// Determines whether this button is enabled for the role, returning true if the role is . /// diff --git a/NewMod/Buttons/WraithCaller/CallWraith.cs b/NewMod/Buttons/WraithCaller/CallWraith.cs index 7a1031f..a3d8e75 100644 --- a/NewMod/Buttons/WraithCaller/CallWraith.cs +++ b/NewMod/Buttons/WraithCaller/CallWraith.cs @@ -85,7 +85,7 @@ protected override void OnClick() player => { menu.Close(); - WraithCallerUtilities.RpcSummonNPC(PlayerControl.LocalPlayer, player); + WraithCallerUtilities.RpcRequestSummonNPC(PlayerControl.LocalPlayer, player); SetTimerPaused(false); }); diff --git a/NewMod/Components/WraithCallerNpc.cs b/NewMod/Components/WraithCallerNpc.cs index 599081d..9df95e7 100644 --- a/NewMod/Components/WraithCallerNpc.cs +++ b/NewMod/Components/WraithCallerNpc.cs @@ -19,52 +19,45 @@ public class WraithCallerNpc(IntPtr ptr) : MonoBehaviour(ptr) public PlayerControl Target { get; set; } public PlayerControl npc; public LightSource ownerLight; - public bool isActive = false; + public bool isActive; [HideFromIl2Cpp] - // Inspired by: https://github.com/NuclearPowered/Reactor/blob/e27a79249ea706318f3c06f3dc56a5c42d65b1cf/Reactor.Debugger/Window/Tabs/GameTab.cs#L70 - public void Initialize(PlayerControl wraith, PlayerControl target) + public void Initialize(PlayerControl wraith, PlayerControl target, PlayerControl spawned) { Owner = wraith; Target = target; + npc = spawned; - var prefab = AmongUsClient.Instance.PlayerPrefab; - npc = Instantiate(prefab); - npc.PlayerId = (byte)GameData.Instance.GetAvailableId(); - - var npcData = GameData.Instance.AddDummy(npc); - AmongUsClient.Instance.Spawn(npcData); - AmongUsClient.Instance.Spawn(npc); + isActive = true; - npc.isDummy = false; - npc.notRealPlayer = true; KillAnimation.SetMovement(npc, true); - npc.NetTransform.RpcSnapTo(Owner.transform.position); - npc.MyPhysics.Speed = OptionGroupSingleton.Instance.NPCSpeed; - - var color = (byte)(npc.PlayerId % Palette.PlayerColors.Length); - npc.RpcSetName("Wraith NPC"); - npc.RpcSetColor(color); - npc.RpcSetHat(""); - npc.RpcSetSkin(""); - npc.RpcSetPet(""); - npc.RpcSetVisor(""); npc.Collider.enabled = false; + npc.MyPhysics.Speed = OptionGroupSingleton.Instance.NPCSpeed; - var noShadow = npc.gameObject.AddComponent(); - if (noShadow != null) - { - noShadow.rend = npc.cosmetics.currentBodySprite.BodySprite; - noShadow.hitOverride = npc.Collider; - } + if (!npc.TryGetComponent(out _)) + npc.gameObject.AddComponent(); - if (!npc.TryGetComponent(out var mc)) + if (AmongUsClient.Instance.AmHost) { - mc = npc.gameObject.AddComponent(); + NewMod.Instance.Log.LogMessage($"Host is setting cosmetics for NPC (ID: {npc.PlayerId}"); + npc.NetTransform.RpcSnapTo(Owner.transform.position); + + var color = (byte)(npc.PlayerId % Palette.PlayerColors.Length); + npc.RpcSetName("Wraith NPC"); + npc.RpcSetColor(color); + + var noShadow = npc.gameObject.AddComponent(); + if (noShadow != null) + { + noShadow.rend = npc.cosmetics.currentBodySprite.BodySprite; + noShadow.hitOverride = npc.Collider; + } } - + + Coroutines.Start(WalkToTarget()); + if (OptionGroupSingleton.Instance.ShouldSwitchCamToNPC) { Camera.main.GetComponent().SetTarget(npc); @@ -73,52 +66,46 @@ public void Initialize(PlayerControl wraith, PlayerControl target) ownerLight.transform.localPosition = npc.Collider.offset; } - npc.cosmetics.enabled = false; + npc.cosmetics.enabled = true; npc.enabled = false; - isActive = true; - - Coroutines.Start(WalkToTarget()); - if (Target.AmOwner) - { SoundManager.Instance.PlaySound(NewModAsset.HeartbeatSound.LoadAsset(), false, 1f); - } } + [HideFromIl2Cpp] - public System.Collections.IEnumerator WalkToTarget() + private System.Collections.IEnumerator WalkToTarget() { - if (Target.Data.IsDead || Target.Data.Disconnected) - { - Dispose(); - } + //yield return null; + + if (!AmongUsClient.Instance.AmHost) yield break; + while (isActive && !MeetingHud.Instance) { + if (!Target || !Target.Data || Target.Data.IsDead) + break; + Vector2 npcPos = npc.GetTruePosition(); Vector2 targetPos = Target.GetTruePosition(); Vector2 dir = (targetPos - npcPos).normalized; npc.MyPhysics.SetNormalizedVelocity(dir); - float distance = Vector2.Distance(npcPos, targetPos); - - if (distance <= 0.1f) + if (Vector2.Distance(npcPos, targetPos) <= 0.1f) { npc.MyPhysics.SetNormalizedVelocity(Vector2.zero); Owner.RpcCustomMurder(Target, true, teleportMurderer: false); if (Target.AmOwner) - { CoroutinesHelper.CoNotify("Oops! The Wraith NPC got you..."); - } - WraithCallerUtilities.AddKillNPC(Owner.PlayerId); - Dispose(); - yield break; + WraithCallerUtilities.AddKillNPC(Owner.PlayerId); + break; } yield return new WaitForFixedUpdate(); } + npc.MyPhysics.SetNormalizedVelocity(Vector2.zero); Dispose(); } @@ -127,25 +114,26 @@ public System.Collections.IEnumerator WalkToTarget() public void Dispose() { if (!isActive) return; - isActive = false; - if (npc != null) + if (OptionGroupSingleton.Instance.ShouldSwitchCamToNPC) + { + Camera.main.GetComponent().SetTarget(Owner); + ownerLight.transform.SetParent(Owner.transform, false); + ownerLight.transform.localPosition = Owner.Collider.offset; + } + + if (AmongUsClient.Instance.AmHost) { - if (OptionGroupSingleton.Instance.ShouldSwitchCamToNPC) - { - var cam = Camera.main.GetComponent(); - cam.SetTarget(Owner); - ownerLight.transform.SetParent(Owner.transform, false); - ownerLight.transform.localPosition = Owner.Collider.offset; - } var info = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(d => d.PlayerId == npc.PlayerId); GameData.Instance.RemovePlayer(info.PlayerId); PlayerControl.AllPlayerControls.Remove(npc); + npc.Despawn(); Destroy(npc.gameObject); npc = null; } + Destroy(gameObject); } } diff --git a/NewMod/CustomRPC.cs b/NewMod/CustomRPC.cs index c340bf9..c196db1 100644 --- a/NewMod/CustomRPC.cs +++ b/NewMod/CustomRPC.cs @@ -2,7 +2,7 @@ namespace NewMod; public enum CustomRPC { - Revive, + HandleRevive, Drain, FakeBody, AssignMission, diff --git a/NewMod/NewMod.csproj b/NewMod/NewMod.csproj index ecceb52..b21bb28 100644 --- a/NewMod/NewMod.csproj +++ b/NewMod/NewMod.csproj @@ -12,7 +12,7 @@ - + diff --git a/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs b/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs index 08f5635..cdf22c2 100644 --- a/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs +++ b/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs @@ -19,13 +19,11 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] EndGam VisionaryUtilities.DeleteAllScreenshots(); WraithCallerUtilities.ClearAll(); Shade.ShadeKills.Clear(); - Revenant.HasUsedFeignDeath = false; - Revenant.FeignDeathStates.Remove(PlayerControl.LocalPlayer.PlayerId); - Revenant.StalkingStates[PlayerControl.LocalPlayer.PlayerId] = false; + Revenant.ResetAllStates(); NewMod.Instance.Log.LogInfo("Reset Drain Count Successfully"); NewMod.Instance.Log.LogInfo("Reset Clone Report Count Successfully"); NewMod.Instance.Log.LogInfo("Reset Mission Success Count Successfully"); NewMod.Instance.Log.LogInfo("Reset Mission Failure Count Successfully"); NewMod.Instance.Log.LogInfo("Deleted all Visionary's screenshots Successfully"); } -} \ No newline at end of file +} diff --git a/NewMod/Roles/CrewmateRoles/Specialist.cs b/NewMod/Roles/CrewmateRoles/Specialist.cs index fac940d..c43cb92 100644 --- a/NewMod/Roles/CrewmateRoles/Specialist.cs +++ b/NewMod/Roles/CrewmateRoles/Specialist.cs @@ -37,17 +37,17 @@ public class Specialist : CrewmateRole, ICustomRole [RegisterEvent] public static void OnTaskComplete(CompleteTaskEvent evt) { - PlayerControl player = evt.Player; - if (player.Data.Role is not Specialist) return; + PlayerControl specialist = evt.Player; + if (specialist.Data.Role is not Specialist) return; List abilityAction = new List { () => { - var target = Utils.GetRandomPlayer(p => !p.Data.IsDead && !p.Data.Disconnected && p != player); + var target = Utils.GetRandomPlayer(p => !p.Data.IsDead && !p.Data.Disconnected && p != specialist); if (target != null) { - Utils.RpcRandomDrainActions(player, target); + Utils.RpcRandomDrainActions(specialist, target); Helpers.CreateAndShowNotification($"Energy Drain activated on {target.Data.PlayerName}!",Color.green); } }, @@ -57,13 +57,13 @@ public static void OnTaskComplete(CompleteTaskEvent evt) var player = Utils.PlayerById(closestBody.ParentId); if (closestBody != null) { - Utils.RpcRevive(closestBody); + Utils.HandleRevive(specialist, closestBody.ParentId, AmongUs.GameOptions.RoleTypes.Crewmate, closestBody.transform.position.x, closestBody.transform.position.y); Helpers.CreateAndShowNotification($"Player {player.Data.PlayerName} has been revived.", Color.green); } }, () => { - PranksterUtilities.CreatePranksterDeadBody(player, player.PlayerId); + PranksterUtilities.CreatePranksterDeadBody(specialist, specialist.PlayerId); Helpers.CreateAndShowNotification("Fake Body created!", Color.green); }, () => diff --git a/NewMod/Roles/ImpostorRoles/Revenant.cs b/NewMod/Roles/ImpostorRoles/Revenant.cs index 1d067f1..e71f56e 100644 --- a/NewMod/Roles/ImpostorRoles/Revenant.cs +++ b/NewMod/Roles/ImpostorRoles/Revenant.cs @@ -41,17 +41,16 @@ public class FeignDeathInfo public bool Reported; } + public static void ResetAllStates() + { + FeignDeathStates.Clear(); + StalkingStates.Clear(); + HasUsedFeignDeath = false; + } + [RegisterEvent] public static void OnPlayerExit(PlayerLeaveEvent evt) { - if (FeignDeathStates.ContainsKey(evt.ClientData.Character.PlayerId)) - { - FeignDeathStates.Remove(evt.ClientData.Character.PlayerId); - } - if (StalkingStates.ContainsKey(evt.ClientData.Character.PlayerId)) - { - StalkingStates.Remove(evt.ClientData.Character.PlayerId); - } - HasUsedFeignDeath = false; + ResetAllStates(); } } diff --git a/NewMod/Utilities/CoroutinesHelper.cs b/NewMod/Utilities/CoroutinesHelper.cs index 23f1be4..d956e8c 100644 --- a/NewMod/Utilities/CoroutinesHelper.cs +++ b/NewMod/Utilities/CoroutinesHelper.cs @@ -9,6 +9,7 @@ using NewMod.Roles.NeutralRoles; using Reactor.Utilities.Extensions; using NewMod.Components.ScreenEffects; +using AmongUs.GameOptions; namespace NewMod.Utilities { @@ -296,7 +297,7 @@ public static IEnumerator CoReviveAndKill(PlayerControl target) { revivedParentId = deadBody.ParentId; - Utils.RpcRevive(deadBody); + Utils.HandleRevive(target, deadBody.ParentId, RoleTypes.Crewmate, deadBody.transform.position.x, deadBody.transform.position.y); yield return new WaitForSeconds(0.5f); @@ -456,5 +457,17 @@ public static IEnumerator RemoveCameraEffect(Camera cam, float duration) if (cam.TryGetComponent(out var sf)) Object.Destroy(sf); } + public static IEnumerator CoRevive(PlayerControl revived, RoleTypes role) + { + yield return new WaitForSeconds(0.15f); + + var body = GameObject.FindObjectsOfType().FirstOrDefault(b => b.ParentId == revived.PlayerId); + + if (body != null) + Object.Destroy(body.gameObject); + + revived.Revive(); + revived.RpcSetRole(role); + } } } diff --git a/NewMod/Utilities/Utils.cs b/NewMod/Utilities/Utils.cs index 8aead77..a5267d0 100644 --- a/NewMod/Utilities/Utils.cs +++ b/NewMod/Utilities/Utils.cs @@ -153,57 +153,6 @@ public static DeadBody GetClosestBody() return closestBody; } - // Inspired By : https://github.com/eDonnes124/Town-Of-Us-R/blob/master/source/Patches/CrewmateRoles/AltruistMod/Coroutine.cs#L57 - public static void Revive(DeadBody body) - { - if (body == null) return; - - var parentId = body.ParentId; - var player = PlayerById(parentId); - - if (player != null) - { - foreach (var deadBody in GameObject.FindObjectsOfType()) - { - if (deadBody.ParentId == body.ParentId) - Object.Destroy(deadBody.gameObject); - } - player.Revive(); - - if (player.Data.Role is NoisemakerRole role) - { - Object.Destroy(role.deathArrowPrefab.gameObject); - } - player.RpcSetRole(RoleTypes.Impostor, true); - } - } - - // Inspired By : https://github.com/eDonnes124/Town-Of-Us-R/blob/master/source/Patches/CrewmateRoles/AltruistMod/Coroutine.cs#L57 - public static void ReviveV2(DeadBody body) - { - if (body == null) return; - - var parentId = body.ParentId; - var player = PlayerById(parentId); - - if (player != null) - { - foreach (var deadBody in GameObject.FindObjectsOfType()) - { - if (deadBody.ParentId == body.ParentId) - Object.Destroy(deadBody.gameObject); - } - player.Revive(); - - if (player.Data.Role is NoisemakerRole role) - { - Object.Destroy(role.deathArrowPrefab.gameObject); - } - player.RpcSetRole((RoleTypes)RoleId.Get(), true); - NewMod.Instance.Log.LogError($"---------------^^^^^^SETTING ROLE TO REVENANT-------------^^^^ NEW ROLE: {player.Data.Role.NiceName}"); - } - } - // Thanks to: https://github.com/Rabek009/MoreGamemodes/blob/master/Modules/Utils.cs#L66 /// /// Checks if a particular system type is active on the current map. @@ -423,38 +372,71 @@ public static void ResetInjections() { InjectedPlayerIds.Clear(); } - /// - /// Sends an RPC to revive a player from a dead body. - /// - /// The DeadBody instance to revive from. - public static void RpcRevive(DeadBody body) - { - Revive(body); - var writer = AmongUsClient.Instance.StartRpcImmediately( - PlayerControl.LocalPlayer.NetId, - (byte)CustomRPC.Revive, - SendOption.Reliable - ); - writer.Write(PlayerControl.LocalPlayer.PlayerId); - writer.Write(body.ParentId); - AmongUsClient.Instance.FinishRpcImmediately(writer); - } + // Inspired By: https://github.com/AU-Avengers/TOU-Mira/blob/dev/TownOfUs/Modules/ReviveUtilities.cs#L40 - /// - /// Sends an RPC to revive a player from a dead body and set their role to Revenant. - /// - /// The DeadBody instance to revive from. - public static void RpcReviveV2(DeadBody body) + [MethodRpc((uint)CustomRPC.HandleRevive)] + public static IEnumerator HandleRevive(PlayerControl source, byte revivedId, RoleTypes roleToSet, float reviveX, float reviveY) { - ReviveV2(body); - var writer = AmongUsClient.Instance.StartRpcImmediately( - PlayerControl.LocalPlayer.NetId, - (byte)CustomRPC.Revive, - SendOption.Reliable - ); - writer.Write(PlayerControl.LocalPlayer.PlayerId); - writer.Write(body.ParentId); - AmongUsClient.Instance.FinishRpcImmediately(writer); + var revived = PlayerById(revivedId); + + if (revived.Data.Disconnected) + yield break; + + yield return new WaitForSeconds(0.15f); + + if (revived.Data.Disconnected || !revived.Data.IsDead) + yield break; + + var revivePos = new Vector2(reviveX, reviveY); + var inMeetingOrExile = MeetingHud.Instance || ExileController.Instance; + + if (revived.Data.Role is NoisemakerRole noisemaker && noisemaker.deathArrowPrefab != null) + { + Object.Destroy(noisemaker.deathArrowPrefab.gameObject); + } + + revived.Revive(); + revived.RemainingEmergencies = 0; + + if (AmongUsClient.Instance.AmHost) + { + revived.RpcSetRole(roleToSet, true); + } + + if (!inMeetingOrExile) + { + revived.transform.position = revivePos; + revived.MyPhysics.body.position = revivePos; + Physics2D.SyncTransforms(); + + if (revived.AmOwner) + { + revived.NetTransform.RpcSnapTo(revivePos); + } + } + + foreach (var deadBody in Object.FindObjectsOfType()) + { + if (deadBody.ParentId == revived.PlayerId) + { + Object.Destroy(deadBody.gameObject); + } + } + + float elapsed = 0f; + while (elapsed < 1f) + { + foreach (var deadBody in Object.FindObjectsOfType()) + { + if (deadBody.ParentId == revived.PlayerId) + { + Object.Destroy(deadBody.gameObject); + } + } + + elapsed += 0.05f; + yield return new WaitForSeconds(0.05f); + } } // Thanks to: https://github.com/yanpla/yanplaRoles/blob/master/Utils.cs#L55 @@ -878,8 +860,8 @@ public static IEnumerator StartFeignDeath(PlayerControl player) yield break; } } - RpcReviveV2(body); - player.transform.position = body.transform.position; + HandleRevive(player, player.PlayerId, (RoleTypes)RoleId.Get(), body.transform.position.x, body.transform.position.y); + yield return new WaitForSeconds(0.2f); player.RpcShapeshift(GetRandomPlayer(p => !p.Data.IsDead && !p.Data.Disconnected), false); Coroutines.Start(CoroutinesHelper.CoNotify("You have been revived in a new body!")); Revenant.HasUsedFeignDeath = true; diff --git a/NewMod/Utilities/WraithCallerUtilities.cs b/NewMod/Utilities/WraithCallerUtilities.cs index 8323c7c..3f8123e 100644 --- a/NewMod/Utilities/WraithCallerUtilities.cs +++ b/NewMod/Utilities/WraithCallerUtilities.cs @@ -1,88 +1,106 @@ - using System.Collections.Generic; - using NewMod.Components; - using Reactor.Networking.Attributes; - using UnityEngine; +using System.Collections.Generic; +using System.Linq; +using NewMod.Components; +using Reactor.Networking.Attributes; +using UnityEngine; - namespace NewMod.Utilities +namespace NewMod.Utilities +{ + public static class WraithCallerUtilities { - public static class WraithCallerUtilities + /// + /// A dictionary holding the number of NPCs sent by each Wraith Caller. + /// + private static readonly Dictionary Sent = []; + + /// + /// A dictionary holding the number of kills achieved by NPCs summoned by each Wraith Caller. + /// + private static readonly Dictionary Kills = []; + + /// + /// Returns the number of NPCs sent by the specified owner. + /// + public static int GetSentNPC(byte ownerId) { - /// - /// A dictionary holding the number of NPCs sent by each Wraith Caller. - /// - private static readonly Dictionary Sent = []; + return Sent.TryGetValue(ownerId, out var v) ? v : 0; + } - /// - /// A dictionary holding the number of kills achieved by NPCs summoned by each Wraith Caller. - /// - private static readonly Dictionary Kills = []; + /// + /// Returns the number of successful kills credited to the owner’s wraiths. + /// + public static int GetKillsNPC(byte ownerId) + { + return Kills.TryGetValue(ownerId, out var v) ? v : 0; + } - /// - /// Returns the number of NPCs sent by the specified owner. - /// - public static int GetSentNPC(byte ownerId) - { - return Sent.TryGetValue(ownerId, out var v) ? v : 0; - } + /// + /// Increments the number of NPCs sent by the owner. + /// + public static void AddSentNPC(byte ownerId, int amount = 1) + { + Sent[ownerId] = GetSentNPC(ownerId) + amount; + } - /// - /// Returns the number of successful kills credited to the owner’s wraiths. - /// - public static int GetKillsNPC(byte ownerId) - { - return Kills.TryGetValue(ownerId, out var v) ? v : 0; - } + /// + /// Increments the number of kills credited to the owner’s wraiths. + /// + public static void AddKillNPC(byte ownerId, int amount = 1) + { + Kills[ownerId] = GetKillsNPC(ownerId) + amount; + } - /// - /// Increments the number of NPCs sent by the owner. - /// - public static void AddSentNPC(byte ownerId, int amount = 1) - { - Sent[ownerId] = GetSentNPC(ownerId) + amount; - } + /// + /// Clears all counters for both NPCs sent and kills achieved. + /// + public static void ClearAll() + { + Sent.Clear(); + Kills.Clear(); + } + [MethodRpc((uint)CustomRPC.RequestSummon)] + public static void RpcRequestSummonNPC(PlayerControl source, PlayerControl target) + { + if (!AmongUsClient.Instance.AmHost) return; - /// - /// Increments the number of kills credited to the owner’s wraiths. - /// - public static void AddKillNPC(byte ownerId, int amount = 1) - { - Kills[ownerId] = GetKillsNPC(ownerId) + amount; - } + var npcId = HostNPC(source); + RpcSummonNPC(source, target, npcId); + } + public static byte HostNPC(PlayerControl source) + { + var prefab = AmongUsClient.Instance.PlayerPrefab; + var npc = Object.Instantiate(prefab); + npc.PlayerId = (byte)GameData.Instance.GetAvailableId(); - /// - /// Clears all counters for both NPCs sent and kills achieved. - /// - public static void ClearAll() - { - Sent.Clear(); - Kills.Clear(); - } + npc.isDummy = false; + npc.notRealPlayer = true; - /// - /// RPC for . Runs on all clients to keep state in sync. - /// - /// The Wraith Caller owner who summoned the NPC. - /// The intended target player. + var host = AmongUsClient.Instance.GetHost(); + var npcInfo = GameData.Instance.AddPlayer(npc, host); - [MethodRpc((uint)CustomRPC.SummonNPC)] - public static void RpcSummonNPC(PlayerControl source, PlayerControl target) - { - AddSentNPC(source.PlayerId); - SummonNPC(source, target); - } + AmongUsClient.Instance.Spawn(npcInfo); + AmongUsClient.Instance.Spawn(npc); - /// - /// Spawns and initializes the wraith NPC that will hunt the target. - /// Runs locally on each client after the RPC dispatch. - /// - /// The Wraith Caller (owner) who summoned the NPC. - /// The intended target player. - public static void SummonNPC(PlayerControl wraith, PlayerControl target) - { - var npcObj = new GameObject("WraithNPC_Holder"); - var wraithNpc = npcObj.AddComponent(); + npc.NetTransform.RpcSnapTo(source.transform.position); + + return npc.PlayerId; + } + [MethodRpc((uint)CustomRPC.SummonNPC)] + public static void RpcSummonNPC(PlayerControl source, PlayerControl target, byte npcId) + { + AddSentNPC(source.PlayerId); - wraithNpc.Initialize(wraith, target); + InitializeNPC(source, target, Utils.PlayerById(npcId)); + return; + + } + public static void InitializeNPC(PlayerControl owner, PlayerControl target, PlayerControl npc) + { + if (!npc.gameObject.TryGetComponent(out _)) + { + var comp = npc.gameObject.AddComponent(); + comp.Initialize(owner, target, npc); } } } +} From 674e4a0f4ef2ccd0c215b0863e45a562aaab2ab6 Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Tue, 17 Mar 2026 12:46:00 +0000 Subject: [PATCH 02/10] Fix CanUse in ReviveButton --- NewMod/Buttons/Necromancer/ReviveButton.cs | 51 ++++++++++------------ NewMod/Utilities/Utils.cs | 2 +- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/NewMod/Buttons/Necromancer/ReviveButton.cs b/NewMod/Buttons/Necromancer/ReviveButton.cs index 9247227..9b92878 100644 --- a/NewMod/Buttons/Necromancer/ReviveButton.cs +++ b/NewMod/Buttons/Necromancer/ReviveButton.cs @@ -6,6 +6,8 @@ using UnityEngine; using NewMod.Utilities; using MiraAPI.Keybinds; +using AmongUs.GameOptions; +using Reactor.Utilities; namespace NewMod.Buttons.Necromancer { @@ -54,13 +56,25 @@ public class ReviveButton : CustomActionButton /// protected override void OnClick() { - SoundManager.Instance.PlaySound(NewModAsset.ReviveSound?.LoadAsset(), false, 2f); - var closestBody = Utils.GetClosestBody(); - if (closestBody != null) + var killedPlayer = GameData.Instance.GetPlayerById(closestBody.ParentId)?.Object; + + var killer = Utils.GetKiller(killedPlayer); + if (killer != null && killer.PlayerId == PlayerControl.LocalPlayer.PlayerId) { - Utils.HandleRevive(PlayerControl.LocalPlayer, closestBody.ParentId, AmongUs.GameOptions.RoleTypes.Impostor, closestBody.TruePosition.x, closestBody.TruePosition.y); + Coroutines.Start(CoroutinesHelper.CoNotify("Heads up: you tried to revive someone you killed, one use has been consumed")); + return; } + + SoundManager.Instance.PlaySound(NewModAsset.ReviveSound?.LoadAsset(), false, 2f); + + Utils.HandleRevive( + PlayerControl.LocalPlayer, + closestBody.ParentId, + RoleTypes.Impostor, + closestBody.transform.position.x, + closestBody.transform.position.y + ); } /// /// Determines whether this button is enabled for the role, returning true if the role is . @@ -78,32 +92,15 @@ public override bool Enabled(RoleBehaviour role) /// True if all requirements to use this button are met; otherwise false. public override bool CanUse() { - bool isTimerDone = Timer <= 0; - bool hasUsesLeft = UsesLeft > 0; + if (Timer > 0) return false; + if (UsesLeft <= 0) return false; + var closestBody = Utils.GetClosestBody(); - bool isNearDeadBody = closestBody != null; - bool isFakeBody = isNearDeadBody && PranksterUtilities.IsPranksterBody(closestBody); + if (closestBody == null) return false; - if (closestBody == null) - { - return false; - } + if (PranksterUtilities.IsPranksterBody(closestBody)) return false; - bool wasNotKilledByNecromancer = true; - var deadBody = closestBody.GetComponent(); - if (deadBody != null) - { - var killedPlayer = GameData.Instance.GetPlayerById(deadBody.ParentId)?.Object; - if (killedPlayer != null) - { - var killer = Utils.GetKiller(killedPlayer); - if (killer != null && killer.Data.Role is NecromancerRole) - { - wasNotKilledByNecromancer = false; - } - } - } - return isTimerDone && hasUsesLeft && isNearDeadBody && wasNotKilledByNecromancer && !isFakeBody; + return true; } } } diff --git a/NewMod/Utilities/Utils.cs b/NewMod/Utilities/Utils.cs index a5267d0..c89208d 100644 --- a/NewMod/Utilities/Utils.cs +++ b/NewMod/Utilities/Utils.cs @@ -102,7 +102,7 @@ public static PlayerControl PlayerById(byte id) /// The player who was killed. public static void RecordOnKill(PlayerControl killer, PlayerControl victim) { - if (PlayerKiller.ContainsKey(killer)) + if (PlayerKiller.ContainsKey(victim)) { PlayerKiller[victim] = killer; } From 9e0c5a0193571a35b8dcb61f3706bb47ab9482cb Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Tue, 17 Mar 2026 12:47:18 +0000 Subject: [PATCH 03/10] Temporarily removed custom stats --- NewMod/Patches/StatsPopupPatch.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NewMod/Patches/StatsPopupPatch.cs b/NewMod/Patches/StatsPopupPatch.cs index eaee3e1..8fead5e 100644 --- a/NewMod/Patches/StatsPopupPatch.cs +++ b/NewMod/Patches/StatsPopupPatch.cs @@ -1,4 +1,4 @@ -using AmongUs.GameOptions; +/*using AmongUs.GameOptions; using UnityEngine; using HarmonyLib; using MiraAPI.Roles; @@ -173,4 +173,4 @@ public static bool Prefix(StatsPopup __instance) return false; } } -} \ No newline at end of file +}*/ \ No newline at end of file From 1d21ae4e04aa2d3423e7e862a0831b2c91c5ab3c Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Thu, 19 Mar 2026 17:20:03 +0000 Subject: [PATCH 04/10] Revive button fixes, end game fixes --- NewMod/Buttons/Necromancer/ReviveButton.cs | 53 +++++++---- NewMod/NewModEventHandler.cs | 3 + NewMod/Patches/EndGamePatch.cs | 87 ++++++++++++++----- NewMod/Patches/RolePatch.cs | 5 +- NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs | 1 + NewMod/Roles/ImpostorRoles/Necromancer.cs | 18 +++- NewMod/Roles/ImpostorRoles/PulseBlade.cs | 19 +++- NewMod/Roles/ImpostorRoles/Revenant.cs | 16 ++++ NewMod/Roles/NeutralRoles/Tyrant.cs | 19 +++- NewMod/Utilities/CoroutinesHelper.cs | 12 --- NewMod/Utilities/Utils.cs | 4 + 11 files changed, 178 insertions(+), 59 deletions(-) diff --git a/NewMod/Buttons/Necromancer/ReviveButton.cs b/NewMod/Buttons/Necromancer/ReviveButton.cs index 9b92878..0e83c53 100644 --- a/NewMod/Buttons/Necromancer/ReviveButton.cs +++ b/NewMod/Buttons/Necromancer/ReviveButton.cs @@ -8,6 +8,8 @@ using MiraAPI.Keybinds; using AmongUs.GameOptions; using Reactor.Utilities; +using MiraAPI.Utilities; +using System.Linq; namespace NewMod.Buttons.Necromancer { @@ -56,24 +58,26 @@ public class ReviveButton : CustomActionButton /// protected override void OnClick() { - var closestBody = Utils.GetClosestBody(); - var killedPlayer = GameData.Instance.GetPlayerById(closestBody.ParentId)?.Object; + var local = PlayerControl.LocalPlayer; - var killer = Utils.GetKiller(killedPlayer); - if (killer != null && killer.PlayerId == PlayerControl.LocalPlayer.PlayerId) - { - Coroutines.Start(CoroutinesHelper.CoNotify("Heads up: you tried to revive someone you killed, one use has been consumed")); - return; - } + var body = Helpers.GetNearestDeadBodies( + local.GetTruePosition(), + ShipStatus.Instance.MaxLightRadius, + Helpers.CreateFilter(Constants.NotShipMask)) + .Where(b => b != null && !PranksterUtilities.IsPranksterBody(b)) + .OrderBy(b => Vector2.Distance(local.GetTruePosition(), b.TruePosition)) + .FirstOrDefault(); + + if (body == null) return; SoundManager.Instance.PlaySound(NewModAsset.ReviveSound?.LoadAsset(), false, 2f); Utils.HandleRevive( - PlayerControl.LocalPlayer, - closestBody.ParentId, + local, + body.ParentId, RoleTypes.Impostor, - closestBody.transform.position.x, - closestBody.transform.position.y + body.transform.position.x, + body.transform.position.y ); } /// @@ -92,15 +96,28 @@ public override bool Enabled(RoleBehaviour role) /// True if all requirements to use this button are met; otherwise false. public override bool CanUse() { - if (Timer > 0) return false; - if (UsesLeft <= 0) return false; + var bodiesInRange = Helpers.GetNearestDeadBodies( + PlayerControl.LocalPlayer.transform.position, + ShipStatus.Instance.MaxLightRadius, + Helpers.CreateFilter(Constants.NotShipMask)); + + bool canUse = bodiesInRange.Any(body => + { + if (PranksterUtilities.IsPranksterBody(body)) return false; + + var killedPlayer = GameData.Instance.GetPlayerById(body.ParentId)?.Object; + if (killedPlayer == null) return false; + + var killer = Utils.GetKiller(killedPlayer); + if (killer.PlayerId == PlayerControl.LocalPlayer.PlayerId) + return false; - var closestBody = Utils.GetClosestBody(); - if (closestBody == null) return false; + return true; + }); - if (PranksterUtilities.IsPranksterBody(closestBody)) return false; + NewMod.Instance.Log.LogMessage($"[ReviveButton] CanUse: {canUse}"); - return true; + return canUse; } } } diff --git a/NewMod/NewModEventHandler.cs b/NewMod/NewModEventHandler.cs index 93f1b4d..01add61 100644 --- a/NewMod/NewModEventHandler.cs +++ b/NewMod/NewModEventHandler.cs @@ -4,6 +4,7 @@ using System.Reflection; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; +using NewMod.Utilities; namespace NewMod { @@ -63,6 +64,8 @@ public static void OnRoundStart(RoundStartEvent evt) if (!evt.TriggeredByIntro) return; HudManager.Instance.Chat.enabled = false; + + Utils.ResetKillTracking(); } } } diff --git a/NewMod/Patches/EndGamePatch.cs b/NewMod/Patches/EndGamePatch.cs index c42a5f4..35e4e2a 100644 --- a/NewMod/Patches/EndGamePatch.cs +++ b/NewMod/Patches/EndGamePatch.cs @@ -25,6 +25,15 @@ namespace NewMod.Patches { public static class EndGamePatch { + public static bool EndGameTriggered = false; + + [RegisterEvent] + public static void OnGameStart(RoundStartEvent evt) + { + EndGameTriggered = false; + EndGameResult.CachedWinners.Clear(); + } + [RegisterEvent] public static void OnGameEnd(GameEndEvent evt) { @@ -68,6 +77,7 @@ public static void OnGameEnd(GameEndEvent evt) { poolablePlayer.SetFlipX(i % 2 == 0); } + poolablePlayer.UpdateFromPlayerOutfit( playerData.Outfit, PlayerMaterial.MaskType.None, @@ -151,6 +161,7 @@ public static void OnGameEnd(GameEndEvent evt) endGameManager.WinText.transform.position.y - 0.5f, endGameManager.WinText.transform.position.z); customWinTextObject.transform.localScale = new Vector3(0.7f, 0.7f, 1f); + var customWinTextComponent = customWinTextObject.GetComponent(); customWinTextComponent.text = customWinText; customWinTextComponent.color = customWinColor; @@ -173,6 +184,7 @@ public static string GetRoleName(CachedPlayerData playerData, out Color roleColo { return $"{newmodRole.RoleName}\n{Utils.GetFactionDisplay((INewModRole)customRole)}"; } + return customRole.RoleName; } else @@ -214,6 +226,20 @@ private static Color GetRoleColor(RoleTypes roleType) return Color.white; } } + + public static bool EndCustomGame(GameOverReason reason, Action winners = null) + { + if (EndGameTriggered) return true; + if (EndGameResult.CachedWinners.Count > 0) return true; + + EndGameTriggered = true; + + EndGameResult.CachedWinners.Clear(); + winners?.Invoke(); + + GameManager.Instance.RpcEndGame(reason, false); + return true; + } } [HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlowNormal.CheckEndCriteria))] @@ -222,7 +248,10 @@ public static class CheckGameEndPatch public static bool Prefix(ShipStatus __instance) { if (DestroyableSingleton.InstanceExists) return true; + if (!AmongUsClient.Instance.AmHost) return true; if (Time.timeSinceLevelLoad < 2f) return true; + if (EndGamePatch.EndGameTriggered) return false; + if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.WraithCallerWin)) return false; if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.ShadeWin)) return false; if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.PulseBladeWin)) return false; @@ -231,18 +260,22 @@ public static bool Prefix(ShipStatus __instance) if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.SpecialAgentWin)) return false; if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.PranksterWin, 3)) return false; if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.EnergyThiefWin)) return false; + if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.InjectorWin)) return false; + return true; } + public static bool CheckForEndGameFaction(ShipStatus __instance, GameOverReason winReason, int maxCount = 1) where TFaction : INewModRole { var players = PlayerControl.AllPlayerControls.ToArray() - .Where(p => p.Data.Role is TFaction) - .Take(maxCount) - .ToList(); + .Where(p => p.Data.Role is TFaction) + .Take(maxCount) + .ToList(); foreach (var player in players) { bool shouldEndGame = false; + Action extraWinners = null; if (typeof(TFaction) == typeof(PulseBlade)) { @@ -260,51 +293,58 @@ public static bool CheckForEndGameFaction(ShipStatus __instance, GameO shouldEndGame = true; } } + if (typeof(TFaction) == typeof(Tyrant)) { if (Tyrant.ApexThroneReady && Tyrant.ApexThroneOutcomeSet) { shouldEndGame = true; - var tyrantRole = player.Data.Role as Tyrant; - byte champId = tyrantRole.GetChampion(); - var champion = Utils.PlayerById(champId); + extraWinners = () => + { + var tyrantRole = player.Data.Role as Tyrant; + byte champId = tyrantRole.GetChampion(); + var champion = Utils.PlayerById(champId); - bool championWin = Tyrant.Outcome == Tyrant.ThroneOutcome.ChampionSideWin; + bool championWin = Tyrant.Outcome == Tyrant.ThroneOutcome.ChampionSideWin; - if (champion && championWin) - { - EndGameResult.CachedWinners.Add(new(champion.Data)); - } + if (champion && championWin) + { + EndGameResult.CachedWinners.Add(new(champion.Data)); + } + }; } } + if (typeof(TFaction) == typeof(WraithCaller)) { int required = (int)OptionGroupSingleton.Instance.RequiredNPCsToSend; int current = WraithCallerUtilities.GetKillsNPC(player.PlayerId); shouldEndGame = current >= required; } + if (typeof(TFaction) == typeof(Shade)) { Shade.ShadeKills.TryGetValue(player.PlayerId, out var count); int required = (int)OptionGroupSingleton.Instance.RequiredKills; shouldEndGame = count >= required; } + if (shouldEndGame) { - GameManager.Instance.RpcEndGame(winReason, false); - CustomStatsManager.IncrementRoleWin((INewModRole)player.Data.Role); - return true; + return EndGamePatch.EndCustomGame(winReason, extraWinners); } } + return false; } + public static bool CheckEndGameForRole(ShipStatus __instance, GameOverReason winReason, int maxCount = 1) where T : RoleBehaviour { var rolePlayers = PlayerControl.AllPlayerControls.ToArray() - .Where(p => p.Data.Role is T) - .Take(maxCount) - .ToList(); + .Where(p => p.Data.Role is T) + .Take(maxCount) + .ToList(); foreach (var player in rolePlayers) { @@ -316,19 +356,21 @@ public static bool CheckEndGameForRole(ShipStatus __instance, GameOverReason bool isSabotageActive = Utils.IsSabotage(); shouldEndGame = tasksCompleted && isSabotageActive; } + if (typeof(T) == typeof(EnergyThief)) { int drainCount = Utils.GetDrainCount(player.PlayerId); int requiredDrainCount = (int)OptionGroupSingleton.Instance.RequiredDrainCount; - shouldEndGame = drainCount >= requiredDrainCount; } + if (typeof(T) == typeof(Prankster)) { int WinReportCount = 2; int currentReportCount = PranksterUtilities.GetReportCount(player.PlayerId); shouldEndGame = currentReportCount >= WinReportCount; } + if (typeof(T) == typeof(SpecialAgent)) { int missionSuccessCount = Utils.GetMissionSuccessCount(player.PlayerId); @@ -336,20 +378,21 @@ public static bool CheckEndGameForRole(ShipStatus __instance, GameOverReason int netScore = missionSuccessCount - missionFailureCount; shouldEndGame = netScore >= OptionGroupSingleton.Instance.RequiredMissionsToWin; } + if (typeof(T) == typeof(InjectorRole)) { int injectedCount = Utils.GetInjectedCount(); int required = (int)OptionGroupSingleton.Instance.RequiredInjectCount; shouldEndGame = injectedCount >= required; } + if (shouldEndGame) { - GameManager.Instance.RpcEndGame(winReason, false); - CustomStatsManager.IncrementRoleWin((ICustomRole)player.Data.Role); - return true; + return EndGamePatch.EndCustomGame(winReason); } } + return false; } } -} +} \ No newline at end of file diff --git a/NewMod/Patches/RolePatch.cs b/NewMod/Patches/RolePatch.cs index 94c010f..3730d0e 100644 --- a/NewMod/Patches/RolePatch.cs +++ b/NewMod/Patches/RolePatch.cs @@ -4,7 +4,6 @@ using System.Linq; using AmongUs.GameOptions; using HarmonyLib; -using Hazel; using MiraAPI.GameOptions; using MiraAPI.Roles; using MiraAPI.Utilities; @@ -31,7 +30,7 @@ public static void Postfix(RoleManager __instance) private static IEnumerator CoAdjustNeutrals() { yield return null; - yield return new WaitForSeconds(0.05f); + yield return new WaitForSeconds(0.06f); var opts = OptionGroupSingleton.Instance; int target = Mathf.RoundToInt(opts.TotalNeutrals); @@ -220,7 +219,7 @@ private static IEnumerator CoAdjustNeutrals() Logger.Instance.LogMessage("-------------- NEUTRAL ADJUST: END --------------"); } - public struct Candidate + public class Candidate { public ICustomRole Role; public int Left; diff --git a/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs b/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs index cdf22c2..df98d8c 100644 --- a/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs +++ b/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs @@ -15,6 +15,7 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] EndGam Utils.ResetMissionFailureCount(); Utils.ResetInjections(); Utils.ResetStrikeCount(); + Utils.ResetKillTracking(); PranksterUtilities.ResetReportCount(); VisionaryUtilities.DeleteAllScreenshots(); WraithCallerUtilities.ClearAll(); diff --git a/NewMod/Roles/ImpostorRoles/Necromancer.cs b/NewMod/Roles/ImpostorRoles/Necromancer.cs index 6ea738d..0a4aef7 100644 --- a/NewMod/Roles/ImpostorRoles/Necromancer.cs +++ b/NewMod/Roles/ImpostorRoles/Necromancer.cs @@ -24,6 +24,22 @@ public class NecromancerRole : ImpostorRole, ICustomRole public TeamIntroConfiguration TeamConfiguration => new() { IntroTeamDescription = RoleDescription, - IntroTeamColor = RoleColor + IntroTeamColor = RoleColor }; + public override bool DidWin(GameOverReason reason) + { + if (reason == (GameOverReason)NewModEndReasons.TyrantWin || + reason == (GameOverReason)NewModEndReasons.ShadeWin || + reason == (GameOverReason)NewModEndReasons.WraithCallerWin || + reason == (GameOverReason)NewModEndReasons.SpecialAgentWin || + reason == (GameOverReason)NewModEndReasons.PranksterWin || + reason == (GameOverReason)NewModEndReasons.EnergyThiefWin || + reason == (GameOverReason)NewModEndReasons.InjectorWin || + reason == (GameOverReason)NewModEndReasons.DoubleAgentWin) + { + return false; + } + + return base.DidWin(reason); + } } diff --git a/NewMod/Roles/ImpostorRoles/PulseBlade.cs b/NewMod/Roles/ImpostorRoles/PulseBlade.cs index a7c0f07..f4d3628 100644 --- a/NewMod/Roles/ImpostorRoles/PulseBlade.cs +++ b/NewMod/Roles/ImpostorRoles/PulseBlade.cs @@ -61,9 +61,24 @@ public StringBuilder SetTabText() return tabText; } - public override bool DidWin(GameOverReason gameOverReason) + public override bool DidWin(GameOverReason reason) { - return gameOverReason == (GameOverReason)NewModEndReasons.PulseBladeWin; + if (reason == (GameOverReason)NewModEndReasons.PulseBladeWin) + return true; + + if (reason == (GameOverReason)NewModEndReasons.TyrantWin || + reason == (GameOverReason)NewModEndReasons.ShadeWin || + reason == (GameOverReason)NewModEndReasons.WraithCallerWin || + reason == (GameOverReason)NewModEndReasons.SpecialAgentWin || + reason == (GameOverReason)NewModEndReasons.PranksterWin || + reason == (GameOverReason)NewModEndReasons.EnergyThiefWin || + reason == (GameOverReason)NewModEndReasons.InjectorWin || + reason == (GameOverReason)NewModEndReasons.DoubleAgentWin) + { + return false; + } + + return base.DidWin(reason); } } } \ No newline at end of file diff --git a/NewMod/Roles/ImpostorRoles/Revenant.cs b/NewMod/Roles/ImpostorRoles/Revenant.cs index e71f56e..eba7774 100644 --- a/NewMod/Roles/ImpostorRoles/Revenant.cs +++ b/NewMod/Roles/ImpostorRoles/Revenant.cs @@ -53,4 +53,20 @@ public static void OnPlayerExit(PlayerLeaveEvent evt) { ResetAllStates(); } + public override bool DidWin(GameOverReason reason) + { + if (reason == (GameOverReason)NewModEndReasons.TyrantWin || + reason == (GameOverReason)NewModEndReasons.ShadeWin || + reason == (GameOverReason)NewModEndReasons.WraithCallerWin || + reason == (GameOverReason)NewModEndReasons.SpecialAgentWin || + reason == (GameOverReason)NewModEndReasons.PranksterWin || + reason == (GameOverReason)NewModEndReasons.EnergyThiefWin || + reason == (GameOverReason)NewModEndReasons.InjectorWin || + reason == (GameOverReason)NewModEndReasons.DoubleAgentWin) + { + return false; + } + + return base.DidWin(reason); + } } diff --git a/NewMod/Roles/NeutralRoles/Tyrant.cs b/NewMod/Roles/NeutralRoles/Tyrant.cs index 82eea6b..00d6dcc 100644 --- a/NewMod/Roles/NeutralRoles/Tyrant.cs +++ b/NewMod/Roles/NeutralRoles/Tyrant.cs @@ -94,6 +94,23 @@ void AppendAbilityLine(int index, string text) return tabText; } + public override bool DidWin(GameOverReason reason) + { + if (reason == (GameOverReason)NewModEndReasons.TyrantWin) return true; + + if (reason == (GameOverReason)NewModEndReasons.ShadeWin || + reason == (GameOverReason)NewModEndReasons.WraithCallerWin || + reason == (GameOverReason)NewModEndReasons.SpecialAgentWin || + reason == (GameOverReason)NewModEndReasons.PranksterWin || + reason == (GameOverReason)NewModEndReasons.EnergyThiefWin || + reason == (GameOverReason)NewModEndReasons.InjectorWin || + reason == (GameOverReason)NewModEndReasons.DoubleAgentWin) + { + return false; + } + + return base.DidWin(reason); + } public int _kills; public static byte _championId; public static bool ApexThroneReady; @@ -111,7 +128,7 @@ public static void OnAfterMurderEvent(AfterMurderEvent evt) { if (!Utils.IsRoleActive("Tyrant")) return; - var tyrant = evt.Source.Data.Role as Tyrant; + if (evt.Source.Data.Role is not Tyrant tyrant) return; tyrant._kills++; diff --git a/NewMod/Utilities/CoroutinesHelper.cs b/NewMod/Utilities/CoroutinesHelper.cs index d956e8c..b7433c3 100644 --- a/NewMod/Utilities/CoroutinesHelper.cs +++ b/NewMod/Utilities/CoroutinesHelper.cs @@ -457,17 +457,5 @@ public static IEnumerator RemoveCameraEffect(Camera cam, float duration) if (cam.TryGetComponent(out var sf)) Object.Destroy(sf); } - public static IEnumerator CoRevive(PlayerControl revived, RoleTypes role) - { - yield return new WaitForSeconds(0.15f); - - var body = GameObject.FindObjectsOfType().FirstOrDefault(b => b.ParentId == revived.PlayerId); - - if (body != null) - Object.Destroy(body.gameObject); - - revived.Revive(); - revived.RpcSetRole(role); - } } } diff --git a/NewMod/Utilities/Utils.cs b/NewMod/Utilities/Utils.cs index c89208d..3cec4ab 100644 --- a/NewMod/Utilities/Utils.cs +++ b/NewMod/Utilities/Utils.cs @@ -111,6 +111,10 @@ public static void RecordOnKill(PlayerControl killer, PlayerControl victim) PlayerKiller.Add(victim, killer); } } + public static void ResetKillTracking() + { + PlayerKiller.Clear(); + } /// /// Retrieves the killer of the specified victim. From 99bd3a986869d10b3c2bd290511b250a31e0005a Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Thu, 19 Mar 2026 18:15:12 +0000 Subject: [PATCH 05/10] Use custom kill button instead of vanilla for revived player --- NewMod/Buttons/Necromancer/ReviveButton.cs | 5 +- NewMod/Buttons/RevivedKillButton.cs | 63 ++++++++++++++++++ NewMod/NewModAsset.cs | 1 + NewMod/NewModEventHandler.cs | 3 +- NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs | 1 + NewMod/Resources/killbutton.png | Bin 0 -> 32313 bytes NewMod/Roles/ImpostorRoles/Necromancer.cs | 2 + 7 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 NewMod/Buttons/RevivedKillButton.cs create mode 100644 NewMod/Resources/killbutton.png diff --git a/NewMod/Buttons/Necromancer/ReviveButton.cs b/NewMod/Buttons/Necromancer/ReviveButton.cs index 0e83c53..d35f4da 100644 --- a/NewMod/Buttons/Necromancer/ReviveButton.cs +++ b/NewMod/Buttons/Necromancer/ReviveButton.cs @@ -75,10 +75,11 @@ protected override void OnClick() Utils.HandleRevive( local, body.ParentId, - RoleTypes.Impostor, + RoleTypes.Crewmate, body.transform.position.x, body.transform.position.y ); + NecromancerRole.RevivedPlayers[body.ParentId] = local.PlayerId; } /// /// Determines whether this button is enabled for the role, returning true if the role is . @@ -115,8 +116,6 @@ public override bool CanUse() return true; }); - NewMod.Instance.Log.LogMessage($"[ReviveButton] CanUse: {canUse}"); - return canUse; } } diff --git a/NewMod/Buttons/RevivedKillButton.cs b/NewMod/Buttons/RevivedKillButton.cs new file mode 100644 index 0000000..b32a61d --- /dev/null +++ b/NewMod/Buttons/RevivedKillButton.cs @@ -0,0 +1,63 @@ +using MiraAPI.Hud; +using MiraAPI.Keybinds; +using MiraAPI.Utilities.Assets; +using NewMod.Roles.ImpostorRoles; +using Reactor.Utilities; +using AmongUs.GameOptions; +using System.Linq; +using UnityEngine; +using MiraAPI.Networking; +using MiraAPI.Utilities; + +namespace NewMod.Buttons +{ + public class RevivedKillButton : CustomActionButton + { + public override string Name => "KILL"; + public override float Cooldown => GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown); + public override int MaxUses => 0; + public override float EffectDuration => 0f; + public override MiraKeybind Keybind => MiraGlobalKeybinds.PrimaryAbility; + public override ButtonLocation Location => ButtonLocation.BottomRight; + public override LoadableAsset Sprite => NewModAsset.VanillaKillButton; + public override bool Enabled(RoleBehaviour role) + { + return NecromancerRole.RevivedPlayers.ContainsKey(PlayerControl.LocalPlayer.PlayerId); + } + public override PlayerControl GetTarget() + { + return PlayerControl.LocalPlayer.GetClosestPlayer(true, Distance); + } + public override bool IsTargetValid(PlayerControl target) + { + return target.PlayerId != PlayerControl.LocalPlayer.PlayerId; + } + public override void SetOutline(bool active) + { + Target.cosmetics.SetOutline(active, new Il2CppSystem.Nullable(Palette.ImpostorRed)); + } + + public override bool CanUse() + { + if (!NecromancerRole.RevivedPlayers.ContainsKey(PlayerControl.LocalPlayer.PlayerId)) return false; + return true; + } + + protected override void OnClick() + { + var local = PlayerControl.LocalPlayer; + + local.RpcCustomMurder( + Target, + didSucceed: true, + resetKillTimer: true, + createDeadBody: true, + teleportMurderer: true, + showKillAnim: true, + playKillSound: true + ); + + NecromancerRole.RevivedPlayers.Remove(local.PlayerId); + } + } +} \ No newline at end of file diff --git a/NewMod/NewModAsset.cs b/NewMod/NewModAsset.cs index 0e92036..2105dcb 100644 --- a/NewMod/NewModAsset.cs +++ b/NewMod/NewModAsset.cs @@ -42,6 +42,7 @@ public static class NewModAsset public static LoadableResourceAsset Shield { get; } = new("NewMod.Resources.Shield.png"); public static LoadableResourceAsset Slash { get; } = new("NewMod.Resources.Slash.png"); public static LoadableResourceAsset DeployZone { get; } = new("NewMod.Resources.deployzone.png"); + public static LoadableResourceAsset VanillaKillButton { get; } = new("NewMod.Resources.killbutton.png"); // SFX public static LoadableAudioResourceAsset ReviveSound { get; } = new("NewMod.Resources.Sounds.revive.wav"); diff --git a/NewMod/NewModEventHandler.cs b/NewMod/NewModEventHandler.cs index 01add61..4ea18f6 100644 --- a/NewMod/NewModEventHandler.cs +++ b/NewMod/NewModEventHandler.cs @@ -4,6 +4,7 @@ using System.Reflection; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; +using NewMod.Roles.ImpostorRoles; using NewMod.Utilities; namespace NewMod @@ -57,7 +58,6 @@ public static void RegisterEventsLogs() NewMod.Instance.Log.LogInfo(sb.ToString()); } - // General events [RegisterEvent] public static void OnRoundStart(RoundStartEvent evt) { @@ -66,6 +66,7 @@ public static void OnRoundStart(RoundStartEvent evt) HudManager.Instance.Chat.enabled = false; Utils.ResetKillTracking(); + NecromancerRole.RevivedPlayers.Clear(); } } } diff --git a/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs b/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs index df98d8c..f8d71d2 100644 --- a/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs +++ b/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs @@ -21,6 +21,7 @@ public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] EndGam WraithCallerUtilities.ClearAll(); Shade.ShadeKills.Clear(); Revenant.ResetAllStates(); + NecromancerRole.RevivedPlayers.Clear(); NewMod.Instance.Log.LogInfo("Reset Drain Count Successfully"); NewMod.Instance.Log.LogInfo("Reset Clone Report Count Successfully"); NewMod.Instance.Log.LogInfo("Reset Mission Success Count Successfully"); diff --git a/NewMod/Resources/killbutton.png b/NewMod/Resources/killbutton.png new file mode 100644 index 0000000000000000000000000000000000000000..d137058bab8a246f0b5ce8be816bfc3838fd8703 GIT binary patch literal 32313 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 literal 0 HcmV?d00001 diff --git a/NewMod/Roles/ImpostorRoles/Necromancer.cs b/NewMod/Roles/ImpostorRoles/Necromancer.cs index 0a4aef7..921fe04 100644 --- a/NewMod/Roles/ImpostorRoles/Necromancer.cs +++ b/NewMod/Roles/ImpostorRoles/Necromancer.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using MiraAPI.GameOptions; using MiraAPI.Roles; using MiraAPI.Utilities; @@ -9,6 +10,7 @@ namespace NewMod.Roles.ImpostorRoles; public class NecromancerRole : ImpostorRole, ICustomRole { + public static Dictionary RevivedPlayers = new(); public string RoleName => "Necromancer"; public string RoleDescription => "You can revive dead players who weren't killed by you"; public string RoleLongDescription => "As the Necromancer, you possess a unique and powerful ability: the power to bring one dead player back to life. However,\nyou can only revive someone who wasn't killed by you"; From 3767cd68a6055023899d0dbecbf8327f190cc07e Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Fri, 20 Mar 2026 12:55:57 +0000 Subject: [PATCH 06/10] Update version to Hotfix 2 --- NewMod/NewMod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewMod/NewMod.cs b/NewMod/NewMod.cs index 4822c30..588367c 100644 --- a/NewMod/NewMod.cs +++ b/NewMod/NewMod.cs @@ -47,7 +47,7 @@ public override void Load() { Instance = this; AddComponent(); - ReactorCredits.Register("NewMod", "v1.2.9 Hotfix 1", true, ReactorCredits.AlwaysShow); + ReactorCredits.Register("NewMod", "v1.2.9 Hotfix 2", true, ReactorCredits.AlwaysShow); Harmony.PatchAll(); NewModEventHandler.RegisterEventsLogs(); From 403f286a48b722bd4872d44ac31b5a2d89590ad0 Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Fri, 20 Mar 2026 13:40:58 +0000 Subject: [PATCH 07/10] =?UTF-8?q?Fixed=20issue=20where=20player=20speed=20?= =?UTF-8?q?didn=E2=80=99t=20restore=20after=20exiting=20Fear=20Pulse=20Are?= =?UTF-8?q?a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NewMod/Components/FearPulseArea.cs | 81 ++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/NewMod/Components/FearPulseArea.cs b/NewMod/Components/FearPulseArea.cs index 989b31f..bdcd8a5 100644 --- a/NewMod/Components/FearPulseArea.cs +++ b/NewMod/Components/FearPulseArea.cs @@ -13,16 +13,25 @@ namespace NewMod.Components public class FearPulseArea(IntPtr ptr) : MonoBehaviour(ptr) { public byte ownerId; - float _radius, _duration, _speedMul, _t; + + float _radius; + float _duration; + float _speedMul; + float _t; + readonly Dictionary _origSpeed = new(); readonly HashSet _insideNow = new(); + public static readonly HashSet AffectedPlayers = new(); public static readonly HashSet _speedNotifShown = new(); public static readonly HashSet _visionNotifShown = new(); + public AudioClip _enterClip; public AudioClip _heartbeatClip; public bool _pulsingHb; + bool _restored; + public void Init(byte ownerId, float radius, float duration, float speedMul) { this.ownerId = ownerId; @@ -35,6 +44,8 @@ public void Init(byte ownerId, float radius, float duration, float speedMul) public void Update() { + if (_restored) return; + _t += Time.deltaTime; if (_t > _duration) { @@ -89,12 +100,13 @@ public void Update() { _visionNotifShown.Add(p.PlayerId); var notif = Helpers.CreateAndShowNotification( - "You have entered the Fear Pulse Area. Your vision is reduced!", - new Color(1f, 0.8f, 0.2f), - spr: NewModAsset.VisionDebuff.LoadAsset() - ); + "You have entered the Fear Pulse Area. Your vision is reduced!", + new Color(1f, 0.8f, 0.2f), + spr: NewModAsset.VisionDebuff.LoadAsset() + ); notif.Text.SetOutlineThickness(0.36f); } + Coroutines.Start(Utils.CoShakeCamera(Camera.main.GetComponent(), 0.5f)); } @@ -112,40 +124,57 @@ public void Update() var toRestore = _origSpeed.Keys.Where(id => !_insideNow.Contains(id)).ToList(); foreach (var id in toRestore) { - var p = Utils.PlayerById(id); - if (p) p.MyPhysics.Speed = _origSpeed[id]; - _origSpeed.Remove(id); + RestorePlayer(id); + } + } + } + + public void RestorePlayer(byte playerId) + { + if (_origSpeed.TryGetValue(playerId, out var originalSpeed)) + { + var p = Utils.PlayerById(playerId); + + if (!p.Data.IsDead || !p.Data.Disconnected) + { + p.MyPhysics.Speed = originalSpeed; if (p.AmOwner) { - AffectedPlayers.Remove(p.PlayerId); p.lightSource.lightChild.SetActive(true); - _speedNotifShown.Remove(p.PlayerId); - _visionNotifShown.Remove(p.PlayerId); - Helpers.CreateAndShowNotification("Your vision is restored.", new Color(0.8f, 1f, 0.8f)); + + Helpers.CreateAndShowNotification( + "Your speed and vision are restored.", + new Color(0.8f, 1f, 0.8f) + ); } } + + _origSpeed.Remove(playerId); } + + AffectedPlayers.Remove(playerId); + _speedNotifShown.Remove(playerId); + _visionNotifShown.Remove(playerId); } + public void RestoreAll() { - foreach (var kv in _origSpeed) + if (_restored) return; + _restored = true; + + var ids = _origSpeed.Keys.ToList(); + foreach (var id in ids) { - var p = Utils.PlayerById(kv.Key); - if (p) p.MyPhysics.Speed = kv.Value; + RestorePlayer(id); } - _origSpeed.Clear(); - AffectedPlayers.Clear(); - - var lp = PlayerControl.LocalPlayer; - - if (AffectedPlayers.Contains(lp.PlayerId)) - AffectedPlayers.Remove(lp.PlayerId); - lp.lightSource.lightChild.SetActive(true); + _insideNow.Clear(); + } - _speedNotifShown.Remove(lp.PlayerId); - _visionNotifShown.Remove(lp.PlayerId); + public void OnDestroy() + { + RestoreAll(); } } -} +} \ No newline at end of file From 6dc7832b513741c85fcc692733e73b48b2ff7b42 Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Fri, 20 Mar 2026 13:43:59 +0000 Subject: [PATCH 08/10] cleaning --- NewMod/NewModDateTime.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/NewMod/NewModDateTime.cs b/NewMod/NewModDateTime.cs index 049d833..6adbaf0 100644 --- a/NewMod/NewModDateTime.cs +++ b/NewMod/NewModDateTime.cs @@ -40,7 +40,6 @@ public static bool IsNewModBirthdayWeek return now >= start && now < end; } } - public static bool IsWraithCallerUnlocked => DateTime.Now >= BirthdayStartThisYear; private const int HalloweenMonth = 10; private const int HalloweenDay = 31; public static readonly TimeSpan HalloweenWindow = TimeSpan.FromDays(7); From 054857af1b6083dfa44876082e40635096c9296f Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Fri, 20 Mar 2026 13:49:50 +0000 Subject: [PATCH 09/10] Fixed spam logs --- NewMod/NewMod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NewMod/NewMod.cs b/NewMod/NewMod.cs index 588367c..b43aa33 100644 --- a/NewMod/NewMod.cs +++ b/NewMod/NewMod.cs @@ -170,7 +170,7 @@ public static class SetTaskTextPatch { public static void Postfix(TaskPanelBehaviour __instance, [HarmonyArgument(0)] string str) { - if (PlayerControl.LocalPlayer.Data.IsDead) + if (__instance.taskText != null && PlayerControl.LocalPlayer.Data.IsDead) { __instance.taskText.text += "\n" + (OptionGroupSingleton.Instance.AllowCams ? "Press F2 For Open Cams" : "You cannot open cams because the host has disabled this setting"); } From 68e4e44f51dad87850d67d00c817a10efcaff62a Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Fri, 20 Mar 2026 13:50:56 +0000 Subject: [PATCH 10/10] Fix --- NewMod/Patches/MainMenuPatch.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/NewMod/Patches/MainMenuPatch.cs b/NewMod/Patches/MainMenuPatch.cs index 0e62cb4..acd3793 100644 --- a/NewMod/Patches/MainMenuPatch.cs +++ b/NewMod/Patches/MainMenuPatch.cs @@ -22,7 +22,6 @@ public static class MainMenuPatch public static SpriteRenderer LogoSprite; public static Texture2D _cachedCursor; public static Transform RightPanel; - public static bool _wraithRegistered = false; [HarmonyPatch(nameof(MainMenuManager.Start))] [HarmonyPostfix] @@ -40,11 +39,6 @@ public static void StartPostfix(MainMenuManager __instance) RightPanel = __instance.transform.Find("MainUI/AspectScaler/RightPanel"); - if (NewModDateTime.IsWraithCallerUnlocked && !_wraithRegistered) - { - _wraithRegistered = true; - } - if (NewModDateTime.IsNewModBirthdayWeek) { Coroutines.Start(ApplyBirthdayUI(__instance));