From 3927546baa19196d18b963e55cdc87364c15771b Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Sun, 22 Mar 2026 20:24:19 +0100 Subject: [PATCH 01/14] cleaning --- NewMod/Buttons/RevivedKillButton.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/NewMod/Buttons/RevivedKillButton.cs b/NewMod/Buttons/RevivedKillButton.cs index b32a61d..27ffd6d 100644 --- a/NewMod/Buttons/RevivedKillButton.cs +++ b/NewMod/Buttons/RevivedKillButton.cs @@ -4,7 +4,6 @@ using NewMod.Roles.ImpostorRoles; using Reactor.Utilities; using AmongUs.GameOptions; -using System.Linq; using UnityEngine; using MiraAPI.Networking; using MiraAPI.Utilities; @@ -20,18 +19,17 @@ public class RevivedKillButton : CustomActionButton 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)); @@ -39,7 +37,6 @@ public override void SetOutline(bool active) public override bool CanUse() { - if (!NecromancerRole.RevivedPlayers.ContainsKey(PlayerControl.LocalPlayer.PlayerId)) return false; return true; } @@ -60,4 +57,4 @@ protected override void OnClick() NecromancerRole.RevivedPlayers.Remove(local.PlayerId); } } -} \ No newline at end of file +} From b79692ac8aaa12328566b64ea5b4cd35e2988bc3 Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Fri, 1 May 2026 18:06:10 +0000 Subject: [PATCH 02/14] more cleaning --- NewMod/Patches/MainMenuPatch.cs | 34 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/NewMod/Patches/MainMenuPatch.cs b/NewMod/Patches/MainMenuPatch.cs index acd3793..9a6e994 100644 --- a/NewMod/Patches/MainMenuPatch.cs +++ b/NewMod/Patches/MainMenuPatch.cs @@ -1,14 +1,5 @@ using UnityEngine; using HarmonyLib; -using NewMod.Roles.NeutralRoles; -using MiraAPI; -using MiraAPI.PluginLoading; -using Reactor.Utilities; -using MiraAPI.Roles; -using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using System; using NewMod.LocalSettings; using MiraAPI.LocalSettings; @@ -16,7 +7,6 @@ namespace NewMod.Patches { [HarmonyPatch(typeof(MainMenuManager))] [HarmonyPriority(Priority.VeryHigh)] - public static class MainMenuPatch { public static SpriteRenderer LogoSprite; @@ -39,22 +29,21 @@ public static void StartPostfix(MainMenuManager __instance) RightPanel = __instance.transform.Find("MainUI/AspectScaler/RightPanel"); - if (NewModDateTime.IsNewModBirthdayWeek) + /*if (NewModDateTime.IsNewModBirthdayWeek) { Coroutines.Start(ApplyBirthdayUI(__instance)); - } - else - { - var Logo = new GameObject("NewModLogo"); - Logo.transform.SetParent(__instance.transform.Find("MainCanvas/MainPanel/RightPanel"), false); - Logo.transform.localPosition = new Vector3(2.34f, -0.7136f, 1f); - LogoSprite = Logo.AddComponent(); - LogoSprite.sprite = NewModAsset.ModLogo.LoadAsset(); - } + }*/ + + var Logo = new GameObject("NewModLogo"); + Logo.transform.SetParent(__instance.transform.Find("MainCanvas/MainPanel/RightPanel"), false); + Logo.transform.localPosition = new Vector3(2.34f, -0.7136f, 1f); + LogoSprite = Logo.AddComponent(); + LogoSprite.sprite = NewModAsset.ModLogo.LoadAsset(); + ModCompatibility.Initialize(); } - private static IEnumerator ApplyBirthdayUI(MainMenuManager __instance) + /*private static IEnumerator ApplyBirthdayUI(MainMenuManager __instance) { yield return null; @@ -87,7 +76,8 @@ private static IEnumerator ApplyBirthdayUI(MainMenuManager __instance) var bg = NewModAsset.MainMenuBG.LoadAsset(); if (auBG != null && bg != null) auBG.sprite = bg; } - } + }*/ + /*[HarmonyPatch(nameof(MainMenuManager.OpenGameModeMenu))] [HarmonyPatch(nameof(MainMenuManager.OpenCredits))] [HarmonyPatch(nameof(MainMenuManager.OpenAccountMenu))] From ad1a3158d3581422b37d8e66c55ef2b146b3b74f Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Mon, 4 May 2026 09:01:19 +0000 Subject: [PATCH 03/14] =?UTF-8?q?Fixed=20Wraith=20Caller=20disconnection?= =?UTF-8?q?=20issues,=20resolved=20the=20endgame=20crash,=20addressed=20an?= =?UTF-8?q?=20issue=20where=20some=20roles=E2=80=99=20DidWin=20wasn?= =?UTF-8?q?=E2=80=99t=20being=20called,=20and=20added=20the=20Feign=20Deat?= =?UTF-8?q?h=20sprite=20along=20with=20the=20Necromancer=20role=20icon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NewMod/Buttons/Necromancer/ReviveButton.cs | 3 +- NewMod/Buttons/Revenant/FeignDeathButton.cs | 2 +- NewMod/Buttons/RevivedKillButton.cs | 3 +- NewMod/Buttons/WraithCaller/CallWraith.cs | 2 +- NewMod/Components/WraithCallerNpc.cs | 164 +++++++++++++------- NewMod/NewMod.cs | 12 +- NewMod/NewModAsset.cs | 2 + NewMod/Resources/RoleIcons/ReviveIcon.png | Bin 0 -> 9067 bytes NewMod/Resources/feigndeath.png | Bin 0 -> 27574 bytes NewMod/Roles/ImpostorRoles/Necromancer.cs | 5 +- NewMod/Roles/ImpostorRoles/PulseBlade.cs | 2 +- NewMod/Roles/ImpostorRoles/Revenant.cs | 2 +- NewMod/Roles/NeutralRoles/Prankster.cs | 38 +++-- NewMod/Roles/NeutralRoles/Tyrant.cs | 6 +- NewMod/Roles/NeutralRoles/WraithCaller.cs | 4 + NewMod/Utilities/WraithCallerUtilities.cs | 79 +++------- 16 files changed, 165 insertions(+), 159 deletions(-) create mode 100644 NewMod/Resources/RoleIcons/ReviveIcon.png create mode 100644 NewMod/Resources/feigndeath.png diff --git a/NewMod/Buttons/Necromancer/ReviveButton.cs b/NewMod/Buttons/Necromancer/ReviveButton.cs index d35f4da..d90152d 100644 --- a/NewMod/Buttons/Necromancer/ReviveButton.cs +++ b/NewMod/Buttons/Necromancer/ReviveButton.cs @@ -7,7 +7,6 @@ using NewMod.Utilities; using MiraAPI.Keybinds; using AmongUs.GameOptions; -using Reactor.Utilities; using MiraAPI.Utilities; using System.Linq; @@ -98,7 +97,7 @@ public override bool Enabled(RoleBehaviour role) public override bool CanUse() { var bodiesInRange = Helpers.GetNearestDeadBodies( - PlayerControl.LocalPlayer.transform.position, + PlayerControl.LocalPlayer.GetTruePosition(), ShipStatus.Instance.MaxLightRadius, Helpers.CreateFilter(Constants.NotShipMask)); diff --git a/NewMod/Buttons/Revenant/FeignDeathButton.cs b/NewMod/Buttons/Revenant/FeignDeathButton.cs index f0f0c87..7bd6d32 100644 --- a/NewMod/Buttons/Revenant/FeignDeathButton.cs +++ b/NewMod/Buttons/Revenant/FeignDeathButton.cs @@ -48,7 +48,7 @@ public class FeignDeathButton : CustomActionButton /// /// The icon or sprite used for this button. Here, set to an empty sprite. /// - public override LoadableAsset Sprite => MiraAssets.Empty; + public override LoadableAsset Sprite => NewModAsset.FeignDeath; /// /// Specifies whether the button is enabled for the given role, ensuring Feign Death hasn't been used yet. diff --git a/NewMod/Buttons/RevivedKillButton.cs b/NewMod/Buttons/RevivedKillButton.cs index 27ffd6d..d906fce 100644 --- a/NewMod/Buttons/RevivedKillButton.cs +++ b/NewMod/Buttons/RevivedKillButton.cs @@ -2,7 +2,6 @@ using MiraAPI.Keybinds; using MiraAPI.Utilities.Assets; using NewMod.Roles.ImpostorRoles; -using Reactor.Utilities; using AmongUs.GameOptions; using UnityEngine; using MiraAPI.Networking; @@ -22,7 +21,7 @@ public class RevivedKillButton : CustomActionButton public override bool Enabled(RoleBehaviour role) { - return NecromancerRole.RevivedPlayers.ContainsKey(PlayerControl.LocalPlayer.PlayerId); + return NecromancerRole.RevivedPlayers.ContainsKey(PlayerControl.LocalPlayer.PlayerId); } public override PlayerControl GetTarget() diff --git a/NewMod/Buttons/WraithCaller/CallWraith.cs b/NewMod/Buttons/WraithCaller/CallWraith.cs index a3d8e75..fc0273e 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.RpcRequestSummonNPC(PlayerControl.LocalPlayer, player); + WraithCallerUtilities.RequestSummonNPC(PlayerControl.LocalPlayer, player); SetTimerPaused(false); }); diff --git a/NewMod/Components/WraithCallerNpc.cs b/NewMod/Components/WraithCallerNpc.cs index 9df95e7..364af12 100644 --- a/NewMod/Components/WraithCallerNpc.cs +++ b/NewMod/Components/WraithCallerNpc.cs @@ -1,8 +1,7 @@ using System; -using System.Linq; +using System.Collections; using Il2CppInterop.Runtime.Attributes; using MiraAPI.GameOptions; -using MiraAPI.Modifiers; using MiraAPI.Networking; using NewMod.Options.Roles.WraithCallerOptions; using NewMod.Utilities; @@ -17,124 +16,171 @@ public class WraithCallerNpc(IntPtr ptr) : MonoBehaviour(ptr) { public PlayerControl Owner { get; set; } public PlayerControl Target { get; set; } - public PlayerControl npc; + public PlayerControl Visual { get; set; } + public Rigidbody2D body; + public PlayerAnimations animations; + public LightSource ownerLight; 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, PlayerControl spawned) + public void Initialize(PlayerControl owner, PlayerControl target, Vector2 start) { - Owner = wraith; + Owner = owner; Target = target; - npc = spawned; - isActive = true; + Visual = Instantiate(AmongUsClient.Instance.PlayerPrefab); + Visual.transform.position = new Vector3(start.x, start.y, Owner.transform.position.z); - KillAnimation.SetMovement(npc, true); + Visual.notRealPlayer = true; + Visual.enabled = false; + Visual.NetTransform.enabled = false; + Visual.Collider.enabled = false; + Visual.MyPhysics.enabled = false; - npc.Collider.enabled = false; - npc.MyPhysics.Speed = OptionGroupSingleton.Instance.NPCSpeed; + PlayerControl.AllPlayerControls.Remove(Visual); - if (!npc.TryGetComponent(out _)) - npc.gameObject.AddComponent(); + body = Visual.MyPhysics.body; + animations = Visual.MyPhysics.Animations; - if (AmongUsClient.Instance.AmHost) - { - NewMod.Instance.Log.LogMessage($"Host is setting cosmetics for NPC (ID: {npc.PlayerId}"); - npc.NetTransform.RpcSnapTo(Owner.transform.position); + body.isKinematic = false; - var color = (byte)(npc.PlayerId % Palette.PlayerColors.Length); - npc.RpcSetName("Wraith NPC"); - npc.RpcSetColor(color); + Visual.cosmetics.enabled = true; + Visual.cosmetics.Visible = true; - var noShadow = npc.gameObject.AddComponent(); - if (noShadow != null) - { - noShadow.rend = npc.cosmetics.currentBodySprite.BodySprite; - noShadow.hitOverride = npc.Collider; - } - } + Visual.cosmetics.SetName("Wraith NPC"); + Visual.cosmetics.ToggleName(true); + Visual.cosmetics.ToggleHat(false); + Visual.cosmetics.TogglePet(false); + Visual.cosmetics.ToggleVisor(false); + + var color = Owner.PlayerId % Palette.PlayerColors.Length; + var bodySprite = Visual.cosmetics.currentBodySprite; + + bodySprite.Visible = true; + PlayerMaterial.SetColors(color, bodySprite.BodySprite); - Coroutines.Start(WalkToTarget()); + var noShadow = Visual.gameObject.AddComponent(); + noShadow.rend = bodySprite.BodySprite; + noShadow.hitOverride = Visual.Collider; - if (OptionGroupSingleton.Instance.ShouldSwitchCamToNPC) + isActive = true; + Coroutines.Start(CoMove()); + + if (Owner.AmOwner && OptionGroupSingleton.Instance.ShouldSwitchCamToNPC) { - Camera.main.GetComponent().SetTarget(npc); + Camera.main.GetComponent().SetTarget(Visual); ownerLight = Owner.lightSource; - ownerLight.transform.SetParent(npc.transform, false); - ownerLight.transform.localPosition = npc.Collider.offset; + ownerLight.transform.SetParent(Visual.transform, false); + ownerLight.transform.localPosition = Visual.Collider.offset; } - npc.cosmetics.enabled = true; - npc.enabled = false; - if (Target.AmOwner) SoundManager.Instance.PlaySound(NewModAsset.HeartbeatSound.LoadAsset(), false, 1f); } [HideFromIl2Cpp] - private System.Collections.IEnumerator WalkToTarget() + public IEnumerator CoMove() { - //yield return null; - - if (!AmongUsClient.Instance.AmHost) yield break; + var speed = OptionGroupSingleton.Instance.NPCSpeed; while (isActive && !MeetingHud.Instance) { - if (!Target || !Target.Data || Target.Data.IsDead) + if (Target.Data.IsDead || Target.Data.Disconnected) break; - Vector2 npcPos = npc.GetTruePosition(); - Vector2 targetPos = Target.GetTruePosition(); - Vector2 dir = (targetPos - npcPos).normalized; + var npcPos = (Vector2)Visual.transform.position; + var targetPos = Target.GetTruePosition(); + var delta = targetPos - npcPos; + + var slowDown = Mathf.Clamp(delta.magnitude * 2f, 0.05f, 1f); + var velocity = delta.normalized * speed * slowDown; - npc.MyPhysics.SetNormalizedVelocity(dir); + body.velocity = velocity; - if (Vector2.Distance(npcPos, targetPos) <= 0.1f) + UpdateWalkAnimation(velocity); + + if (AmongUsClient.Instance.AmHost && delta.magnitude <= 0.15f) { - npc.MyPhysics.SetNormalizedVelocity(Vector2.zero); + body.velocity = Vector2.zero; + UpdateWalkAnimation(Vector2.zero); Owner.RpcCustomMurder(Target, true, teleportMurderer: false); if (Target.AmOwner) + { CoroutinesHelper.CoNotify("Oops! The Wraith NPC got you..."); + } WraithCallerUtilities.AddKillNPC(Owner.PlayerId); break; } - yield return new WaitForFixedUpdate(); + + yield return null; } - npc.MyPhysics.SetNormalizedVelocity(Vector2.zero); + body.velocity = Vector2.zero; + UpdateWalkAnimation(Vector2.zero); Dispose(); } + [HideFromIl2Cpp] + public void UpdateWalkAnimation(Vector2 velocity) + { + bool moving = velocity.sqrMagnitude >= 0.01f; + if (velocity.x < -0.01f) + Visual.cosmetics.SetFlipXWithoutPet(true); + else if (velocity.x > 0.01f) + Visual.cosmetics.SetFlipXWithoutPet(false); + + if (moving) + { + if (!animations.IsPlayingRunAnimation()) + { + animations.PlayRunAnimation(); + } + if (Visual.cosmetics.HasSkinLoaded() && !Visual.cosmetics.IsSkinPlayingRunAnim()) + { + Visual.cosmetics.AnimateSkinRun(); + } + } + else + { + if (animations.IsPlayingRunAnimation() || !animations.IsPlayingSomeAnimation()) + { + animations.PlayIdleAnimation(); + + if (Visual.cosmetics.HasSkinLoaded()) + { + Visual.cosmetics.AnimateSkinIdle(); + } + } + } + var pos = Visual.transform.position; + pos.z = pos.y / 1000f; + Visual.transform.position = pos; + } [HideFromIl2Cpp] public void Dispose() { if (!isActive) return; + isActive = false; - if (OptionGroupSingleton.Instance.ShouldSwitchCamToNPC) + if (Owner.AmOwner && OptionGroupSingleton.Instance.ShouldSwitchCamToNPC) { Camera.main.GetComponent().SetTarget(Owner); ownerLight.transform.SetParent(Owner.transform, false); ownerLight.transform.localPosition = Owner.Collider.offset; } - if (AmongUsClient.Instance.AmHost) - { - 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; - } + if (body) + body.velocity = Vector2.zero; + Destroy(Visual.gameObject); Destroy(gameObject); } } -} +} \ No newline at end of file diff --git a/NewMod/NewMod.cs b/NewMod/NewMod.cs index b43aa33..312136a 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 2", true, ReactorCredits.AlwaysShow); + ReactorCredits.Register("NewMod", "v1.2.9 Hotfix 3", true, ReactorCredits.AlwaysShow); Harmony.PatchAll(); NewModEventHandler.RegisterEventsLogs(); @@ -59,16 +59,6 @@ public override void Load() ShouldEnableBepInExConsole = Config.Bind("NewMod", "Console", true, "Whether to enable BepInEx Console for debugging"); if (!ShouldEnableBepInExConsole.Value) ConsoleManager.DetachConsole(); - var bundle = NewModAsset.Bundle; - var assetNames = bundle.GetAllAssetNames(); - - Instance.Log.LogMessage($"AssetBundle '{bundle.name}' contains {assetNames.Length} assets"); - - foreach (var name in assetNames) - { - Instance.Log.LogMessage($"{name}"); - } - Instance.Log.LogMessage($"Loaded Successfully NewMod v{ModVersion} With MiraAPI Version : {MiraApiPlugin.Version}"); } diff --git a/NewMod/NewModAsset.cs b/NewMod/NewModAsset.cs index 2105dcb..16e78ea 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 FeignDeath { get; } = new("NewMod.Resources.feigndeath.png"); public static LoadableResourceAsset VanillaKillButton { get; } = new("NewMod.Resources.killbutton.png"); // SFX @@ -64,6 +65,7 @@ public static class NewModAsset public static LoadableResourceAsset RadarIcon { get; } = new("NewMod.Resources.RoleIcons.RadarIcon.png"); public static LoadableResourceAsset SlashIcon { get; } = new("NewMod.Resources.RoleIcons.SlashIcon.png"); public static LoadableResourceAsset DeployZoneIcon { get; } = new("NewMod.Resources.RoleIcons.DeployzoneIcon.png"); + public static LoadableResourceAsset ReviveIcon { get; } = new("NewMod.Resources.RoleIcons.ReviveIcon.png"); // Notif Icons public static LoadableResourceAsset VisionDebuff { get; } = new("NewMod.Resources.NotifIcons.vision_debuff.png"); diff --git a/NewMod/Resources/RoleIcons/ReviveIcon.png b/NewMod/Resources/RoleIcons/ReviveIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..355c4ac9cb5487eff500b23a435beea03c605dcc GIT binary patch literal 9067 zcmV-xBb3~UP)M;7^G#1IUm*jc3Bf$hx!>QOv(M~v<~;U!&dhPotp8q*z1DxN^@8FsNco0HE?Tkf9)NboNZr&u@S6rn>qy4ZJb2?qVfDm+61odo3eAFQ;xYP(Llx;KMa{dIB~3`9 zt9v?#Oau~9HExl#%?{ie`(VY*zi{n>s-Sm4ABDaMT?_TQu~7gZTFtKkEC)cJgBC#F zfUYF%xzHL&UD68dd!c8cHfT5`8Sln+joY26dwZThf$ZPdzk_6B^&g|k^`FxALP-J1 zVCZCMA=zs>J{emBVQCo2Pb6(3dYyZ}hkgkOOn=8#xVkbeB#Oomb|3UkYwiBf-q3L9 zL?{3df~gn-XpW{b^^jJN1p=+(NP`FnB5tKM9o*4CT4~(4atkD90Tzvwh|<4}o}T_) zgA$b<@SFha>Cj$KC!{V(T1OfMuP-AI?exSHpCIP! zf$oIvfi}lTZiT4}yOdPwwgfnJe-)#Bs8{fTDG4DB-9@;x4_4d=1frpIaTDU985{uW zE^aqLN~Z_*e%uvfRH2HgOff4$!#@YV??KWSYJ8kVwzQE3)Sm}LpMw01iI$UC>p@ZY zuESk1CIC=|rlhf3p%$Wi19}+JdX7MZ)6w3C<5&cUE`_E+gYfq-@YqJ6w9xmlE6M}_ z^3j|CU>9@;bOq5@L4Sobqr!5`V6NJ_5Rk3_NMD9df^-UzTgH&~>(JSz!u|pP<=CKr z;IGh1=n|5C7kUM9wUW9wZD))l>;sU1bP7O9shPOn?@a)p3{9wphqMa)6>Mo1bywfS z{VfqmzW@m=(nii02fvJfB-v`Lq{Ya&#E)wuMW_@~B`c*aMXd)7J{pGSL0W~@o|ZO9 zHDDGv2#y>1wN9k_a>sAP917F+#VIj=MOpnCK&w};uDjue8y0x!#v5;3;F9j=(^`N> zW13l{t%cM*NyFeYTF23f^c#4zqpp=^-N)-O-A<*`09qmM(52F-Ujb<4%9V$E!4e9E z)-e`Y>GfRJP)X z2j2Wm{j2rI&fhk_siC#un#Qe-+G@$4S&>poAVp$TdNx&6RX2ITLXK)cth6TV#&^M# z4M+e`Jl`GTs1@W~WhBr^Gie1|XQ8Eeq@`R<6l0y*n%_FV4uC9>Ex_RZDfC9;w#F#+ ze<)-^4b4wwX_EFd}r-qJD}6ekaS1q6}0G65wOk(}l=&Z48XLj)Y1i`Jr~?)4yn1&}Og z*xGP)$c7%Fo{O+I2r#%m0qOWiLb|ApYLj=kHFo0Qn!(BYbO~`w>FE;y5=hjr2r8jF zl3(FZKmD|xW`lOn`I z31$BpbOqD`-3MvR#O-e50tc{kb;Tij5qEvJNz_DdjzlAu{%6O31X7ZwH{|slG}cal z`nzIkEZ>DqU$nyRc)+8sqX#o(1Y2F&hf?r)lQ6GbUa;0` z$6b43+$!9-;DJ-lz#&cC(2aS)sy((v+)So*$gi$xR8u{3$S+!Bqkw3C#!io1@Rth; z?)tofBJc7Uzu<|L$f+nZs`F$1qF4aq@)TX!CJ%ER%lMARU$8SXcyePO?Tk8SjR45E zNSO|d3U?lqrrM_O5qAY3ve3~3M-F_rP!RPRfEb5-$Xct)QFnia%4>GxFS5T_skG2D zo}@2(+yy&}2_6+D+8%vcHso|u*y+sW$j*t=(b2J-v6ChU22FGXAiBjA@Yibq%BReL zPJ`HdlBP`qh!eCBes8znJh^ET=WwIg;8E6BkH1=PcE~wimkgu_)#=I>k6Zi{+UHq7 z6vd{x_P-cdUy~ti##?0r(3&-CuA~dv25Cn-W1&*q)O1+0FfHDtxLr_CssZ(XVjRcw z(rqBrFqj$TWgpX!kFJTvC)P2FzSABELNSm}uq7`%5{>d7Rg`%DW`_{i)pR=|t zDS)(hMFBNK7>EG%lx#o${PVXlmhnA+Cf$N*)23~!uC7-1bpZUzV#xPLkQMLB!@s7I z&J>)|xXb@I{TnpUi;4}ODUiPOC2zkW8cE}hqJt+UfGPFc>L>sGKYyQ#->XgVrZg&t z%44+n$Yx7?QbLzzNpXCxo3teZ(BQ#?Z>K=oILkRP2h`)W(q)9iiR&xaI z(@3cOAFPrX@9e828I*wOzE#nxgMYvF_v&V~y^>oN*0_@er3+bOt4+vUW^(u|34oY! zeFkkYRpp@JRmdv2q1}*)L?ZVRo^efw`%eKL0nor}U()GCKdjOi^3$GHX_%Le>rhss zXPkce>Dtu2jchW?l=}&WI)@ViEbuYr-Sscm>oA@1^H&l8QLwAX+yzZFZheJ9ZzOyg zx0y0!iuPg_%AJBp9u;^FbMFD5S&%zmbc2g(Gr0d%Q5lwpS%_|EI@9@2BOiSt<^i(x z4I(s-wryY$b-KT(iB{pM4%LLd0v}(d=m4U-Iho41jlB~zoW~y4!H~>Dnt|!a2Bk|i zD~S+)1DYoe9^z^JXCK0S3r1;jq@7>-(wE}vA~~k*)#%829HgVS($qQ#_d^Pl{6Zu_ zJx#<$l4oKeV@()feX?il)FGkN!TE<$8TV`Ko&23Nd-m)b#mxl{GqA3s3%2Jr1|Zo4 zcytEY0J1rd28Y^2eJ^}(;=TsrVpmjxud}CARHiXzbTO~M{|%mQ=P8c`7t;#S=u=#V z$Vo?W^>FIFeK$Cz-7>1>8)%&S0ZgYzktnCes3p_78`|Puw z#VLU&6L5$t^9JB?1hHw;ChdSbfyTMVMy7R#> zK-7*~4{6vAuxBQuO5-UifXG89=l)D#ZkRc9rn8If0Z-z9jmTbuwsOWt07e?f-P=JH zcIxAgKRyCYwiUi#gNohNJimSWcD>)_6o)SOzo$>1-iqyPK01K~UYbTXeF80!`z$GY zz8whYiRRJGCm@&op^KsaQ8L1turGyPfxJfT;2!x{x8%Sj?}?a*egsBS`Zyi{{VCo= z>D=)I=_2G3&LHP0XcFYSf$0}K%A>8VO`k$BR9s)QunLROUIPaXbUuj!-=A~;j#zlX z7Q)^QzaPz;H!rRo@N2+69NSqhDL9jE21V7LbTt_Be550ycl5`RjgFz{lY6&PuMPMm z%O%)442PA{2uvSp8QroGWRk<4W6l2ZXQQG5C^<8_Enh3nRQS!q4?ldzv(G-O zeKNNTjI*Oc`=FT|fVH=`Uqt+^@tgTOG$wg=&z?Q{wxPp?F8E{Q^?p1SDyoS@BkSoN zin7*3V>7g}o3Pi-7(7F5sI%gl?W)Ww`%%c~osP%a8Q7{TR!XBTt!|Iqb1~)nJ3N&> zWR2b1&p1e#r{tPzpj18$;Jl-iYg*z<02=3LtHR<>HX$1Js7D@oWc-{tb6!L^`r1V% zp(LkI<=0H*2Xp7n?K&Gn4D$FqmX5Syn(rNCaagw`}?)$m8qDI~M zHx7LMz;i!1_=EbG)qterYFF=PWa1h&DW1I2LhF|7y<|K9I?uAbK{@rSLSeQx66>JY zNu-RDf^zo?JZKwtcC`o~IobkF5y+3xN~)=TbvbnbTrs;=Hp9GH{8k;bgvAV=ao5C??N=qP`(p1}Yzlj=WxIq2)q7E}t z#`Xjj1wdq`@q!BBu*N?YP-*+Y*CBWs&N-?|x)Bb8G#BoVd*X>FT#)H4h%_-X2a})5 zHB^0w{YQbkA#bGC`vBkiX%V-(;*#+pey1Q{n2p(G|bdjU!vg1$T{VJ(d$ z-o9oY%)p#24$?^QNvsmR0|pGxCvpvW>g~|PoBn*#01u#j;!}6(a1Kee)}%7yERLZ* z2M-@H{39;6-EF(?Km+N>th9(5oU8rQ2TpjPBbG>uhB0SGOFID>BXH9JP}qjYQ$}YH zPMC>35OSuZf(1rHx=)-pgTZu1i#KoHydEKDJ*&d5z0++>;9|qo000mGNkl(=o*ckY}(W#a=?-AtlA04M&0baZs+Yw+$9mK4=! zTjMqHgS=fsk9cVU+7QUk&N=5CeL>ZUQe9oG&$g&5uueeYC8usof`-3W8;7HzXkGoQ z^%`3_oB{7I8D~5Li1L{PyxcOb#ohGHHqG|V_^xv@&v1FM?L{a4#IX~PLAFPEqK2)p zQWBn^Zf8LNLRey)?kN~SL9RXW$Rl58>U|jQRG`?+CZ-l2of0wl9&zZQhi(Bxhop%g zA^fq|UVBZ)G4=ZtPP!Z}FVKMTY5U5RD-T5be2RJu%z;PRs4+n&$|Gv_t+G}6dYO~% z>akZp!5HH~;^wS{sJk?n!-tF0;Ml2*h6G-Xst!=)gD ze9~&6yaAL!`?#5g$apfKxc#uh4%-xoM5ZEuOJZ~}{{KDXkVEzYVC`$xI0x=EtJ;kF zH-JckK7G6?`6>O=cV0$J0st(=k*GZd^4|OQC8ymu+ z&9U>g%u}~Zc4$!(y%!DSycyvH_`Co*uROeNF9&J+36#5&UHUV11NYt7w?H}tAvx=~ z?jP}M#fatbIAANMlh7BA3 z0&Uv3apOY|Km73hgkJ&ugvOl()Es``yvga+0&Vnx3#ggaXs4P2dqbJo%iaz!wFOP`>^#J9q-0g z*n6-q5DUO_H|}}Hnv~j@dnvV0PK}_9awJQ9Jq;?bo^~xv%>e+HGxpH;lXZqgU!Tyo zcof%AP;Da|y=BXm+h2b9<&*HAK$lhrc$_{Tq3Cxe_JOZ&Lw^58<0VYn;j51y)cR(U zC8F&bC!_|WH6x8R$6WNvMb2iiSh2}^Ie^GX3sL4V0OM4pzi-!l8cb_@L~CdQqP_s4 zk2(Cbaa*GRDvc-bjt0CB6ZSXKeyF~+{t)ld0>HT)dIR2?EoPdSgkyD7_HimTfD8pw z9`B$L^~6slXFUbSW50`g`lTA($D3eu^mXb#|M}0y*RNl{FB&V1#`qQVPh#oo85Y~G z(UW;Ei;p+B3E-mzGHu$rL8Z0@tf^JiRS6rE(Ma@XjEAH({N^uNeXilvhJ&8m{p3#M zHyPRo8VOB@mI9JrBkQAZ@8vR$x%W5L+*4(%o*OY@#0~%z6>;ysp!hh{P2<;Af&9je z$DK(tcRR zXxa`REnEtuPFOiEEqu;o3=YlbGcYEltw<8*^zVh$&>U>0I}QS&*TijL)z>} z8uWuiJQn*Ps2p0S$2cfN0V>V_9G&yI3(cdh%BkOyB}<0U6<&&<-|htT!UfG?>8>U) z5tw<=3m0kP;FO|ibdx3*npPLG#o_QO_oWg6q-p;h+qZAmKsheYVZ(;$+c-xM;B1>< z`!;~CptT@xMu4aq5Y<%=tiI424v6#){)^tQ{@|`80HJuI8uR@4&O7fsW1i=f;Tjey zX^f8mgc|I8OqjG9_oYmmVGSr{d;*8FxOh4PV68*p?J`ZHnkE9S9bLZuajggdfgGv+ zm$8@AZJl$=Ew^aL-D!a}Yt}Rpeioj1mw~&e2CX7+`5wFyA7l1s0DPghD^sDGM>UV6 zyw1j^{C%A&0YIJBTAgUTf&ueFufPG%CHTJtje{)IXN)zr8lW5#9u(F&=7e%JFKZrx zCeoHfLU>>FE z)w3ie_AsSfLv7Ta$`t&oo?v4xGm*BAHm!N}1sR)6e|ZU(Cw(K0rXvcyRlz8gdIf-7 z-`d(bjq>PRFhRy#fBp4iCr+IBf85jFPoL8~P9eP)t?O7jw|-lF!rP$Nj=gqEv@_b* zK-7U|l=6tzATIzZHI25BMoQ>16kZ0>fwKnyFvFc$=QlJo$nRv!7RCF*;qc9P{)eYK zpYuW_ExQTPC=)^^bm_cp^Ry5cFF^B{=6|)l(Wd=6y|Wb$?CPjd&J^7-Ilq?LmR|z0HI(O7^3*smIZ7$~DZ4H= zqk;h>MiMShM3*wzp~Mc~!|O>|!K=;pBTS zuTSwAUiQ$w29Vwh4T(mg`iT33fJ&NNtMfStg>JDEYGtzDd;p0^tJTgj+P)2YMR zp_80%??iwC z+M|GN17UY`?&++hdkjOhbXm7?-W*<#Tsij%4Prme`?M7ML*WFS`!F~A!8E@ zm)4N38gtcyEwwE-H`O*ZqoKY4IL^fWB=(FL(CPr&(S)6H)!VPiCg7>fzOWiK51(^R~);OV1XxV@P1N5rSgMi@yw8!1pw_^VY`#S8U*vkRQGUDnh zu8Xks1+5DZ%6~(8n#N#DK(=^dNXbcKsK@9=H8}k~AW5eUd}*JQIV1(5p*HiRpGY^e zmO75YpY>r&V$s73ADwWV3Xm?XHr4k6re_%|-9|U}Ei}|@fOSH{mWBg9|Bug)JniYz zvd&cXwuV;2=Wm@q2R_$?283>c$7gzE>wKnnWz8h;F}C2Yfp5hMOs6|K74hU{|q=r>NL!6W1EXM{`z|7EC}8t=U| zH8svV@_eYVtqLFs$8tG>uJyOrX6DS9v(Xf5aBGFP z1C4Pkv&4<~b24;I$y(@VbX!gyJmtUPep!BTD5pZFkDPW3!%gy529RVR5Pg8Lne#my zmKV+eM4I~M1s*Y!7GfR6S(OJYJDBa9q3{^hSz~R?-RCW2u~7BNRM59NfFv8sU!4a; z59OnI#KvC)EtNT*q06>yLKnW61 zFQSP+LSPzC0H8F234;AeAOKLeoN``y?oOKC#ad9mo}H$zS=XJS0|@okF9k-qbK3Xn z5?}VKx&rae9LcXOgI)GFVp16Lgg;e%{jPDtkWK|LX^+PRGr16hL z+OMfY`*`Yh{nnx+0P>r8Ux|mtEXnxCtzUl`NJ|d6fJgNU03<1tnA9k0oDyeK5c>nF z4=^(Z&3I3)OiBhI1Qv{ga&3)XjDP0fGtF^(9j6ndIhCSh0P08Mpt6jA`g%9ESIGIu zYlsJ5XPJD1sAK?A2`EgT*Fu!Bq~jmSLDbi~F>9QEoyI&y<@HM?1yDcILVo2{k?}@X zIdAXiJh%Yl{h6q&^(`rYvgY3V5z5kixz)_jjk!^J#hzL-HG=-pt4#vXak&(t3o00=aMc_&Ab6SWpF|>FO0009BNkl%T zhs`yM_Fm-Bm3v=l<_w!-4yr!Lgl)KM?qzVeh3m3~i+VuxBy<Nm>;eUa;x2h{Gq&+9{FUt!E!$ZSORqQ`xy8apSn4OWG1~4a=L0=Bh*GsA>pFVK$ zQEdIPq<*Ki$Ji*u&9WHK+ZVdAUI&jW>~eyrX-AX!>h`aime*U%+FfhS<9i-AyP~_y z;LzYsfTZdB8?ka}7N)_paPb0R`Z@L;*#Cxp2g%0jHj3EoE1^qIyqe7ob#&Nm++ojY*GiFz_d@2}m5N8e=H-&(sj zG#ok+>OBBaAR`c=i5ALTX6kd`aqC}h5XJKCXp6QTJG5%(hw)46xQaxBX=xmNfI(Aw zZf7=b!pyOXcozVbyDRIn;Bm$3f(Y5@$6-pXp*uAGQl4lOfkdBDR#7Y$0q8lxeSfX2 z>;RG$Qdd?6VQHpyiSkmO2&YdQHE8qb<}1~>6tol|>XJv#pDWPUa>>#$0?`wW%jc!+ z08+WsptEMpx>AjczgWU@1SDXJ#9MhNFY@R&MnDpXREJbVMf}PuQ62zC02P0Dc#S@> ztg(%{GHD>++DA-!1whJJq{dZ4Cku*uULhq2A~~+>7asw}CGmz&zcgxOHFhe>1TI$= zvK7ypUOZC?gYN1s!kZugNO-s(w{N2az9W|+q8i$5bk`ObBm|M%Zg?hJd`eNizQ$8P z@B$;9Tk&!z`*Py8Ls0;=4%@fsbpWL*slecZMebB_g0KQ5;7JZ>w&9f)(r5l~`Zs+N zK>mv##4MHs0MXYWeKRp9mH~ir5?2s8Sporok|h%ObCN&+pq#`NL{64K0H9=v1pb^P z5CAAAaRrf+B@h58St5Zyiv$vH2LMWxN8rm*0s(+>6j%^FQ3C%D00960?Z)0O00006 dNklyiuadbv-tROuBLN5tL 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 new(this) { - Icon = MiraAssets.Empty, + Icon = NewModAsset.ReviveIcon, OptionsScreenshot = NewModAsset.Banner, MaxRoleCount = 3, }; @@ -41,7 +41,6 @@ public override bool DidWin(GameOverReason reason) { return false; } - - return base.DidWin(reason); + return true; } } diff --git a/NewMod/Roles/ImpostorRoles/PulseBlade.cs b/NewMod/Roles/ImpostorRoles/PulseBlade.cs index f4d3628..7c78180 100644 --- a/NewMod/Roles/ImpostorRoles/PulseBlade.cs +++ b/NewMod/Roles/ImpostorRoles/PulseBlade.cs @@ -78,7 +78,7 @@ public override bool DidWin(GameOverReason reason) return false; } - return base.DidWin(reason); + return true; } } } \ No newline at end of file diff --git a/NewMod/Roles/ImpostorRoles/Revenant.cs b/NewMod/Roles/ImpostorRoles/Revenant.cs index eba7774..2d140e6 100644 --- a/NewMod/Roles/ImpostorRoles/Revenant.cs +++ b/NewMod/Roles/ImpostorRoles/Revenant.cs @@ -67,6 +67,6 @@ public override bool DidWin(GameOverReason reason) return false; } - return base.DidWin(reason); + return true; } } diff --git a/NewMod/Roles/NeutralRoles/Prankster.cs b/NewMod/Roles/NeutralRoles/Prankster.cs index bd9c777..d23c09c 100644 --- a/NewMod/Roles/NeutralRoles/Prankster.cs +++ b/NewMod/Roles/NeutralRoles/Prankster.cs @@ -4,6 +4,7 @@ using UnityEngine; namespace NewMod.Roles.NeutralRoles; + public class Prankster : CrewmateRole, ICustomRole { public string RoleName => "Prankster"; @@ -14,22 +15,25 @@ public class Prankster : CrewmateRole, ICustomRole public RoleOptionsGroup RoleOptionsGroup { get; } = RoleOptionsGroup.Neutral; public CustomRoleConfiguration Configuration => new(this) { - MaxRoleCount = 3, - DefaultRoleCount = 1, - DefaultChance = 30, - OptionsScreenshot = MiraAssets.Empty, - Icon = MiraAssets.Empty, - AffectedByLightOnAirship = false, - KillButtonOutlineColor = RoleColor, - RoleHintType = RoleHintType.RoleTab, - GhostRole = AmongUs.GameOptions.RoleTypes.CrewmateGhost, - CanGetKilled = true, - UseVanillaKillButton = false, - CanUseVent = false, - CanUseSabotage = false, - TasksCountForProgress = false, - HideSettings = false, - CanModifyChance = true, + MaxRoleCount = 3, + DefaultRoleCount = 1, + DefaultChance = 30, + OptionsScreenshot = MiraAssets.Empty, + Icon = MiraAssets.Empty, + AffectedByLightOnAirship = false, + KillButtonOutlineColor = RoleColor, + RoleHintType = RoleHintType.RoleTab, + GhostRole = AmongUs.GameOptions.RoleTypes.CrewmateGhost, + CanGetKilled = true, + UseVanillaKillButton = false, + CanUseVent = false, + CanUseSabotage = false, + TasksCountForProgress = false, + HideSettings = false, + CanModifyChance = true, }; - + public override bool DidWin(GameOverReason gameOverReason) + { + return gameOverReason == (GameOverReason)NewModEndReasons.PranksterWin; + } } \ No newline at end of file diff --git a/NewMod/Roles/NeutralRoles/Tyrant.cs b/NewMod/Roles/NeutralRoles/Tyrant.cs index 00d6dcc..49004bc 100644 --- a/NewMod/Roles/NeutralRoles/Tyrant.cs +++ b/NewMod/Roles/NeutralRoles/Tyrant.cs @@ -96,7 +96,8 @@ void AppendAbilityLine(int index, string text) } public override bool DidWin(GameOverReason reason) { - if (reason == (GameOverReason)NewModEndReasons.TyrantWin) return true; + if (reason == (GameOverReason)NewModEndReasons.TyrantWin) + return true; if (reason == (GameOverReason)NewModEndReasons.ShadeWin || reason == (GameOverReason)NewModEndReasons.WraithCallerWin || @@ -108,8 +109,7 @@ public override bool DidWin(GameOverReason reason) { return false; } - - return base.DidWin(reason); + return false; } public int _kills; public static byte _championId; diff --git a/NewMod/Roles/NeutralRoles/WraithCaller.cs b/NewMod/Roles/NeutralRoles/WraithCaller.cs index da6ea51..7628a8a 100644 --- a/NewMod/Roles/NeutralRoles/WraithCaller.cs +++ b/NewMod/Roles/NeutralRoles/WraithCaller.cs @@ -64,5 +64,9 @@ public StringBuilder SetTabText() } return tab; } + public override bool DidWin(GameOverReason gameOverReason) + { + return gameOverReason == (GameOverReason)NewModEndReasons.WraithCallerWin; + } } } diff --git a/NewMod/Utilities/WraithCallerUtilities.cs b/NewMod/Utilities/WraithCallerUtilities.cs index 3f8123e..2f6ab9c 100644 --- a/NewMod/Utilities/WraithCallerUtilities.cs +++ b/NewMod/Utilities/WraithCallerUtilities.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using NewMod.Components; using Reactor.Networking.Attributes; using UnityEngine; @@ -8,99 +7,63 @@ namespace NewMod.Utilities { public static class WraithCallerUtilities { - /// - /// A dictionary holding the number of NPCs sent by each Wraith Caller. - /// - private static readonly Dictionary Sent = []; + public static readonly Dictionary Sent = []; + public static readonly Dictionary Kills = []; - /// - /// 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) { return Sent.TryGetValue(ownerId, out var v) ? v : 0; } - /// - /// 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 NPCs sent by the owner. - /// public static void AddSentNPC(byte ownerId, int amount = 1) { Sent[ownerId] = GetSentNPC(ownerId) + amount; } - /// - /// 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; } - /// - /// 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; - var npcId = HostNPC(source); - RpcSummonNPC(source, target, npcId); - } - public static byte HostNPC(PlayerControl source) + public static void RequestSummonNPC(PlayerControl owner, PlayerControl target) { - var prefab = AmongUsClient.Instance.PlayerPrefab; - var npc = Object.Instantiate(prefab); - npc.PlayerId = (byte)GameData.Instance.GetAvailableId(); - - npc.isDummy = false; - npc.notRealPlayer = true; + RpcRequestSummonNPC(owner, target.PlayerId); + } - var host = AmongUsClient.Instance.GetHost(); - var npcInfo = GameData.Instance.AddPlayer(npc, host); + [MethodRpc((uint)CustomRPC.RequestSummon)] + public static void RpcRequestSummonNPC(PlayerControl source, byte targetId) + { + if (!AmongUsClient.Instance.AmHost) return; - AmongUsClient.Instance.Spawn(npcInfo); - AmongUsClient.Instance.Spawn(npc); + var target = Utils.PlayerById(targetId); + if (!target) return; - npc.NetTransform.RpcSnapTo(source.transform.position); + var start = source.GetTruePosition(); - return npc.PlayerId; + RpcSummonNPC(source, target.PlayerId, start.x, start.y); } + [MethodRpc((uint)CustomRPC.SummonNPC)] - public static void RpcSummonNPC(PlayerControl source, PlayerControl target, byte npcId) + public static void RpcSummonNPC(PlayerControl source, byte targetId, float x, float y) { - AddSentNPC(source.PlayerId); + var target = Utils.PlayerById(targetId); - InitializeNPC(source, target, Utils.PlayerById(npcId)); - return; + AddSentNPC(source.PlayerId); - } - 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); - } + var holder = new GameObject("WraithNPC_Holder"); + var npc = holder.AddComponent(); + npc.Initialize(source, target, new Vector2(x, y)); } } -} +} \ No newline at end of file From 95ee90e21d5002994b24e2bfd66a294377a9be04 Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Mon, 4 May 2026 09:13:50 +0000 Subject: [PATCH 04/14] Fixed the Revive button not highlighting --- NewMod/Buttons/Necromancer/ReviveButton.cs | 82 ++++++++++++---------- NewMod/Utilities/Utils.cs | 13 +--- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/NewMod/Buttons/Necromancer/ReviveButton.cs b/NewMod/Buttons/Necromancer/ReviveButton.cs index d90152d..bcf2d59 100644 --- a/NewMod/Buttons/Necromancer/ReviveButton.cs +++ b/NewMod/Buttons/Necromancer/ReviveButton.cs @@ -52,22 +52,56 @@ public class ReviveButton : CustomActionButton /// public override LoadableAsset Sprite => NewModAsset.NecromancerButton; + private DeadBody GetReviveTarget() + { + var local = PlayerControl.LocalPlayer; + var localPos = local.GetTruePosition(); + + return Helpers.GetNearestDeadBodies( + localPos, + ShipStatus.Instance.MaxLightRadius, + Helpers.CreateFilter(Constants.NotShipMask)) + .Where(body => body != null && IsValidReviveTarget(local, body)) + .OrderBy(body => Vector2.Distance(localPos, body.TruePosition)) + .FirstOrDefault(); + } + + public static bool IsValidReviveTarget(PlayerControl local, DeadBody 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 != null && killer.PlayerId == local.PlayerId) + return false; + + return true; + } + + /// + /// Checks whether the player can currently use the revive button, ensuring cooldowns, ability uses, and conditions are met. + /// + /// True if all requirements to use this button are met; otherwise false. /// Invoked when the revive button is clicked. Plays a sound and revives the nearest dead body. /// protected override void OnClick() { var local = PlayerControl.LocalPlayer; + var body = GetReviveTarget(); - 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; + if (body == null) + return; SoundManager.Instance.PlaySound(NewModAsset.ReviveSound?.LoadAsset(), false, 2f); @@ -78,8 +112,10 @@ protected override void OnClick() 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 . /// @@ -89,33 +125,5 @@ public override bool Enabled(RoleBehaviour role) { return role is NecromancerRole; } - - /// - /// Checks whether the player can currently use the revive button, ensuring cooldowns, ability uses, and conditions are met. - /// - /// True if all requirements to use this button are met; otherwise false. - public override bool CanUse() - { - var bodiesInRange = Helpers.GetNearestDeadBodies( - PlayerControl.LocalPlayer.GetTruePosition(), - 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; - - return true; - }); - - return canUse; - } } } diff --git a/NewMod/Utilities/Utils.cs b/NewMod/Utilities/Utils.cs index 3cec4ab..741fd48 100644 --- a/NewMod/Utilities/Utils.cs +++ b/NewMod/Utilities/Utils.cs @@ -41,7 +41,7 @@ public static class Utils /// /// Maps a victim player to its killer. /// - public static Dictionary PlayerKiller = new Dictionary(); + public static Dictionary PlayerKiller = new Dictionary(); /// /// Stores the number of successful missions per player, keyed by their ID. @@ -102,14 +102,7 @@ public static PlayerControl PlayerById(byte id) /// The player who was killed. public static void RecordOnKill(PlayerControl killer, PlayerControl victim) { - if (PlayerKiller.ContainsKey(victim)) - { - PlayerKiller[victim] = killer; - } - else - { - PlayerKiller.Add(victim, killer); - } + PlayerKiller[victim.PlayerId] = killer.PlayerId; } public static void ResetKillTracking() { @@ -123,7 +116,7 @@ public static void ResetKillTracking() /// The player who killed the victim, or null if not found. public static PlayerControl GetKiller(PlayerControl victim) { - return PlayerKiller.TryGetValue(victim, out var killer) ? killer : null; + return PlayerKiller.TryGetValue(victim.PlayerId, out var killerId) ? PlayerById(killerId) : null; } /// From 234a94331f203a1a3192c86305e27c4bbda21aa4 Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Mon, 4 May 2026 09:42:51 +0000 Subject: [PATCH 05/14] Allowed random colors for NPCs instead of always red, and fixed name position --- NewMod/Components/WraithCallerNpc.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NewMod/Components/WraithCallerNpc.cs b/NewMod/Components/WraithCallerNpc.cs index 364af12..184fc89 100644 --- a/NewMod/Components/WraithCallerNpc.cs +++ b/NewMod/Components/WraithCallerNpc.cs @@ -52,11 +52,12 @@ public void Initialize(PlayerControl owner, PlayerControl target, Vector2 start) Visual.cosmetics.SetName("Wraith NPC"); Visual.cosmetics.ToggleName(true); + Visual.cosmetics.SetNamePosition(new(0f, 0.8f, -0.5f)); Visual.cosmetics.ToggleHat(false); Visual.cosmetics.TogglePet(false); Visual.cosmetics.ToggleVisor(false); - var color = Owner.PlayerId % Palette.PlayerColors.Length; + var color = UnityEngine.Random.Range(0, Palette.PlayerColors.Length); var bodySprite = Visual.cosmetics.currentBodySprite; bodySprite.Visible = true; From 5273cea6cf46a98586239f37e9a60747c4fbe01a Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Mon, 4 May 2026 09:51:30 +0000 Subject: [PATCH 06/14] Fixed RevivedKillButton issues --- NewMod/Buttons/RevivedKillButton.cs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/NewMod/Buttons/RevivedKillButton.cs b/NewMod/Buttons/RevivedKillButton.cs index d906fce..748dee4 100644 --- a/NewMod/Buttons/RevivedKillButton.cs +++ b/NewMod/Buttons/RevivedKillButton.cs @@ -19,9 +19,20 @@ public class RevivedKillButton : CustomActionButton public override ButtonLocation Location => ButtonLocation.BottomRight; public override LoadableAsset Sprite => NewModAsset.VanillaKillButton; + private static bool CanUseRevivedKillButton() + { + var local = PlayerControl.LocalPlayer; + return local != null && NecromancerRole.RevivedPlayers.ContainsKey(local.PlayerId); + } + public override bool Enabled(RoleBehaviour role) { - return NecromancerRole.RevivedPlayers.ContainsKey(PlayerControl.LocalPlayer.PlayerId); + return CanUseRevivedKillButton(); + } + + protected override void FixedUpdate(PlayerControl playerControl) + { + Button?.ToggleVisible(CanUseRevivedKillButton()); } public override PlayerControl GetTarget() @@ -29,6 +40,14 @@ public override PlayerControl GetTarget() return PlayerControl.LocalPlayer.GetClosestPlayer(true, Distance); } + public override bool IsTargetValid(PlayerControl target) + { + return CanUseRevivedKillButton() && + target != null && + target.PlayerId != PlayerControl.LocalPlayer.PlayerId && + !target.Data.IsDead && !target.Data.Disconnected; + } + public override void SetOutline(bool active) { Target.cosmetics.SetOutline(active, new Il2CppSystem.Nullable(Palette.ImpostorRed)); @@ -36,7 +55,7 @@ public override void SetOutline(bool active) public override bool CanUse() { - return true; + return CanUseRevivedKillButton(); } protected override void OnClick() @@ -54,6 +73,8 @@ protected override void OnClick() ); NecromancerRole.RevivedPlayers.Remove(local.PlayerId); + ResetTarget(); + Button?.ToggleVisible(false); } } -} +} \ No newline at end of file From f419b5035e87cab9e0692d27c8ab09408e7ca73a Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Mon, 4 May 2026 09:57:10 +0000 Subject: [PATCH 07/14] Addressed an issue where players inside the Shade Zone could remain invisible even after the effect ended --- NewMod/Components/ShadowZone.cs | 36 ++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/NewMod/Components/ShadowZone.cs b/NewMod/Components/ShadowZone.cs index 9a5cb36..4435a92 100644 --- a/NewMod/Components/ShadowZone.cs +++ b/NewMod/Components/ShadowZone.cs @@ -42,18 +42,34 @@ private bool Contains(Vector2 pos) public void Update() { timer += Time.deltaTime; + + var lp = PlayerControl.LocalPlayer; + var hud = HudManager.Instance; + var killButton = hud.KillButton; + if (timer >= duration) { Coroutines.Start(CoroutinesHelper.RemoveCameraEffect(Camera.main, 0f)); + + if (active && lp.PlayerId == shadeId) + { + lp.cosmetics.SetPhantomRoleAlpha(1); + lp.cosmetics.nameText.gameObject.SetActive(true); + + if (killButton.currentTarget) + { + killButton.currentTarget.ToggleHighlight(false, RoleTeamTypes.Impostor); + killButton.currentTarget = null; + } + + killButton.gameObject.SetActive(false); + } + active = false; Destroy(gameObject); return; } - var lp = PlayerControl.LocalPlayer; - var hud = HudManager.Instance; - var killButton = hud.KillButton; - bool inside = Contains(lp.GetTruePosition()); var mode = OptionGroupSingleton.Instance.Behavior; var cam = Camera.main; @@ -61,6 +77,7 @@ public void Update() if (inside && !active) { cam.gameObject.AddComponent(); + if (lp.PlayerId == shadeId && lp.Data.Role is Shade) { if (mode is ShadeOptions.ShadowMode.Invisible or ShadeOptions.ShadowMode.Both) @@ -72,6 +89,7 @@ public void Update() killButton.gameObject.SetActive(true); killButton.currentTarget = null; } + active = true; } @@ -81,13 +99,18 @@ public void Update() { var list = new Il2CppSystem.Collections.Generic.List(); lp.Data.Role.GetPlayersInAbilityRangeSorted(list); - var players = list.ToArray().Where(p => p.PlayerId != lp.PlayerId && !p.Data.IsDead).ToList(); + + var players = list.ToArray() + .Where(p => p.PlayerId != lp.PlayerId && !p.Data.IsDead) + .ToList(); + var closest = players.Count > 0 ? players[0] : null; if (killButton.currentTarget && killButton.currentTarget != closest) killButton.currentTarget.ToggleHighlight(false, RoleTeamTypes.Impostor); killButton.currentTarget = closest; + if (closest != null) closest.ToggleHighlight(true, RoleTeamTypes.Impostor); } @@ -95,6 +118,7 @@ public void Update() else if (!inside && active) { Coroutines.Start(CoroutinesHelper.RemoveCameraEffect(cam, 0f)); + if (lp.PlayerId == shadeId) { lp.cosmetics.SetPhantomRoleAlpha(1); @@ -105,8 +129,10 @@ public void Update() killButton.currentTarget.ToggleHighlight(false, RoleTeamTypes.Impostor); killButton.currentTarget = null; } + killButton.gameObject.SetActive(false); } + active = false; } } From 4b6a7d8c0ddb3e2856c248a2c6104221fe45d7ba Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Mon, 4 May 2026 10:37:03 +0000 Subject: [PATCH 08/14] Probably fixed the Revenant issues --- NewMod/Buttons/Revenant/DoomAwakening.cs | 2 +- NewMod/Roles/ImpostorRoles/Revenant.cs | 2 +- NewMod/Utilities/Utils.cs | 13 ++++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/NewMod/Buttons/Revenant/DoomAwakening.cs b/NewMod/Buttons/Revenant/DoomAwakening.cs index 742ec01..e831282 100644 --- a/NewMod/Buttons/Revenant/DoomAwakening.cs +++ b/NewMod/Buttons/Revenant/DoomAwakening.cs @@ -60,7 +60,7 @@ public class DoomAwakening : CustomActionButton /// True if the role is , otherwise false. public override bool Enabled(RoleBehaviour role) { - return role is RV; + return role is RV && RV.StalkingStates.ContainsKey(PlayerControl.LocalPlayer.PlayerId); } /// diff --git a/NewMod/Roles/ImpostorRoles/Revenant.cs b/NewMod/Roles/ImpostorRoles/Revenant.cs index 2d140e6..b0f13b9 100644 --- a/NewMod/Roles/ImpostorRoles/Revenant.cs +++ b/NewMod/Roles/ImpostorRoles/Revenant.cs @@ -28,7 +28,7 @@ public class Revenant : ImpostorRole, ICustomRole DefaultChance = 50, DefaultRoleCount = 1, CanModifyChance = true, - GhostRole = AmongUs.GameOptions.RoleTypes.Crewmate, //Indeed + GhostRole = (AmongUs.GameOptions.RoleTypes)RoleId.Get(), RoleHintType = RoleHintType.RoleTab }; public static Dictionary FeignDeathStates = new Dictionary(); diff --git a/NewMod/Utilities/Utils.cs b/NewMod/Utilities/Utils.cs index 741fd48..1e6aec2 100644 --- a/NewMod/Utilities/Utils.cs +++ b/NewMod/Utilities/Utils.cs @@ -815,6 +815,8 @@ public static IEnumerator StartFeignDeath(PlayerControl player) { var clip = NewModAsset.FeignDeathSound.LoadAsset(); + SavePlayerRole(player.PlayerId, player.Data.Role); + player.RpcCustomMurder(player, didSucceed: true, resetKillTimer: false, @@ -853,16 +855,21 @@ public static IEnumerator StartFeignDeath(PlayerControl player) if (info.Reported) { yield return CoroutinesHelper.CoNotify("Your feign death has been reported. You remain dead."); + Revenant.FeignDeathStates.Remove(player.PlayerId); SoundManager.Instance.StopSound(clip); yield break; } } - HandleRevive(player, player.PlayerId, (RoleTypes)RoleId.Get(), body.transform.position.x, body.transform.position.y); + Revenant.HasUsedFeignDeath = true; + Revenant.StalkingStates[player.PlayerId] = true; + + var roleHistory = GetPlayerRolesHistory(player.PlayerId); + var roleToRestore = roleHistory.Count > 0 ? roleHistory[^1].Role : (RoleTypes)RoleId.Get(); + + HandleRevive(player, player.PlayerId, roleToRestore, 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; - Revenant.StalkingStates[player.PlayerId] = true; Revenant.FeignDeathStates.Remove(player.PlayerId); if (player.AmOwner) From 344215ade8cbba102aef4d8beb81217a76828e2a Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Mon, 4 May 2026 21:51:46 +0000 Subject: [PATCH 09/14] =?UTF-8?q?Addressed=20an=20issue=20where=20the=20re?= =?UTF-8?q?vived=20kill=20button=20wasn=E2=80=99t=20showing=20for=20revive?= =?UTF-8?q?d=20players,=20and=20fixed=20an=20issue=20where=20revived=20pla?= =?UTF-8?q?yers=20couldn=E2=80=99t=20move=20or=20see=20death=20task=20info?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NewMod/Buttons/Necromancer/ReviveButton.cs | 3 --- NewMod/Buttons/RevivedKillButton.cs | 2 +- NewMod/Patches/Roles/RevivePatch.cs | 11 +++++++++++ NewMod/Utilities/Utils.cs | 4 +++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/NewMod/Buttons/Necromancer/ReviveButton.cs b/NewMod/Buttons/Necromancer/ReviveButton.cs index bcf2d59..1f0cab5 100644 --- a/NewMod/Buttons/Necromancer/ReviveButton.cs +++ b/NewMod/Buttons/Necromancer/ReviveButton.cs @@ -100,9 +100,6 @@ protected override void OnClick() var local = PlayerControl.LocalPlayer; var body = GetReviveTarget(); - if (body == null) - return; - SoundManager.Instance.PlaySound(NewModAsset.ReviveSound?.LoadAsset(), false, 2f); Utils.HandleRevive( diff --git a/NewMod/Buttons/RevivedKillButton.cs b/NewMod/Buttons/RevivedKillButton.cs index 748dee4..fce1eed 100644 --- a/NewMod/Buttons/RevivedKillButton.cs +++ b/NewMod/Buttons/RevivedKillButton.cs @@ -55,7 +55,7 @@ public override void SetOutline(bool active) public override bool CanUse() { - return CanUseRevivedKillButton(); + return base.CanUse() && CanUseRevivedKillButton(); } protected override void OnClick() diff --git a/NewMod/Patches/Roles/RevivePatch.cs b/NewMod/Patches/Roles/RevivePatch.cs index 74dcddf..468c03e 100644 --- a/NewMod/Patches/Roles/RevivePatch.cs +++ b/NewMod/Patches/Roles/RevivePatch.cs @@ -14,6 +14,8 @@ public static bool Prefix(PlayerControl __instance) __instance.MyPhysics.ResetMoveState(true); __instance.clickKillCollider.enabled = true; __instance.cosmetics.SetNameMask(true); + __instance.cosmetics.SetPetSource(__instance); + __instance.moveable = true; if (__instance.AmOwner) { @@ -24,4 +26,13 @@ public static bool Prefix(PlayerControl __instance) return false; } } + [HarmonyPatch(typeof(KillOverlay), nameof(KillOverlay.IsOpen), MethodType.Getter)] + public static class KillOverlayPatch + { + public static void Postfix(ref bool __result) + { + if (__result && PlayerControl.LocalPlayer != null && !PlayerControl.LocalPlayer.Data.IsDead) + __result = false; + } + } } \ No newline at end of file diff --git a/NewMod/Utilities/Utils.cs b/NewMod/Utilities/Utils.cs index 1e6aec2..6e18108 100644 --- a/NewMod/Utilities/Utils.cs +++ b/NewMod/Utilities/Utils.cs @@ -394,6 +394,8 @@ public static IEnumerator HandleRevive(PlayerControl source, byte revivedId, Rol revived.Revive(); revived.RemainingEmergencies = 0; + RoleManager.Instance.SetRole(revived, roleToSet); + revived.Data.Role.SpawnTaskHeader(revived); if (AmongUsClient.Instance.AmHost) { @@ -829,7 +831,7 @@ public static IEnumerator StartFeignDeath(PlayerControl player) if (player.AmOwner) { - HudManager.Instance.SetHudActive(false); + HudManager.Instance.SetHudActive(player, player.Data.Role, false); } yield return new WaitForSeconds(0.5f); From a5c764751391310a9bc6c29d0a96c63b1386658e Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Mon, 4 May 2026 22:04:25 +0000 Subject: [PATCH 10/14] Visionary path fix on Android, addressed a potential issue where the end game could trigger after assigning roles --- NewMod/NewModEventHandler.cs | 61 +++++++++++++++++++ NewMod/Options/GeneralOption.cs | 5 -- NewMod/Patches/HudPatch.cs | 2 +- NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs | 31 ---------- NewMod/Utilities/VisionaryUtilities.cs | 2 +- 5 files changed, 63 insertions(+), 38 deletions(-) delete mode 100644 NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs diff --git a/NewMod/NewModEventHandler.cs b/NewMod/NewModEventHandler.cs index 4ea18f6..1237e2c 100644 --- a/NewMod/NewModEventHandler.cs +++ b/NewMod/NewModEventHandler.cs @@ -4,7 +4,9 @@ using System.Reflection; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; +using MiraAPI.Events.Vanilla.Player; using NewMod.Roles.ImpostorRoles; +using NewMod.Roles.NeutralRoles; using NewMod.Utilities; namespace NewMod @@ -67,6 +69,65 @@ public static void OnRoundStart(RoundStartEvent evt) Utils.ResetKillTracking(); NecromancerRole.RevivedPlayers.Clear(); + Utils.ResetDrainCount(); + Utils.ResetMissionSuccessCount(); + Utils.ResetMissionFailureCount(); + Utils.ResetInjections(); + Utils.ResetStrikeCount(); + Utils.ResetKillTracking(); + PranksterUtilities.ResetReportCount(); + VisionaryUtilities.DeleteAllScreenshots(); + 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"); + NewMod.Instance.Log.LogInfo("Reset Mission Failure Count Successfully"); + NewMod.Instance.Log.LogInfo("Deleted all Visionary's screenshots Successfully"); + } + [RegisterEvent] + public static void OnPlayerLeft(PlayerLeaveEvent evt) + { + Utils.ResetDrainCount(); + Utils.ResetMissionSuccessCount(); + Utils.ResetMissionFailureCount(); + Utils.ResetInjections(); + Utils.ResetStrikeCount(); + Utils.ResetKillTracking(); + PranksterUtilities.ResetReportCount(); + VisionaryUtilities.DeleteAllScreenshots(); + 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"); + NewMod.Instance.Log.LogInfo("Reset Mission Failure Count Successfully"); + NewMod.Instance.Log.LogInfo("Deleted all Visionary's screenshots Successfully"); + } + [RegisterEvent] + public static void OnGameEnd(GameEndEvent evt) + { + Utils.ResetDrainCount(); + Utils.ResetMissionSuccessCount(); + Utils.ResetMissionFailureCount(); + Utils.ResetInjections(); + Utils.ResetStrikeCount(); + Utils.ResetKillTracking(); + PranksterUtilities.ResetReportCount(); + VisionaryUtilities.DeleteAllScreenshots(); + 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"); + NewMod.Instance.Log.LogInfo("Reset Mission Failure Count Successfully"); + NewMod.Instance.Log.LogInfo("Deleted all Visionary's screenshots Successfully"); } } } diff --git a/NewMod/Options/GeneralOption.cs b/NewMod/Options/GeneralOption.cs index 75c17f2..bd79be1 100644 --- a/NewMod/Options/GeneralOption.cs +++ b/NewMod/Options/GeneralOption.cs @@ -25,11 +25,6 @@ public class GeneralOption : AbstractOptionGroup [ModdedToggleOption("Anonymous Names in Meetings")] public bool EnableAnonymousNamesInMeetings { get; set; } = false; - public ModdedNumberOption SpawnChanceOfGlitchEffect { get; } = new("Spawn Chance of Glitch Effect", 0f, 0f, 100f, 10f, MiraAPI.Utilities.MiraNumberSuffixes.Percent); - public ModdedPlayerOption ChosenPlayer { get; } = new("Player who will receive the effect", true) - { - Visible = () => OptionGroupSingleton.Instance.SpawnChanceOfGlitchEffect.Value > 0f - }; /*[ModdedToggleOption("Should spawn NPC after round start")] public bool SpawnNpcAfterRoundStart { get; set; } = false;*/ diff --git a/NewMod/Patches/HudPatch.cs b/NewMod/Patches/HudPatch.cs index 4bc3769..eb59a8e 100644 --- a/NewMod/Patches/HudPatch.cs +++ b/NewMod/Patches/HudPatch.cs @@ -9,7 +9,7 @@ public static class HudPatch public static void Postfix(HudManager __instance) { // For some reason the effect gets destroyed, so the best way to keep it persistent is to reassign it here - ShadowFluxEffect._shader = NewModAsset.ShadowFluxShader.LoadAsset(); + ShadowFluxEffect._shader ??= NewModAsset.ShadowFluxShader.LoadAsset(); } } } \ No newline at end of file diff --git a/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs b/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs deleted file mode 100644 index f8d71d2..0000000 --- a/NewMod/Patches/Roles/EnergyThief/OnGameEnd.cs +++ /dev/null @@ -1,31 +0,0 @@ -using HarmonyLib; -using NewMod.Utilities; -using NewMod.Roles.ImpostorRoles; -using NewMod.Roles.NeutralRoles; - -namespace NewMod.Patches.Roles.EnergyThief; - -[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))] -public static class OnGameEndPatch -{ - public static void Postfix(AmongUsClient __instance, [HarmonyArgument(0)] EndGameResult endGameResult) - { - Utils.ResetDrainCount(); - Utils.ResetMissionSuccessCount(); - Utils.ResetMissionFailureCount(); - Utils.ResetInjections(); - Utils.ResetStrikeCount(); - Utils.ResetKillTracking(); - PranksterUtilities.ResetReportCount(); - VisionaryUtilities.DeleteAllScreenshots(); - 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"); - NewMod.Instance.Log.LogInfo("Reset Mission Failure Count Successfully"); - NewMod.Instance.Log.LogInfo("Deleted all Visionary's screenshots Successfully"); - } -} diff --git a/NewMod/Utilities/VisionaryUtilities.cs b/NewMod/Utilities/VisionaryUtilities.cs index 7759bc8..ea73c2d 100644 --- a/NewMod/Utilities/VisionaryUtilities.cs +++ b/NewMod/Utilities/VisionaryUtilities.cs @@ -33,7 +33,7 @@ public static string ScreenshotDirectory { get { - string directory = Path.Combine(Application.persistentDataPath, "NewMod", "Screenshots"); + string directory = OperatingSystem.IsAndroid() ? Environment.GetEnvironmentVariable("STAR_DATA_PATH") : Path.Combine(Application.persistentDataPath, "NewMod", "Screenshots"); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); From 671b8ffce019d9254433300b0a22426d20095550 Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Tue, 5 May 2026 16:23:05 +0000 Subject: [PATCH 11/14] Add Jsenm to the credits section --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 18af618..62d459e 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,8 @@ For more information about Starlight, please visit: [https://discord.gg/FYYqJU2b - **TownOfUs-R**: - Portions of code (PlayerById, GetClosestBody) and asset (ReviveSprite) derived from [Town-Of-Us-R](https://github.com/eDonnes124/Town-Of-Us-R). - **MoreGamemodes**: [MoreGamemodes](https://github.com/Rabek009/MoreGamemodes) - Derivation of IsActive and IsSabotage code. - **yanplaRoles**: [yanplaRoles](https://github.com/yanpla/yanplaRoles) - Portions of code (SavePlayerRole, GetPlayerRolesHistory). -- **EloySus**: [EloySus](https://github.com/EloySus) – for all button sprites used in NewMod +- **EloySus**: [EloySus](https://github.com/EloySus) – for old button sprites used in NewMod +- **Jsenm**: [Jsenm Discord](jsenm) - For the newer sprites/art for NewMod - **Pixabay**: [Pixabay](https://pixabay.com) - For sound effects used in NewMod - **angxlwtf**: [angxlwtf](https://github.com/angxlwtf) - Idea for **Wraith Caller** (originally for Hitman LP) From a5e71d61506d2846eb62194226160da2dc292de3 Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Tue, 5 May 2026 16:27:24 +0000 Subject: [PATCH 12/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 62d459e..b61ff38 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ NewMod is compatible with the following mods, enabling an enhanced experience wi | LaunchpadReloaded | v0.3.7+ | [Download](https://github.com/All-Of-Us-Mods/LaunchpadReloaded) | ✅ Supported | | LevelImposter | v0.21.2-beta+ | [Download](https://github.com/DigiWorm0/LevelImposter) | ✅ Supported | | Submerged | v2025.11.20+ | [Download](https://github.com/SubmergedAmongUs/Submerged) | ✅ Supported | -| Town Of Us Mira | v1.5.2+ | [Download](https://github.com/Au-Avengers/TOU-Mira) | ✅ Supported | +| Town Of Us Mira | v1.6.1+ | [Download](https://github.com/Au-Avengers/TOU-Mira) | ✅ Supported | --- From 8fdf45d94a412958d071f39c71f4d755d141a5e7 Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Tue, 5 May 2026 16:32:32 +0000 Subject: [PATCH 13/14] Update the forgotten csproj --- NewMod/NewMod.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NewMod/NewMod.csproj b/NewMod/NewMod.csproj index b21bb28..977fab2 100644 --- a/NewMod/NewMod.csproj +++ b/NewMod/NewMod.csproj @@ -12,8 +12,8 @@ - - + + From e4d09f2b03315c8f793f7ed20e7c34d4c22596a7 Mon Sep 17 00:00:00 2001 From: CallOfCreator Date: Wed, 6 May 2026 09:44:15 +0000 Subject: [PATCH 14/14] Bug Fixes --- NewMod/Buttons/Visionary/CaptureButton.cs | 1 + .../ScreenEffects/ShadowFluxEffect.cs | 12 +++++-- NewMod/Components/ShadowZone.cs | 9 +++++ NewMod/Patches/HudPatch.cs | 15 -------- NewMod/Utilities/Utils.cs | 17 +++++---- NewMod/Utilities/VisionaryUtilities.cs | 36 +++++++------------ 6 files changed, 43 insertions(+), 47 deletions(-) delete mode 100644 NewMod/Patches/HudPatch.cs diff --git a/NewMod/Buttons/Visionary/CaptureButton.cs b/NewMod/Buttons/Visionary/CaptureButton.cs index 02967ee..68e6fc9 100644 --- a/NewMod/Buttons/Visionary/CaptureButton.cs +++ b/NewMod/Buttons/Visionary/CaptureButton.cs @@ -57,6 +57,7 @@ protected override void OnClick() { var timestamp = System.DateTime.UtcNow.ToString("yyyy-MM-dd_HH-mm-ss"); string path = Path.Combine(VisionaryUtilities.ScreenshotDirectory, $"screenshot_{timestamp}.png"); + NewMod.Instance.Log.LogMessage($"Screenshot will be saved to: {path}"); Coroutines.Start(Utils.CaptureScreenshot(path)); } diff --git a/NewMod/Components/ScreenEffects/ShadowFluxEffect.cs b/NewMod/Components/ScreenEffects/ShadowFluxEffect.cs index e55085e..290807a 100644 --- a/NewMod/Components/ScreenEffects/ShadowFluxEffect.cs +++ b/NewMod/Components/ScreenEffects/ShadowFluxEffect.cs @@ -7,7 +7,6 @@ namespace NewMod.Components.ScreenEffects [RegisterInIl2Cpp] public class ShadowFluxEffect(IntPtr ptr) : MonoBehaviour(ptr) { - public Texture2D texture = NewModAsset.NoiseTex.LoadAsset(); public float noiseScale = 2f; public float speed = 0.3f; public float edgeWidth = 0.25f; @@ -15,12 +14,19 @@ public class ShadowFluxEffect(IntPtr ptr) : MonoBehaviour(ptr) public float opacity = 0.75f; public float darkness = 0.8f; public Color tint = Color.white; - public static Shader _shader = NewModAsset.ShadowFluxShader.LoadAsset(); public Material _mat; public void OnEnable() { - _mat = new Material(_shader) { hideFlags = HideFlags.DontSave }; + var shader = NewModAsset.ShadowFluxShader.LoadAsset(); + var texture = NewModAsset.NoiseTex.LoadAsset(); + + if (shader == null) + { + NewMod.Instance.Log.LogError("ShadowFluxEffect - Shader null"); + } + + _mat = new Material(shader) { hideFlags = HideFlags.DontSave }; _mat.SetTexture("_NoiseTex", texture); } public void OnDisable() diff --git a/NewMod/Components/ShadowZone.cs b/NewMod/Components/ShadowZone.cs index 4435a92..ae075e5 100644 --- a/NewMod/Components/ShadowZone.cs +++ b/NewMod/Components/ShadowZone.cs @@ -54,6 +54,9 @@ public void Update() if (active && lp.PlayerId == shadeId) { lp.cosmetics.SetPhantomRoleAlpha(1); + lp.cosmetics.ToggleHat(true); + lp.cosmetics.ToggleVisor(true); + lp.cosmetics.TogglePet(true); lp.cosmetics.nameText.gameObject.SetActive(true); if (killButton.currentTarget) @@ -83,6 +86,9 @@ public void Update() if (mode is ShadeOptions.ShadowMode.Invisible or ShadeOptions.ShadowMode.Both) { lp.cosmetics.SetPhantomRoleAlpha(0); + lp.cosmetics.ToggleHat(false); + lp.cosmetics.ToggleVisor(false); + lp.cosmetics.TogglePet(false); lp.cosmetics.nameText.gameObject.SetActive(false); } @@ -122,6 +128,9 @@ public void Update() if (lp.PlayerId == shadeId) { lp.cosmetics.SetPhantomRoleAlpha(1); + lp.cosmetics.ToggleHat(true); + lp.cosmetics.TogglePet(false); + lp.cosmetics.ToggleVisor(false); lp.cosmetics.nameText.gameObject.SetActive(true); if (killButton.currentTarget) diff --git a/NewMod/Patches/HudPatch.cs b/NewMod/Patches/HudPatch.cs deleted file mode 100644 index eb59a8e..0000000 --- a/NewMod/Patches/HudPatch.cs +++ /dev/null @@ -1,15 +0,0 @@ -using HarmonyLib; -using NewMod.Components.ScreenEffects; - -namespace NewMod.Patches -{ - [HarmonyPatch(typeof(HudManager), nameof(HudManager.Start))] - public static class HudPatch - { - public static void Postfix(HudManager __instance) - { - // For some reason the effect gets destroyed, so the best way to keep it persistent is to reassign it here - ShadowFluxEffect._shader ??= NewModAsset.ShadowFluxShader.LoadAsset(); - } - } -} \ No newline at end of file diff --git a/NewMod/Utilities/Utils.cs b/NewMod/Utilities/Utils.cs index 6e18108..c6dff3e 100644 --- a/NewMod/Utilities/Utils.cs +++ b/NewMod/Utilities/Utils.cs @@ -25,6 +25,7 @@ using NewMod.Roles; using NewMod.Components; using NewMod.Buttons.WraithCaller; +using System.IO; namespace NewMod.Utilities { @@ -799,8 +800,11 @@ public static IEnumerator CaptureScreenshot(string filePath) HudManager.Instance.SetHudActive(PlayerControl.LocalPlayer, PlayerControl.LocalPlayer.Data.Role, false); SoundManager.Instance.PlaySound(clip, false, 1f, null); - ScreenCapture.CaptureScreenshot(filePath, 4); - NewMod.Instance.Log.LogInfo($"Capturing screenshot at {System.IO.Path.GetFileName(filePath)}."); + yield return new WaitForEndOfFrame(); + var tex = ScreenCapture.CaptureScreenshotAsTexture(4); + File.WriteAllBytes(filePath, tex.EncodeToPNG()); + Object.Destroy(tex); + NewMod.Instance.Log.LogInfo($"Capturing screenshot at {Path.GetFileName(filePath)}."); yield return new WaitForSeconds(0.2f); @@ -829,10 +833,6 @@ public static IEnumerator StartFeignDeath(PlayerControl player) SoundManager.Instance.PlaySound(clip, false, 1f, null); - if (player.AmOwner) - { - HudManager.Instance.SetHudActive(player, player.Data.Role, false); - } yield return new WaitForSeconds(0.5f); var body = player.GetNearestDeadBody(15f); @@ -847,6 +847,11 @@ public static IEnumerator StartFeignDeath(PlayerControl player) Coroutines.Start(CoroutinesHelper.CoNotify("You are now feigning death.\nYou will be revived in 10 seconds if unreported.")); + if (player.AmOwner) + { + HudManager.Instance.SetHudActive(player, player.Data.Role, false); + } + float timer = 10f; while (timer > 0) { diff --git a/NewMod/Utilities/VisionaryUtilities.cs b/NewMod/Utilities/VisionaryUtilities.cs index ea73c2d..0f0c0ba 100644 --- a/NewMod/Utilities/VisionaryUtilities.cs +++ b/NewMod/Utilities/VisionaryUtilities.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.IO; using System.Collections; using UnityEngine; @@ -33,11 +32,21 @@ public static string ScreenshotDirectory { get { - string directory = OperatingSystem.IsAndroid() ? Environment.GetEnvironmentVariable("STAR_DATA_PATH") : Path.Combine(Application.persistentDataPath, "NewMod", "Screenshots"); - if (!Directory.Exists(directory)) + string basePath; + + if (OperatingSystem.IsAndroid()) + { + basePath = Environment.GetEnvironmentVariable("STAR_DATA_PATH")!; + } + else { - Directory.CreateDirectory(directory); + basePath = Application.persistentDataPath; } + + string directory = Path.Combine(basePath, "NewMod", "Screenshots"); + + Directory.CreateDirectory(directory); + return directory; } } @@ -151,25 +160,6 @@ public static IEnumerator ShowScreenshot(Sprite sprite, DateTime timestamp, floa _showing = false; } - // - /// Loads and displays a screenshot from a given file path. - /// If the file does not exist, no action is taken. - /// - /// The full path of the screenshot file to display. - /// The duration, in seconds, to display the screenshot. - /// An IEnumerator coroutine for handling display. - public static IEnumerator ShowScreenshotByPath(string filePath, float displayDuration) - { - if (!File.Exists(filePath)) yield break; - - byte[] data = File.ReadAllBytes(filePath); - Texture2D tex = new(2, 2); - tex.LoadImage(data); - Sprite screenshotSprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f)); - - yield return ShowScreenshot(screenshotSprite, File.GetCreationTime(filePath), displayDuration); - } - /// /// Deletes all screenshots from the Visionary screenshot directory. ///