diff --git a/NewMod/Buttons/Necromancer/ReviveButton.cs b/NewMod/Buttons/Necromancer/ReviveButton.cs index d35f4da..1f0cab5 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; @@ -53,22 +52,53 @@ 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 = 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; + var body = GetReviveTarget(); SoundManager.Instance.PlaySound(NewModAsset.ReviveSound?.LoadAsset(), false, 2f); @@ -79,8 +109,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 . /// @@ -90,33 +122,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.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; - - return true; - }); - - return canUse; - } } } 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/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 b32a61d..fce1eed 100644 --- a/NewMod/Buttons/RevivedKillButton.cs +++ b/NewMod/Buttons/RevivedKillButton.cs @@ -2,9 +2,7 @@ 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; @@ -20,18 +18,36 @@ public class RevivedKillButton : CustomActionButton public override MiraKeybind Keybind => MiraGlobalKeybinds.PrimaryAbility; 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() { return PlayerControl.LocalPlayer.GetClosestPlayer(true, Distance); } + public override bool IsTargetValid(PlayerControl target) { - return target.PlayerId != PlayerControl.LocalPlayer.PlayerId; + 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)); @@ -39,8 +55,7 @@ public override void SetOutline(bool active) public override bool CanUse() { - if (!NecromancerRole.RevivedPlayers.ContainsKey(PlayerControl.LocalPlayer.PlayerId)) return false; - return true; + return base.CanUse() && CanUseRevivedKillButton(); } protected override void OnClick() @@ -58,6 +73,8 @@ protected override void OnClick() ); NecromancerRole.RevivedPlayers.Remove(local.PlayerId); + ResetTarget(); + Button?.ToggleVisible(false); } } } \ No newline at end of file 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/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/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 9a5cb36..ae075e5 100644 --- a/NewMod/Components/ShadowZone.cs +++ b/NewMod/Components/ShadowZone.cs @@ -42,18 +42,37 @@ 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.ToggleHat(true); + lp.cosmetics.ToggleVisor(true); + lp.cosmetics.TogglePet(true); + 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,17 +80,22 @@ 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) { lp.cosmetics.SetPhantomRoleAlpha(0); + lp.cosmetics.ToggleHat(false); + lp.cosmetics.ToggleVisor(false); + lp.cosmetics.TogglePet(false); lp.cosmetics.nameText.gameObject.SetActive(false); } killButton.gameObject.SetActive(true); killButton.currentTarget = null; } + active = true; } @@ -81,13 +105,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,9 +124,13 @@ public void Update() else if (!inside && active) { Coroutines.Start(CoroutinesHelper.RemoveCameraEffect(cam, 0f)); + 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) @@ -105,8 +138,10 @@ public void Update() killButton.currentTarget.ToggleHighlight(false, RoleTeamTypes.Impostor); killButton.currentTarget = null; } + killButton.gameObject.SetActive(false); } + active = false; } } diff --git a/NewMod/Components/WraithCallerNpc.cs b/NewMod/Components/WraithCallerNpc.cs index 9df95e7..184fc89 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,172 @@ 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.SetNamePosition(new(0f, 0.8f, -0.5f)); + Visual.cosmetics.ToggleHat(false); + Visual.cosmetics.TogglePet(false); + Visual.cosmetics.ToggleVisor(false); + + var color = UnityEngine.Random.Range(0, 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/NewMod.csproj b/NewMod/NewMod.csproj index b21bb28..977fab2 100644 --- a/NewMod/NewMod.csproj +++ b/NewMod/NewMod.csproj @@ -12,8 +12,8 @@ - - + + 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/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 deleted file mode 100644 index 4bc3769..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/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))] 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/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/Resources/RoleIcons/ReviveIcon.png b/NewMod/Resources/RoleIcons/ReviveIcon.png new file mode 100644 index 0000000..355c4ac Binary files /dev/null and b/NewMod/Resources/RoleIcons/ReviveIcon.png differ diff --git a/NewMod/Resources/feigndeath.png b/NewMod/Resources/feigndeath.png new file mode 100644 index 0000000..01da701 Binary files /dev/null and b/NewMod/Resources/feigndeath.png differ diff --git a/NewMod/Roles/ImpostorRoles/Necromancer.cs b/NewMod/Roles/ImpostorRoles/Necromancer.cs index 921fe04..8bc4b0c 100644 --- a/NewMod/Roles/ImpostorRoles/Necromancer.cs +++ b/NewMod/Roles/ImpostorRoles/Necromancer.cs @@ -19,7 +19,7 @@ public class NecromancerRole : ImpostorRole, ICustomRole public RoleOptionsGroup RoleOptionsGroup { get; } = RoleOptionsGroup.Impostor; public CustomRoleConfiguration Configuration => 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..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(); @@ -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/Utils.cs b/NewMod/Utilities/Utils.cs index 3cec4ab..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 { @@ -41,7 +42,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 +103,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 +117,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; } /// @@ -401,6 +395,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) { @@ -804,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); @@ -822,6 +821,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, @@ -832,10 +833,6 @@ public static IEnumerator StartFeignDeath(PlayerControl player) SoundManager.Instance.PlaySound(clip, false, 1f, null); - if (player.AmOwner) - { - HudManager.Instance.SetHudActive(false); - } yield return new WaitForSeconds(0.5f); var body = player.GetNearestDeadBody(15f); @@ -850,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) { @@ -860,16 +862,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) diff --git a/NewMod/Utilities/VisionaryUtilities.cs b/NewMod/Utilities/VisionaryUtilities.cs index 7759bc8..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 = 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. /// 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 diff --git a/README.md b/README.md index 18af618..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 | --- @@ -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)