diff --git a/NewMod/Buttons/Overload/FinalButton.cs b/NewMod/Buttons/Overload/FinalButton.cs index 164512f..ad74388 100644 --- a/NewMod/Buttons/Overload/FinalButton.cs +++ b/NewMod/Buttons/Overload/FinalButton.cs @@ -1,3 +1,4 @@ +using MiraAPI.GameEnd; using MiraAPI.GameOptions; using MiraAPI.Hud; using MiraAPI.Keybinds; @@ -66,10 +67,7 @@ public override bool Enabled(RoleBehaviour role) /// protected override void OnClick() { - GameManager.Instance.RpcEndGame( - (GameOverReason)NewModEndReasons.OverloadWin, - false - ); + CustomGameOver.Trigger([PlayerControl.LocalPlayer.Data]); } } -} +} \ No newline at end of file diff --git a/NewMod/Components/WraithCallerNpc.cs b/NewMod/Components/WraithCallerNpc.cs index 184fc89..82de866 100644 --- a/NewMod/Components/WraithCallerNpc.cs +++ b/NewMod/Components/WraithCallerNpc.cs @@ -103,7 +103,7 @@ public IEnumerator CoMove() UpdateWalkAnimation(velocity); - if (AmongUsClient.Instance.AmHost && delta.magnitude <= 0.15f) + if (AmongUsClient.Instance.AmHost && delta.magnitude <= 0.01f) { body.velocity = Vector2.zero; UpdateWalkAnimation(Vector2.zero); @@ -126,6 +126,7 @@ public IEnumerator CoMove() UpdateWalkAnimation(Vector2.zero); Dispose(); } + [HideFromIl2Cpp] public void UpdateWalkAnimation(Vector2 velocity) { @@ -142,6 +143,7 @@ public void UpdateWalkAnimation(Vector2 velocity) { animations.PlayRunAnimation(); } + if (Visual.cosmetics.HasSkinLoaded() && !Visual.cosmetics.IsSkinPlayingRunAnim()) { Visual.cosmetics.AnimateSkinRun(); @@ -159,10 +161,12 @@ public void UpdateWalkAnimation(Vector2 velocity) } } } + var pos = Visual.transform.position; pos.z = pos.y / 1000f; Visual.transform.position = pos; } + [HideFromIl2Cpp] public void Dispose() { diff --git a/NewMod/CustomGameOvers/CustomGameEnd.cs b/NewMod/CustomGameOvers/CustomGameEnd.cs new file mode 100644 index 0000000..3d1a128 --- /dev/null +++ b/NewMod/CustomGameOvers/CustomGameEnd.cs @@ -0,0 +1,209 @@ +using System.Collections.Generic; +using System.Linq; +using HarmonyLib; +using MiraAPI.Events; +using MiraAPI.Events.Vanilla.Gameplay; +using MiraAPI.GameEnd; +using MiraAPI.GameOptions; +using NewMod.Options.Roles.EnergyThiefOptions; +using NewMod.Options.Roles.InjectorOptions; +using NewMod.Options.Roles.PulseBladeOptions; +using NewMod.Options.Roles.ShadeOptions; +using NewMod.Options.Roles.SpecialAgentOptions; +using NewMod.Options.Roles.WraithCallerOptions; +using NewMod.Roles.CrewmateRoles; +using NewMod.Roles.ImpostorRoles; +using NewMod.Roles.NeutralRoles; +using NewMod.Utilities; + +namespace NewMod.CustomGameOvers; + +public static class CustomEndGame +{ + internal static bool IsMatchReady { get; set; } + + [RegisterEvent] + public static void OnRoundStart(RoundStartEvent evt) + { + if (evt.TriggeredByIntro) + { + IsMatchReady = true; + } + } + + [RegisterEvent] + public static void OnGameEnd(GameEndEvent evt) + { + IsMatchReady = false; + } + + public static bool TryEndGame() + { + if (!IsMatchReady || !AmongUsClient.Instance.AmHost || !GameManager.Instance.ShouldCheckForGameEnd) + { + return false; + } + + var alivePlayers = PlayerControl.AllPlayerControls.ToArray() + .Where(player => !player.Data.IsDead && !player.Data.Disconnected) + .ToArray(); + + var wraithRequired = (int)OptionGroupSingleton.Instance.RequiredNPCsToSend; + var wraithCaller = alivePlayers.FirstOrDefault(player => + player.Data.Role is WraithCaller && + WraithCallerUtilities.GetKillsNPC(player.PlayerId) >= wraithRequired); + + if (wraithCaller) + { + CustomGameOver.Trigger([wraithCaller.Data]); + return true; + } + + var shadeRequired = (int)OptionGroupSingleton.Instance.RequiredKills; + var shade = alivePlayers.FirstOrDefault(player => + { + if (player.Data.Role is not Shade) + { + return false; + } + + Shade.ShadeKills.TryGetValue(player.PlayerId, out var kills); + return kills >= shadeRequired; + }); + + if (shade) + { + CustomGameOver.Trigger([shade.Data]); + return true; + } + + var pulseBladeOptions = OptionGroupSingleton.Instance; + + if (alivePlayers.Length <= pulseBladeOptions.PlayersThreshold) + { + var pulseBlade = alivePlayers.FirstOrDefault(player => + player.Data.Role is PulseBlade && + Utils.GetStrikes(player.PlayerId) >= pulseBladeOptions.RequiredStrikes); + + if (pulseBlade) + { + CustomGameOver.Trigger([pulseBlade.Data]); + return true; + } + } + + if (Tyrant.ApexThroneReady && Tyrant.ApexThroneOutcomeSet) + { + var tyrant = alivePlayers.FirstOrDefault(player => player.Data.Role is Tyrant); + + if (tyrant) + { + var winners = new List { tyrant.Data }; + + if (Tyrant.Outcome == Tyrant.ThroneOutcome.ChampionSideWin) + { + var champion = Utils.PlayerById(Tyrant.ChampionId); + + if (champion && !champion.Data.Disconnected) + { + winners.Add(champion.Data); + } + } + + CustomGameOver.Trigger(winners); + return true; + } + } + + var doubleAgent = alivePlayers.FirstOrDefault(player => + player.Data.Role is DoubleAgent && + player.AllTasksCompleted() && + Utils.IsSabotage()); + + if (doubleAgent) + { + CustomGameOver.Trigger([doubleAgent.Data]); + return true; + } + + var specialAgentRequired = + OptionGroupSingleton.Instance.RequiredMissionsToWin; + + var specialAgent = alivePlayers.FirstOrDefault(player => + player.Data.Role is SpecialAgent && + Utils.GetMissionSuccessCount(player.PlayerId) - + Utils.GetMissionFailureCount(player.PlayerId) >= specialAgentRequired); + + if (specialAgent) + { + CustomGameOver.Trigger([specialAgent.Data]); + return true; + } + + var prankster = alivePlayers.FirstOrDefault(player => + player.Data.Role is Prankster && + PranksterUtilities.GetReportCount(player.PlayerId) >= 2); + + if (prankster) + { + CustomGameOver.Trigger([prankster.Data]); + return true; + } + + var energyThiefRequired = + (int)OptionGroupSingleton.Instance.RequiredDrainCount; + + var energyThief = alivePlayers.FirstOrDefault(player => + player.Data.Role is EnergyThief && + Utils.GetDrainCount(player.PlayerId) >= energyThiefRequired); + + if (energyThief) + { + CustomGameOver.Trigger([energyThief.Data]); + return true; + } + + var injectorRequired = + (int)OptionGroupSingleton.Instance.RequiredInjectCount; + + var injector = alivePlayers.FirstOrDefault(player => + player.Data.Role is InjectorRole && + Utils.GetInjectedCount() >= injectorRequired); + + if (injector) + { + CustomGameOver.Trigger([injector.Data]); + return true; + } + + return false; + } +} + +[HarmonyPatch(typeof(GameManager), nameof(GameManager.StartGame))] +public static class GameStartPatch +{ + [HarmonyPrefix] + public static void Prefix() + { + CustomEndGame.IsMatchReady = false; + NewModEventHandler.ResetMatchState(); + } +} + +[HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlowNormal.CheckEndCriteria))] +public static class CustomEndGameCheckPatch +{ + [HarmonyPostfix] + public static void Postfix() + { + if (!CustomEndGame.IsMatchReady || !AmongUsClient.Instance.AmHost || + DestroyableSingleton.InstanceExists || MeetingHud.Instance || ExileController.Instance || + !GameManager.Instance.ShouldCheckForGameEnd) + { + return; + } + + CustomEndGame.TryEndGame(); + } +} \ No newline at end of file diff --git a/NewMod/DiscordStatus.cs b/NewMod/DiscordStatus.cs index a8dde5a..7b32c5c 100644 --- a/NewMod/DiscordStatus.cs +++ b/NewMod/DiscordStatus.cs @@ -1,4 +1,5 @@ // Inspired by: https://github.com/All-Of-Us-Mods/LaunchpadReloaded/blob/master/LaunchpadReloaded/Patches/Generic/DiscordManagerPatch.cs#L12 + using System; using Discord; using HarmonyLib; @@ -11,37 +12,6 @@ namespace NewMod [HarmonyPatch] public static class NewModDiscordPatch { - private static Discord.Discord discord; - public static ActivityManager activityManager; - - [HarmonyPrefix] - [HarmonyPatch(typeof(DiscordManager), nameof(DiscordManager.Start))] - public static bool StartPrefix(DiscordManager __instance) - { - if (Application.platform == RuntimePlatform.Android) return true; - - InitializeDiscord(__instance); - return false; - } - - private static void InitializeDiscord(DiscordManager __instance) - { - const long clientId = 1405946628115791933; - - discord = new Discord.Discord(clientId, (ulong)CreateFlags.Default); - activityManager = discord.GetActivityManager(); - - activityManager.RegisterSteam(945360U); - activityManager.add_OnActivityJoin((Action)__instance.HandleJoinRequest); - - SceneManager.add_sceneLoaded((Action)((scene, _) => - { - __instance.OnSceneChange(scene.name); - })); - __instance.presence = discord; - __instance.SetInMenus(); - } - [HarmonyPrefix] [HarmonyPatch(typeof(ActivityManager), nameof(ActivityManager.UpdateActivity))] public static void UpdateActivityPrefix([HarmonyArgument(0)] ref Activity activity) @@ -69,7 +39,8 @@ public static void UpdateActivityPrefix([HarmonyArgument(0)] ref Activity activi var miraVersion = MiraApiPlugin.Version; var platform = Application.platform; - activity.Details += $" | Lobby: {lobbyCode} | Max: {maxPlayers} | MiraAPI: {miraVersion} | {platform}"; + activity.Details += + $" | Lobby: {lobbyCode} | Max: {maxPlayers} | MiraAPI: {miraVersion} | {platform}"; } if (MeetingHud.Instance) @@ -83,4 +54,4 @@ public static void UpdateActivityPrefix([HarmonyArgument(0)] ref Activity activi } } } -} +} \ No newline at end of file diff --git a/NewMod/NewMod.cs b/NewMod/NewMod.cs index 312136a..20e3ec1 100644 --- a/NewMod/NewMod.cs +++ b/NewMod/NewMod.cs @@ -1,3 +1,4 @@ +global using MiraAPI.GameEnd; using System.Linq; using UnityEngine; using Object = UnityEngine.Object; @@ -43,11 +44,12 @@ public partial class NewMod : BasePlugin, IMiraPlugin public static ConfigEntry ShouldEnableBepInExConsole { get; set; } public ConfigFile GetConfigFile() => Config; public string OptionsTitleText => "NewMod"; + public override void Load() { Instance = this; AddComponent(); - ReactorCredits.Register("NewMod", "v1.2.9 Hotfix 3", true, ReactorCredits.AlwaysShow); + ReactorCredits.Register("NewMod", "v1.2.9 Hotfix 4", true, ReactorCredits.AlwaysShow); Harmony.PatchAll(); NewModEventHandler.RegisterEventsLogs(); @@ -56,10 +58,13 @@ public override void Load() Harmony.PatchAll(typeof(LaunchpadCompatibility)); Harmony.PatchAll(typeof(LaunchpadHackTextPatch)); } - ShouldEnableBepInExConsole = Config.Bind("NewMod", "Console", true, "Whether to enable BepInEx Console for debugging"); + + ShouldEnableBepInExConsole = + Config.Bind("NewMod", "Console", true, "Whether to enable BepInEx Console for debugging"); if (!ShouldEnableBepInExConsole.Value) ConsoleManager.DetachConsole(); - Instance.Log.LogMessage($"Loaded Successfully NewMod v{ModVersion} With MiraAPI Version : {MiraApiPlugin.Version}"); + Instance.Log.LogMessage( + $"Loaded Successfully NewMod v{ModVersion} With MiraAPI Version : {MiraApiPlugin.Version}"); } [HarmonyPatch(typeof(KeyboardJoystick), nameof(KeyboardJoystick.Update))] @@ -70,9 +75,11 @@ public static void Postfix(KeyboardJoystick __instance) InitializeKeyBinds(); } } + public static void InitializeKeyBinds() { - if (Input.GetKeyDown(KeyCode.F2) && PlayerControl.LocalPlayer.Data.IsDead && OptionGroupSingleton.Instance.AllowCams) + if (Input.GetKeyDown(KeyCode.F2) && PlayerControl.LocalPlayer.Data.IsDead && + OptionGroupSingleton.Instance.AllowCams) { var sys = Utils.FindSurveillanceConsole(); var mainCam = Camera.main; @@ -82,9 +89,11 @@ public static void InitializeKeyBinds() minigame.transform.localPosition = new Vector3(0f, 0f, -50f); minigame.Begin(null); } + if (Input.GetKeyDown(KeyCode.F3) && PlayerControl.LocalPlayer.Data.Role is NecromancerRole) { - var deadBodies = Helpers.GetNearestDeadBodies(PlayerControl.LocalPlayer.GetTruePosition(), 20f, Helpers.CreateFilter(Constants.NotShipMask)); + var deadBodies = Helpers.GetNearestDeadBodies(PlayerControl.LocalPlayer.GetTruePosition(), 20f, + Helpers.CreateFilter(Constants.NotShipMask)); if (deadBodies != null && deadBodies.Count > 0) { var randomIndex = Random.Range(0, deadBodies.Count); @@ -104,12 +113,14 @@ public static void OnBeforeMurder(BeforeMurderEvent evt) if (evt.Target != OverloadRole.chosenPrey) return; //TODO: Use the newest MiraAPI roles for button mapping - if (evt.Target.Data.Role is ICustomRole customRole && Utils.RoleToButtonsMap.TryGetValue(customRole.GetType(), out var buttonsType)) + if (evt.Target.Data.Role is ICustomRole customRole && + Utils.RoleToButtonsMap.TryGetValue(customRole.GetType(), out var buttonsType)) { OverloadRole.CachedButtons = [.. CustomButtonManager.Buttons.Where(b => buttonsType.Contains(b.GetType()))]; Instance.Log.LogMessage($"CachedButton: {buttonsType.GetType().Name}"); } } + [RegisterEvent] public static void OnAfterMurder(AfterMurderEvent evt) { @@ -119,7 +130,8 @@ public static void OnAfterMurder(AfterMurderEvent evt) if (target != OverloadRole.chosenPrey) return; - foreach (var pc in PlayerControl.AllPlayerControls.ToArray().Where(p => p.AmOwner && p.Data.Role is OverloadRole)) + foreach (var pc in PlayerControl.AllPlayerControls.ToArray() + .Where(p => p.AmOwner && p.Data.Role is OverloadRole)) { if (target.Data.Role is ICustomRole customRole) { @@ -140,14 +152,17 @@ public static void OnAfterMurder(AfterMurderEvent evt) pb.OnClick.AddListener((UnityAction)target.Data.Role.UseAbility); } } + OverloadRole.CachedButtons.Clear(); OverloadRole.AbsorbedAbilityCount++; OverloadRole.chosenPrey = null; - Coroutines.Start(CoroutinesHelper.CoNotify($"Charge {OverloadRole.AbsorbedAbilityCount}/{OptionGroupSingleton.Instance.NeededCharge}")); + Coroutines.Start(CoroutinesHelper.CoNotify( + $"Charge {OverloadRole.AbsorbedAbilityCount}/{OptionGroupSingleton.Instance.NeededCharge}")); if (OverloadRole.AbsorbedAbilityCount >= OptionGroupSingleton.Instance.NeededCharge) { - Coroutines.Start(CoroutinesHelper.CoNotify("Objective completed: Final Ability unlocked!")); + Coroutines.Start( + CoroutinesHelper.CoNotify("Objective completed: Final Ability unlocked!")); } else { @@ -162,8 +177,10 @@ public static void Postfix(TaskPanelBehaviour __instance, [HarmonyArgument(0)] s { if (__instance.taskText != null && PlayerControl.LocalPlayer.Data.IsDead) { - __instance.taskText.text += "\n" + (OptionGroupSingleton.Instance.AllowCams ? "Press F2 For Open Cams" : "You cannot open cams because the host has disabled this setting"); + __instance.taskText.text += "\n" + (OptionGroupSingleton.Instance.AllowCams + ? "Press F2 For Open Cams" + : "You cannot open cams because the host has disabled this setting"); } } } -} +} \ No newline at end of file diff --git a/NewMod/NewMod.csproj b/NewMod/NewMod.csproj index 977fab2..a29d520 100644 --- a/NewMod/NewMod.csproj +++ b/NewMod/NewMod.csproj @@ -5,7 +5,7 @@ NewMod is a mod for Among Us that introduces a variety of new roles, unique abilities CallofCreator net6.0 - latest + 12 embedded Debug;Release; @@ -13,12 +13,16 @@ - + + + + + diff --git a/NewMod/NewModEndReasons.cs b/NewMod/NewModEndReasons.cs deleted file mode 100644 index fea1f8e..0000000 --- a/NewMod/NewModEndReasons.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace NewMod -{ - public enum NewModEndReasons - { - EnergyThiefWin = 110, - DoubleAgentWin = 111, - PranksterWin = 112, - SpecialAgentWin = 113, - TheVisionaryWin = 114, - OverloadWin = 115, - EgoistWin = 116, - InjectorWin = 117, - PulseBladeWin = 118, - TyrantWin = 119, - WraithCallerWin = 120, - ShadeWin = 121 - } -} \ No newline at end of file diff --git a/NewMod/NewModEventHandler.cs b/NewMod/NewModEventHandler.cs index 1237e2c..c38959c 100644 --- a/NewMod/NewModEventHandler.cs +++ b/NewMod/NewModEventHandler.cs @@ -2,132 +2,137 @@ using System.Collections; using System.Collections.Generic; using System.Reflection; +using System.Linq; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Gameplay; -using MiraAPI.Events.Vanilla.Player; +using NewMod.Buttons.Revenant; +using NewMod.Components; +using NewMod.Modifiers; +using NewMod.Roles.CrewmateRoles; using NewMod.Roles.ImpostorRoles; using NewMod.Roles.NeutralRoles; using NewMod.Utilities; +using UnityEngine; -namespace NewMod +namespace NewMod; + +public static class NewModEventHandler { - public static class NewModEventHandler + public static void RegisterEventsLogs() { - public static void RegisterEventsLogs() + var type = typeof(MiraEventManager); + var field = type.GetField("EventWrappers", BindingFlags.NonPublic | BindingFlags.Static); + var wrappersObject = field.GetValue(null); + if (wrappersObject is not IDictionary wrappersByEvent || wrappersByEvent.Count == 0) + { + return; + } + + var builder = new System.Text.StringBuilder(); + builder.AppendLine("=== Registered NewMod Events ==="); + + foreach (DictionaryEntry entry in wrappersByEvent) { - var type = typeof(MiraEventManager); - var fld = type.GetField("EventWrappers", BindingFlags.NonPublic | BindingFlags.Static); - var dictObj = fld.GetValue(null); - if (dictObj is not IDictionary dict || dict.Count == 0) + var eventType = entry.Key as Type; + var lines = new List(); + + if (entry.Value is IEnumerable wrappers) { - return; + foreach (var wrapper in wrappers) + { + if (wrapper == null) continue; + + var wrapperType = wrapper.GetType(); + var eventHandlerProperty = + wrapperType.GetProperty("EventHandler", BindingFlags.Public | BindingFlags.Instance); + var priorityProperty = + wrapperType.GetProperty("Priority", BindingFlags.Public | BindingFlags.Instance); + var handler = eventHandlerProperty.GetValue(wrapper) as Delegate; + var priority = priorityProperty.GetValue(wrapper) as int? ?? 0; + var method = handler.Method; + + lines.Add($" [{priority}] {method.DeclaringType.FullName}.{method.Name}()"); + } } - var sb = new System.Text.StringBuilder(); - sb.AppendLine("=== Registered NewMod Events ==="); - foreach (DictionaryEntry entry in dict) + builder.AppendLine($"{eventType.FullName} (handlers: {lines.Count})"); + foreach (var line in lines) { - var eventType = entry.Key as Type; - var listObj = entry.Value; - int count = 0; - var lines = new List(); + builder.AppendLine(line); + } + } - if (listObj is IEnumerable wrappers) - { - foreach (var wrapper in wrappers) - { - if (wrapper == null) continue; - var wType = wrapper.GetType(); + NewMod.Instance.Log.LogInfo(builder.ToString()); + } - var ehProp = wType.GetProperty("EventHandler", BindingFlags.Public | BindingFlags.Instance); - var prProp = wType.GetProperty("Priority", BindingFlags.Public | BindingFlags.Instance); + public static void ResetMatchState() + { + Utils.ResetKillTracking(); + Utils.ResetDrainCount(); + Utils.ResetMissionSuccessCount(); + Utils.ResetMissionFailureCount(); + Utils.ResetInjections(); + Utils.ResetStrikeCount(); + Utils.waitingPlayers.Clear(); + Utils.savedPlayerRoles.Clear(); + Utils.MissionTimer.Clear(); + Utils.savedTasks.Clear(); - var del = ehProp.GetValue(wrapper) as Delegate; - var prio = prProp.GetValue(wrapper) as int? ?? 0; + PranksterUtilities.ResetReportCount(); + WraithCallerUtilities.ClearAll(); + Shade.ShadeKills.Clear(); + Revenant.ResetAllStates(); + NecromancerRole.RevivedPlayers.Clear(); - var method = del.Method; - var declType = method.DeclaringType.FullName; - var methodName = method.Name; + CoroutinesHelper.bodiesCreated.Clear(); + CoroutinesHelper.drainCount.Clear(); + PendingEffectManager.pendingEffects.Clear(); + DoomAwakening.killedPlayers.Clear(); - lines.Add($" [{prio}] {declType}.{methodName}()"); - count++; - } - } + StickyModifier.linkedPlayers.Clear(); + StickyModifier._IsActive = false; + FearPulseArea.AffectedPlayers.Clear(); + FearPulseArea._speedNotifShown.Clear(); + FearPulseArea._visionNotifShown.Clear(); + AegisUtilities.ActiveOwners.Clear(); - sb.AppendLine($"{eventType.FullName} (handlers: {count})"); - foreach (var l in lines) sb.AppendLine(l); + foreach (var shield in ShieldArea._active.ToArray()) + { + if (shield) + { + UnityEngine.Object.Destroy(shield.gameObject); } - NewMod.Instance.Log.LogInfo(sb.ToString()); } - [RegisterEvent] - public static void OnRoundStart(RoundStartEvent evt) - { - if (!evt.TriggeredByIntro) return; - - HudManager.Instance.Chat.enabled = false; - - 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) + ShieldArea._active.Clear(); + + Tyrant.ResetState(); + OverloadRole.ResetState(); + SpecialAgent.AssignedPlayer = null; + + Beacon.charges = 0; + Beacon.grantedFromTasks = 0; + Beacon.lastCompletedTasks = 0; + Beacon.cooldownUntil = 0f; + Beacon.pulseUntil = 0f; + } + + [RegisterEvent] + public static void OnRoundStart(RoundStartEvent evt) + { + if (!evt.TriggeredByIntro) { - 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"); + return; } + + HudManager.Instance.Chat.enabled = false; + VisionaryUtilities.DeleteAllScreenshots(); + } + + [RegisterEvent(100)] + public static void OnGameEnd(GameEndEvent evt) + { + ResetMatchState(); + VisionaryUtilities.DeleteAllScreenshots(); } -} +} \ No newline at end of file diff --git a/NewMod/NewModGameOver.cs b/NewMod/NewModGameOver.cs new file mode 100644 index 0000000..3ff0b82 --- /dev/null +++ b/NewMod/NewModGameOver.cs @@ -0,0 +1,203 @@ +using System.Linq; +using MiraAPI.Roles; +using MiraAPI.Utilities; +using NewMod.Roles.CrewmateRoles; +using NewMod.Roles.ImpostorRoles; +using NewMod.Roles.NeutralRoles; + +namespace NewMod; + +internal static class NewModGameOver +{ + public static bool CaptureWinnerIds(NetworkedPlayerInfo[] winners, out byte[] winnerIds) + { + if (winners.Length == 0) + { + winnerIds = []; + return false; + } + + winnerIds = winners.Select(player => player.PlayerId).Distinct().ToArray(); + return true; + } + + public static bool CaptureWinners(NetworkedPlayerInfo[] winners, out byte[] winnerIds) + where TRole : RoleBehaviour, ICustomRole + { + if (winners.Length == 0 || winners[0].Role is not TRole) + { + winnerIds = []; + return false; + } + + return CaptureWinnerIds(winners, out winnerIds); + } + + public static bool SetWinners(byte[] winnerIds) + { + EndGameResult.CachedWinners.Clear(); + + foreach (var playerId in winnerIds) + { + var player = GameData.Instance.GetPlayerById(playerId); + if (player != null) + { + EndGameResult.CachedWinners.Add(new CachedPlayerData(player)); + } + } + + return true; + } + + public static void SetPresentation(EndGameManager manager, string text) + where TRole : RoleBehaviour, ICustomRole + { + var color = CustomRoleSingleton.Instance.RoleColor; + manager.WinText.text = text; + manager.WinText.color = color; + manager.BackgroundBar.material.SetColor(ShaderID.Color, color); + } +} + +public class EnergyThiefGameOver : CustomGameOver +{ + private byte[] _winnerIds = []; + + public override bool VerifyCondition(PlayerControl playerControl, NetworkedPlayerInfo[] winners) => + NewModGameOver.CaptureWinners(winners, out _winnerIds); + + public override bool BeforeEndGameSetup(EndGameManager manager) => NewModGameOver.SetWinners(_winnerIds); + + public override void AfterEndGameSetup(EndGameManager manager) => + NewModGameOver.SetPresentation(manager, "Energy Thief Wins!"); +} + +public class DoubleAgentGameOver : CustomGameOver +{ + private byte[] _winnerIds = []; + + public override bool VerifyCondition(PlayerControl playerControl, NetworkedPlayerInfo[] winners) => + NewModGameOver.CaptureWinners(winners, out _winnerIds); + + public override bool BeforeEndGameSetup(EndGameManager manager) => NewModGameOver.SetWinners(_winnerIds); + + public override void AfterEndGameSetup(EndGameManager manager) => + NewModGameOver.SetPresentation(manager, "Double Agent Wins!"); +} + +public class PranksterGameOver : CustomGameOver +{ + private byte[] _winnerIds = []; + + public override bool VerifyCondition(PlayerControl playerControl, NetworkedPlayerInfo[] winners) => + NewModGameOver.CaptureWinners(winners, out _winnerIds); + + public override bool BeforeEndGameSetup(EndGameManager manager) => NewModGameOver.SetWinners(_winnerIds); + + public override void AfterEndGameSetup(EndGameManager manager) => + NewModGameOver.SetPresentation(manager, "Prankster Wins!"); +} + +public class SpecialAgentGameOver : CustomGameOver +{ + private byte[] _winnerIds = []; + + public override bool VerifyCondition(PlayerControl playerControl, NetworkedPlayerInfo[] winners) => + NewModGameOver.CaptureWinners(winners, out _winnerIds); + + public override bool BeforeEndGameSetup(EndGameManager manager) => NewModGameOver.SetWinners(_winnerIds); + + public override void AfterEndGameSetup(EndGameManager manager) => + NewModGameOver.SetPresentation(manager, "Special Agent Victory"); +} + +public class OverloadGameOver : CustomGameOver +{ + private byte[] _winnerIds = []; + + public override bool VerifyCondition(PlayerControl playerControl, NetworkedPlayerInfo[] winners) => + NewModGameOver.CaptureWinners(winners, out _winnerIds); + + public override bool BeforeEndGameSetup(EndGameManager manager) => NewModGameOver.SetWinners(_winnerIds); + + public override void AfterEndGameSetup(EndGameManager manager) => + NewModGameOver.SetPresentation(manager, "Overload Wins!"); +} + +public class EgoistGameOver : CustomGameOver +{ + private byte[] _winnerIds = []; + + public override bool VerifyCondition(PlayerControl playerControl, NetworkedPlayerInfo[] winners) => + NewModGameOver.CaptureWinners(winners, out _winnerIds); + + public override bool BeforeEndGameSetup(EndGameManager manager) => NewModGameOver.SetWinners(_winnerIds); + + public override void AfterEndGameSetup(EndGameManager manager) => + NewModGameOver.SetPresentation(manager, "Egoist Wins!"); +} + +public class InjectorGameOver : CustomGameOver +{ + private byte[] _winnerIds = []; + + public override bool VerifyCondition(PlayerControl playerControl, NetworkedPlayerInfo[] winners) => + NewModGameOver.CaptureWinners(winners, out _winnerIds); + + public override bool BeforeEndGameSetup(EndGameManager manager) => NewModGameOver.SetWinners(_winnerIds); + + public override void AfterEndGameSetup(EndGameManager manager) => + NewModGameOver.SetPresentation(manager, "Injector Victory"); +} + +public class PulseBladeGameOver : CustomGameOver +{ + private byte[] _winnerIds = []; + + public override bool VerifyCondition(PlayerControl playerControl, NetworkedPlayerInfo[] winners) => + NewModGameOver.CaptureWinners(winners, out _winnerIds); + + public override bool BeforeEndGameSetup(EndGameManager manager) => NewModGameOver.SetWinners(_winnerIds); + + public override void AfterEndGameSetup(EndGameManager manager) => + NewModGameOver.SetPresentation(manager, "PulseBlade Victory"); +} + +public class TyrantGameOver : CustomGameOver +{ + private byte[] _winnerIds = []; + + public override bool VerifyCondition(PlayerControl playerControl, NetworkedPlayerInfo[] winners) => + NewModGameOver.CaptureWinners(winners, out _winnerIds); + + public override bool BeforeEndGameSetup(EndGameManager manager) => NewModGameOver.SetWinners(_winnerIds); + + public override void AfterEndGameSetup(EndGameManager manager) => + NewModGameOver.SetPresentation(manager, "Tyrant Victory"); +} + +public class WraithCallerGameOver : CustomGameOver +{ + private byte[] _winnerIds = []; + + public override bool VerifyCondition(PlayerControl playerControl, NetworkedPlayerInfo[] winners) => + NewModGameOver.CaptureWinners(winners, out _winnerIds); + + public override bool BeforeEndGameSetup(EndGameManager manager) => NewModGameOver.SetWinners(_winnerIds); + + public override void AfterEndGameSetup(EndGameManager manager) => + NewModGameOver.SetPresentation(manager, "NPC Invasion Completed\nWraith Caller Wins!"); +} + +public class ShadeGameOver : CustomGameOver +{ + private byte[] _winnerIds = []; + + public override bool VerifyCondition(PlayerControl playerControl, NetworkedPlayerInfo[] winners) => + NewModGameOver.CaptureWinners(winners, out _winnerIds); + + public override bool BeforeEndGameSetup(EndGameManager manager) => NewModGameOver.SetWinners(_winnerIds); + + public override void AfterEndGameSetup(EndGameManager manager) => + NewModGameOver.SetPresentation(manager, "Darkness Consumes All"); +} \ No newline at end of file diff --git a/NewMod/Patches/EndGamePatch.cs b/NewMod/Patches/EndGamePatch.cs deleted file mode 100644 index 35e4e2a..0000000 --- a/NewMod/Patches/EndGamePatch.cs +++ /dev/null @@ -1,398 +0,0 @@ -using UnityEngine; -using HarmonyLib; -using System.Linq; -using MiraAPI.Events.Vanilla.Gameplay; -using MiraAPI.Roles; -using AmongUs.GameOptions; -using Object = UnityEngine.Object; -using NewMod.Roles.CrewmateRoles; -using NewMod.Roles.NeutralRoles; -using NewMod.Utilities; -using NewMod.Options.Roles.SpecialAgentOptions; -using MiraAPI.GameOptions; -using MiraAPI.Events; -using NewMod.Options.Roles.InjectorOptions; -using NewMod.Roles; -using System; -using NewMod.Roles.ImpostorRoles; -using NewMod.Options.Roles.PulseBladeOptions; -using MiraAPI.Utilities; -using NewMod.Options.Roles.EnergyThiefOptions; -using NewMod.Options.Roles.WraithCallerOptions; -using NewMod.Options.Roles.ShadeOptions; - -namespace NewMod.Patches -{ - public static class EndGamePatch - { - public static bool EndGameTriggered = false; - - [RegisterEvent] - public static void OnGameStart(RoundStartEvent evt) - { - EndGameTriggered = false; - EndGameResult.CachedWinners.Clear(); - } - - [RegisterEvent] - public static void OnGameEnd(GameEndEvent evt) - { - EndGameManager endGameManager = evt?.EndGameManager; - - foreach (var playerObj in endGameManager.GetComponentsInChildren()) - { - GameObject.Destroy(playerObj.gameObject); - } - - var winningPlayers = EndGameResult.CachedWinners.ToArray() - .OrderByDescending(p => !p.IsYou) - .ToList(); - int num = winningPlayers.Count; - - for (int i = 0; i < num; i++) - { - var playerData = winningPlayers[i]; - - int num2 = (i % 2 == 0 ? -1 : 1); - int num3 = (i + 1) / 2; - float num4 = (float)num3 / num; - float num5 = Mathf.Lerp(1f, 0.75f, num4); - float num6 = (i == 0) ? -8f : -1f; - - PoolablePlayer poolablePlayer = Object.Instantiate(endGameManager.PlayerPrefab, endGameManager.transform); - - float xPos = 1f * num2 * num3 * num5 * 0.9f; - float yPos = FloatRange.SpreadToEdges(-1.125f, 0f, num3, num) * 0.9f; - float zPos = (num6 + num3 * 0.01f) * 0.9f; - - poolablePlayer.transform.localPosition = new Vector3(xPos, yPos, zPos); - poolablePlayer.transform.localScale = Vector3.one * num5; - - if (playerData.IsDead) - { - poolablePlayer.SetBodyAsGhost(); - poolablePlayer.SetDeadFlipX(i % 2 == 0); - } - else - { - poolablePlayer.SetFlipX(i % 2 == 0); - } - - poolablePlayer.UpdateFromPlayerOutfit( - playerData.Outfit, - PlayerMaterial.MaskType.None, - playerData.IsDead, - true, - null, - false - ); - - string roleName = GetRoleName(playerData, out Color roleColor); - string playerNameWithRole = $"{playerData.PlayerName}\n{roleName}"; - - var nameText = poolablePlayer.cosmetics.nameText; - nameText.transform.localPosition = new Vector3(0f, -1.5f, -15f); - nameText.text = playerNameWithRole; - nameText.color = roleColor; - nameText.alignment = TMPro.TextAlignmentOptions.Center; - } - - string customWinText; - Color customWinColor; - - switch (EndGameResult.CachedGameOverReason) - { - case (GameOverReason)NewModEndReasons.EnergyThiefWin: - customWinText = "Energy Thief Win!"; - customWinColor = GetRoleColor(GetRoleType()); - endGameManager.BackgroundBar.material.SetColor("_Color", customWinColor); - break; - case (GameOverReason)NewModEndReasons.DoubleAgentWin: - customWinText = "Double Agent Win!"; - customWinColor = GetRoleColor(GetRoleType()); - endGameManager.BackgroundBar.material.SetColor("_Color", customWinColor); - break; - case (GameOverReason)NewModEndReasons.PranksterWin: - customWinText = "Prankster Win!"; - customWinColor = GetRoleColor(GetRoleType()); - endGameManager.BackgroundBar.material.SetColor("_Color", customWinColor); - break; - case (GameOverReason)NewModEndReasons.SpecialAgentWin: - customWinText = "Special Agent Victory"; - customWinColor = GetRoleColor(GetRoleType()); - endGameManager.BackgroundBar.material.SetColor("_Color", customWinColor); - break; - case (GameOverReason)NewModEndReasons.InjectorWin: - customWinText = "Injector Victory"; - customWinColor = GetRoleColor(GetRoleType()); - endGameManager.BackgroundBar.material.SetColor("_Color", customWinColor); - break; - case (GameOverReason)NewModEndReasons.PulseBladeWin: - customWinText = "PulseBlade Victory"; - customWinColor = GetRoleColor(GetRoleType()); - endGameManager.BackgroundBar.material.SetColor("_Color", customWinColor); - break; - case (GameOverReason)NewModEndReasons.TyrantWin: - customWinText = "Tyrant Victory"; - customWinColor = GetRoleColor(GetRoleType()); - endGameManager.BackgroundBar.material.SetColor("_Color", customWinColor); - break; - case (GameOverReason)NewModEndReasons.WraithCallerWin: - customWinText = "NPC Invasion Completed\nWraith Caller Win!"; - customWinColor = GetRoleColor(GetRoleType()); - endGameManager.BackgroundBar.material.SetColor("_Color", customWinColor); - break; - case (GameOverReason)NewModEndReasons.ShadeWin: - customWinText = "Darkness Consumes All"; - customWinColor = GetRoleColor(GetRoleType()); - endGameManager.BackgroundBar.material.SetColor("_Color", customWinColor); - break; - default: - customWinText = string.Empty; - customWinColor = Color.white; - break; - } - - if (!string.IsNullOrEmpty(customWinText)) - { - var customWinTextObject = Object.Instantiate(endGameManager.WinText.gameObject, endGameManager.transform); - customWinTextObject.transform.localPosition = new Vector3( - endGameManager.WinText.transform.position.x, - endGameManager.WinText.transform.position.y - 0.5f, - endGameManager.WinText.transform.position.z); - customWinTextObject.transform.localScale = new Vector3(0.7f, 0.7f, 1f); - - var customWinTextComponent = customWinTextObject.GetComponent(); - customWinTextComponent.text = customWinText; - customWinTextComponent.color = customWinColor; - customWinTextComponent.fontSize = 4f; - } - } - - public static string GetRoleName(CachedPlayerData playerData, out Color roleColor) - { - RoleTypes roleType = playerData.RoleWhenAlive; - RoleBehaviour roleBehaviour = RoleManager.Instance.GetRole(roleType); - - if (roleBehaviour != null) - { - if (CustomRoleManager.GetCustomRoleBehaviour(roleType, out var customRole)) - { - roleColor = customRole.RoleColor; - - if (customRole is INewModRole newmodRole) - { - return $"{newmodRole.RoleName}\n{Utils.GetFactionDisplay((INewModRole)customRole)}"; - } - - return customRole.RoleName; - } - else - { - roleColor = roleBehaviour.NameColor; - return roleBehaviour.NiceName; - } - } - else - { - roleColor = Color.white; - return null; - } - } - - private static RoleTypes GetRoleType() where T : ICustomRole - { - ushort roleId = RoleId.Get(); - return (RoleTypes)roleId; - } - - private static Color GetRoleColor(RoleTypes roleType) - { - RoleBehaviour roleBehaviour = RoleManager.Instance.GetRole(roleType); - - if (roleBehaviour != null) - { - if (CustomRoleManager.GetCustomRoleBehaviour(roleType, out var customRole)) - { - return customRole.RoleColor; - } - else - { - return roleBehaviour.NameColor; - } - } - else - { - return Color.white; - } - } - - public static bool EndCustomGame(GameOverReason reason, Action winners = null) - { - if (EndGameTriggered) return true; - if (EndGameResult.CachedWinners.Count > 0) return true; - - EndGameTriggered = true; - - EndGameResult.CachedWinners.Clear(); - winners?.Invoke(); - - GameManager.Instance.RpcEndGame(reason, false); - return true; - } - } - - [HarmonyPatch(typeof(LogicGameFlowNormal), nameof(LogicGameFlowNormal.CheckEndCriteria))] - public static class CheckGameEndPatch - { - public static bool Prefix(ShipStatus __instance) - { - if (DestroyableSingleton.InstanceExists) return true; - if (!AmongUsClient.Instance.AmHost) return true; - if (Time.timeSinceLevelLoad < 2f) return true; - if (EndGamePatch.EndGameTriggered) return false; - - if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.WraithCallerWin)) return false; - if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.ShadeWin)) return false; - if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.PulseBladeWin)) return false; - if (CheckForEndGameFaction(__instance, (GameOverReason)NewModEndReasons.TyrantWin)) return false; - if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.DoubleAgentWin)) return false; - if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.SpecialAgentWin)) return false; - if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.PranksterWin, 3)) return false; - if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.EnergyThiefWin)) return false; - if (CheckEndGameForRole(__instance, (GameOverReason)NewModEndReasons.InjectorWin)) return false; - - return true; - } - - public static bool CheckForEndGameFaction(ShipStatus __instance, GameOverReason winReason, int maxCount = 1) where TFaction : INewModRole - { - var players = PlayerControl.AllPlayerControls.ToArray() - .Where(p => p.Data.Role is TFaction) - .Take(maxCount) - .ToList(); - - foreach (var player in players) - { - bool shouldEndGame = false; - Action extraWinners = null; - - if (typeof(TFaction) == typeof(PulseBlade)) - { - var opts = OptionGroupSingleton.Instance; - float requiredStrikes = opts.RequiredStrikes; - float playersThreshold = opts.PlayersThreshold; - - var alives = Helpers.GetAlivePlayers(); - - if (alives.Count >= playersThreshold) continue; - - int strikes = Utils.GetStrikes(player.PlayerId); - if (strikes >= requiredStrikes) - { - shouldEndGame = true; - } - } - - if (typeof(TFaction) == typeof(Tyrant)) - { - if (Tyrant.ApexThroneReady && Tyrant.ApexThroneOutcomeSet) - { - shouldEndGame = true; - - extraWinners = () => - { - var tyrantRole = player.Data.Role as Tyrant; - byte champId = tyrantRole.GetChampion(); - var champion = Utils.PlayerById(champId); - - bool championWin = Tyrant.Outcome == Tyrant.ThroneOutcome.ChampionSideWin; - - if (champion && championWin) - { - EndGameResult.CachedWinners.Add(new(champion.Data)); - } - }; - } - } - - if (typeof(TFaction) == typeof(WraithCaller)) - { - int required = (int)OptionGroupSingleton.Instance.RequiredNPCsToSend; - int current = WraithCallerUtilities.GetKillsNPC(player.PlayerId); - shouldEndGame = current >= required; - } - - if (typeof(TFaction) == typeof(Shade)) - { - Shade.ShadeKills.TryGetValue(player.PlayerId, out var count); - int required = (int)OptionGroupSingleton.Instance.RequiredKills; - shouldEndGame = count >= required; - } - - if (shouldEndGame) - { - return EndGamePatch.EndCustomGame(winReason, extraWinners); - } - } - - return false; - } - - public static bool CheckEndGameForRole(ShipStatus __instance, GameOverReason winReason, int maxCount = 1) where T : RoleBehaviour - { - var rolePlayers = PlayerControl.AllPlayerControls.ToArray() - .Where(p => p.Data.Role is T) - .Take(maxCount) - .ToList(); - - foreach (var player in rolePlayers) - { - bool shouldEndGame = false; - - if (typeof(T) == typeof(DoubleAgent)) - { - bool tasksCompleted = player.AllTasksCompleted(); - bool isSabotageActive = Utils.IsSabotage(); - shouldEndGame = tasksCompleted && isSabotageActive; - } - - if (typeof(T) == typeof(EnergyThief)) - { - int drainCount = Utils.GetDrainCount(player.PlayerId); - int requiredDrainCount = (int)OptionGroupSingleton.Instance.RequiredDrainCount; - shouldEndGame = drainCount >= requiredDrainCount; - } - - if (typeof(T) == typeof(Prankster)) - { - int WinReportCount = 2; - int currentReportCount = PranksterUtilities.GetReportCount(player.PlayerId); - shouldEndGame = currentReportCount >= WinReportCount; - } - - if (typeof(T) == typeof(SpecialAgent)) - { - int missionSuccessCount = Utils.GetMissionSuccessCount(player.PlayerId); - int missionFailureCount = Utils.GetMissionFailureCount(player.PlayerId); - int netScore = missionSuccessCount - missionFailureCount; - shouldEndGame = netScore >= OptionGroupSingleton.Instance.RequiredMissionsToWin; - } - - if (typeof(T) == typeof(InjectorRole)) - { - int injectedCount = Utils.GetInjectedCount(); - int required = (int)OptionGroupSingleton.Instance.RequiredInjectCount; - shouldEndGame = injectedCount >= required; - } - - if (shouldEndGame) - { - return EndGamePatch.EndCustomGame(winReason); - } - } - - return false; - } - } -} \ No newline at end of file diff --git a/NewMod/Patches/GameStartPatch.cs b/NewMod/Patches/GameStartPatch.cs new file mode 100644 index 0000000..cab6a1c --- /dev/null +++ b/NewMod/Patches/GameStartPatch.cs @@ -0,0 +1,17 @@ +using HarmonyLib; + +namespace NewMod.Patches; + +public static class GameStartPatch +{ + // Thanks to: https://github.com/AU-Avengers/TOU-Mira/blob/main/TownOfUs/Patches/CancelCountdownStartPatches.cs#L120 + + [HarmonyPatch(typeof(GameStartManager), nameof(GameStartManager.ResetStartState))] + public static void Prefix(GameStartManager __instance) + { + if (__instance.startState == GameStartManager.StartingStates.Countdown) + { + GameManager.Instance.LogicOptions.SyncOptions(); + } + } +} \ No newline at end of file diff --git a/NewMod/Patches/RolePatch.cs b/NewMod/Patches/RolePatch.cs index 3730d0e..a908ed9 100644 --- a/NewMod/Patches/RolePatch.cs +++ b/NewMod/Patches/RolePatch.cs @@ -42,7 +42,8 @@ private static IEnumerator CoAdjustNeutrals() var allPlayers = allInfos.Select(p => p.Object).ToList(); Logger.Instance.LogMessage("-------------- NEUTRAL ADJUST: START --------------"); - Logger.Instance.LogMessage($"Players={allPlayers.Count}, TotalNeutrals={opts.TotalNeutrals} target={target}, KeepCrewMajority={opts.KeepCrewMajority}, PreferVariety={opts.PreferVariety}"); + Logger.Instance.LogMessage( + $"Players={allPlayers.Count}, TotalNeutrals={opts.TotalNeutrals} target={target}, KeepCrewMajority={opts.KeepCrewMajority}, PreferVariety={opts.PreferVariety}"); var neutrals = allPlayers .Where(pc => @@ -66,7 +67,8 @@ private static IEnumerator CoAdjustNeutrals() int maxAllowed = Math.Max(0, (int)Math.Floor((crewCount - 1) / 2.0)); int before = target; target = Math.Min(target, maxAllowed); - Logger.Instance.LogMessage($"KeepCrewMajority applied -> crewCount={crewCount}, maxNeutrals={maxAllowed}, adjustedTarget={target} (was {before})"); + Logger.Instance.LogMessage( + $"KeepCrewMajority applied -> crewCount={crewCount}, maxNeutrals={maxAllowed}, adjustedTarget={target} (was {before})"); } int have = neutrals.Count; @@ -171,6 +173,7 @@ private static IEnumerator CoAdjustNeutrals() c.Left--; ordered[i] = c; } + candidates = ordered; } @@ -187,7 +190,11 @@ private static IEnumerator CoAdjustNeutrals() foreach (var c in available) { acc += c.Weight; - if (rnum <= acc) { chosen = c; break; } + if (rnum <= acc) + { + chosen = c; + break; + } } picks.Add(chosen.Role); @@ -228,11 +235,13 @@ public class Candidate } } // Thanks to:https://github.com/AU-Avengers/TOU-Mira/blob/main/TownOfUs/Patches/RoleManagerPatches.cs#L1070 + [HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CoSetRole))] public static class CoSetRoleOverridePatch { [HarmonyPrefix] - public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] RoleTypes role, [HarmonyArgument(1)] bool canOverrideRole) + public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] RoleTypes role, + [HarmonyArgument(1)] bool canOverrideRole) { if (canOverrideRole) { @@ -240,4 +249,4 @@ public static void Prefix(PlayerControl __instance, [HarmonyArgument(0)] RoleTyp } } } -} +} \ No newline at end of file diff --git a/NewMod/Roles/CrewmateRoles/Aegis.cs b/NewMod/Roles/CrewmateRoles/Aegis.cs index 3b7d9d5..c103c0a 100644 --- a/NewMod/Roles/CrewmateRoles/Aegis.cs +++ b/NewMod/Roles/CrewmateRoles/Aegis.cs @@ -23,6 +23,7 @@ public class Aegis : CrewmateRole, INewModRole public Color RoleColor => new(0.227f, 0.651f, 1f); public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; public NewModFaction Faction => NewModFaction.Sentinel; + public CustomRoleConfiguration Configuration => new(this) { AffectedByLightOnAirship = true, @@ -54,20 +55,18 @@ public StringBuilder SetTabText() tab.AppendLine(); tab.AppendLine($"Mode: {mode}"); - tab.AppendLine($"Radius: {radius:F1}u • Duration: {dur:F0}s"); - tab.AppendLine($"Cooldown: {cd:F0}s • Charges: {uses}"); + tab.AppendLine( + $"Radius: {radius:F1}u • Duration: {dur:F0}s"); + tab.AppendLine( + $"Cooldown: {cd:F0}s • Charges: {uses}"); tab.AppendLine(); - tab.AppendLine("Tip: Place wards on choke points or common kill paths."); + tab.AppendLine( + "Tip: Place wards on choke points or common kill paths."); return tab; } - public override bool DidWin(GameOverReason gameOverReason) - { - return gameOverReason is GameOverReason.CrewmatesByTask or GameOverReason.CrewmatesByVote; - } - [RegisterEvent] public static void OnAnyButtonClick(MiraButtonClickEvent evt) { @@ -90,8 +89,9 @@ public static void OnAnyButtonClick(MiraButtonClickEvent evt) NewMod.Instance.Log.LogError("Role Ability Canceled"); Coroutines.Start(CoroutinesHelper.CoNotify( - "Aegis blocks your ability here")); + "Aegis blocks your ability here")); } + [RegisterEvent] public static void OnBeforeMurder(BeforeMurderEvent evt) { @@ -107,16 +107,18 @@ public static void OnBeforeMurder(BeforeMurderEvent evt) Coroutines.Start(CoroutinesHelper.CoNotify( "Aegis blocks your kill here")); } + foreach (var area in ShieldArea.AreasAt(evt.Target.GetTruePosition())) { var aegis = Utils.PlayerById(area.ownerId); if (aegis.AmOwner) { Coroutines.Start(CoroutinesHelper.CoNotify( - $"Aegis Ward Alert: Kill attempt blocked inside your ward!")); + $"Aegis Ward Alert: Kill attempt blocked inside your ward!")); } } } + [RegisterEvent] public static void OnAfterMurder(AfterMurderEvent evt) { @@ -142,9 +144,10 @@ public static void OnAfterMurder(AfterMurderEvent evt) Coroutines.Start(CoroutinesHelper.CoNotify( $"Aegis Ward Alert: A player was killed inside your ward by {killerName}!")); } + break; } } } } -} +} \ No newline at end of file diff --git a/NewMod/Roles/CrewmateRoles/Beacon.cs b/NewMod/Roles/CrewmateRoles/Beacon.cs index ec3a628..01d3015 100644 --- a/NewMod/Roles/CrewmateRoles/Beacon.cs +++ b/NewMod/Roles/CrewmateRoles/Beacon.cs @@ -20,10 +20,14 @@ public class Beacon : CrewmateRole, INewModRole { public string RoleName => "Beacon"; public string RoleDescription => "Scan. Locate. Coordinate."; - public string RoleLongDescription => "Send out a map-wide pulse that briefly reveals the position of all players."; + + public string RoleLongDescription => + "Send out a map-wide pulse that briefly reveals the position of all players."; + public Color RoleColor => new(0.494f, 0.341f, 0.761f); public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; public NewModFaction Faction => NewModFaction.Sentinel; + public CustomRoleConfiguration Configuration => new(this) { AffectedByLightOnAirship = true, @@ -50,17 +54,17 @@ public StringBuilder SetTabText() tab.AppendLine($"Recon Support"); tab.AppendLine(); - tab.AppendLine($"Charges: {chargesFromTasks} / {maxCharges} (+1 per {taskPerCh} tasks)"); - tab.AppendLine($"Pulse Duration: {pulseDur:F0}s • Cooldown: {cd:F0}s"); + tab.AppendLine( + $"Charges: {chargesFromTasks} / {maxCharges} (+1 per {taskPerCh} tasks)"); + tab.AppendLine( + $"Pulse Duration: {pulseDur:F0}s • Cooldown: {cd:F0}s"); tab.AppendLine(); - tab.AppendLine("Tip: Use pulses after lights or suspected kills to catch rotations."); + tab.AppendLine( + "Tip: Use pulses after lights or suspected kills to catch rotations."); return tab; } - public override bool DidWin(GameOverReason gameOverReason) - { - return gameOverReason is GameOverReason.CrewmatesByVote or GameOverReason.CrewmatesByTask; - } + public static int charges; public static int grantedFromTasks; public static int lastCompletedTasks; @@ -74,12 +78,14 @@ public static void OnRoundStart(RoundStartEvent evt) cooldownUntil = 0f; charges = (int)OptionGroupSingleton.Instance.MaxCharges; } + [RegisterEvent] public static void OnTaskComplete(CompleteTaskEvent evt) { if (PlayerControl.LocalPlayer.Data.Role is not Beacon) return; UpdateChargesFromTasks(); } + public static void UpdateChargesFromTasks() { var settings = OptionGroupSingleton.Instance; @@ -102,13 +108,15 @@ public static void UpdateChargesFromTasks() Rpc.Instance.Send(new BeaconPulseRpc.Data(settings.PulseDuration)); } } + public static int GetCompletedTasks() { var lp = PlayerControl.LocalPlayer; int done = 0; foreach (var t in lp.myTasks) - if (t && t.IsComplete) done++; + if (t && t.IsComplete) + done++; return done; } } -} +} \ No newline at end of file diff --git a/NewMod/Roles/CrewmateRoles/DoubleAgent.cs b/NewMod/Roles/CrewmateRoles/DoubleAgent.cs index f31c0da..7acff1b 100644 --- a/NewMod/Roles/CrewmateRoles/DoubleAgent.cs +++ b/NewMod/Roles/CrewmateRoles/DoubleAgent.cs @@ -1,6 +1,7 @@ using MiraAPI.Roles; using UnityEngine; using MiraAPI.Utilities.Assets; +using MiraAPI.GameEnd; namespace NewMod.Roles.CrewmateRoles; @@ -8,10 +9,14 @@ public class DoubleAgent : CrewmateRole, ICustomRole { public string RoleName => "Double Agent"; public string RoleDescription => "Mimic. Mislead. Win"; - public string RoleLongDescription => $"A Crewmate posing as an Impostor: You can't kill or vent, but you can sabotage and confuse the real Impostors. Complete all tasks and sabotage to win\n\nTeam: {Team}."; + + public string RoleLongDescription => + $"A Crewmate posing as an Impostor: You can't kill or vent, but you can sabotage and confuse the real Impostors. Complete all tasks and sabotage to win\n\nTeam: {Team}."; + public Color RoleColor => Palette.ImpostorRed; public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; public RoleOptionsGroup RoleOptionsGroup { get; } = RoleOptionsGroup.Crewmate; + public CustomRoleConfiguration Configuration => new(this) { MaxRoleCount = 1, @@ -30,6 +35,6 @@ public class DoubleAgent : CrewmateRole, ICustomRole public override bool DidWin(GameOverReason gameOverReason) { - return gameOverReason == (GameOverReason)NewModEndReasons.DoubleAgentWin; + return gameOverReason == CustomGameOver.GameOverReason(); } } \ No newline at end of file diff --git a/NewMod/Roles/CrewmateRoles/Specialist.cs b/NewMod/Roles/CrewmateRoles/Specialist.cs index c43cb92..f47b4ec 100644 --- a/NewMod/Roles/CrewmateRoles/Specialist.cs +++ b/NewMod/Roles/CrewmateRoles/Specialist.cs @@ -19,6 +19,7 @@ public class Specialist : CrewmateRole, ICustomRole public Color RoleColor => new(0.0f, 0.8f, 1.0f, 1f); public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; public RoleOptionsGroup RoleOptionsGroup { get; } = RoleOptionsGroup.Crewmate; + public CustomRoleConfiguration Configuration => new(this) { MaxRoleCount = 1, @@ -34,6 +35,7 @@ public class Specialist : CrewmateRole, ICustomRole CanModifyChance = true, RoleHintType = RoleHintType.RoleTab }; + [RegisterEvent] public static void OnTaskComplete(CompleteTaskEvent evt) { @@ -47,8 +49,9 @@ public static void OnTaskComplete(CompleteTaskEvent evt) var target = Utils.GetRandomPlayer(p => !p.Data.IsDead && !p.Data.Disconnected && p != specialist); if (target != null) { - Utils.RpcRandomDrainActions(specialist, target); - Helpers.CreateAndShowNotification($"Energy Drain activated on {target.Data.PlayerName}!",Color.green); + Utils.RpcRandomDrainActions(specialist, target); + Helpers.CreateAndShowNotification($"Energy Drain activated on {target.Data.PlayerName}!", + Color.green); } }, () => @@ -57,8 +60,10 @@ public static void OnTaskComplete(CompleteTaskEvent evt) var player = Utils.PlayerById(closestBody.ParentId); if (closestBody != null) { - Utils.HandleRevive(specialist, closestBody.ParentId, AmongUs.GameOptions.RoleTypes.Crewmate, closestBody.transform.position.x, closestBody.transform.position.y); - Helpers.CreateAndShowNotification($"Player {player.Data.PlayerName} has been revived.", Color.green); + Utils.HandleRevive(specialist, closestBody.ParentId, AmongUs.GameOptions.RoleTypes.Crewmate, + closestBody.transform.position.x, closestBody.transform.position.y); + Helpers.CreateAndShowNotification($"Player {player.Data.PlayerName} has been revived.", + Color.green); } }, () => @@ -71,8 +76,8 @@ public static void OnTaskComplete(CompleteTaskEvent evt) var randPlayer = Utils.GetRandomPlayer(p => !p.Data.IsDead && !p.Data.Disconnected); if (randPlayer != null && randPlayer.Data.Role is not ICustomRole) { - var role = randPlayer.Data.Role; - role.UseAbility(); + var role = randPlayer.Data.Role; + role.UseAbility(); } }, () => @@ -86,11 +91,8 @@ public static void OnTaskComplete(CompleteTaskEvent evt) { return; } + int randomIndex = UnityEngine.Random.Range(0, abilityAction.Count); abilityAction[randomIndex].Invoke(); } - public override bool DidWin(GameOverReason gameOverReason) - { - return gameOverReason == GameOverReason.CrewmatesByTask; - } -} +} \ No newline at end of file diff --git a/NewMod/Roles/CrewmateRoles/TheVisionary.cs b/NewMod/Roles/CrewmateRoles/TheVisionary.cs index de9802d..0ec8d53 100644 --- a/NewMod/Roles/CrewmateRoles/TheVisionary.cs +++ b/NewMod/Roles/CrewmateRoles/TheVisionary.cs @@ -12,6 +12,7 @@ public class TheVisionary : CrewmateRole, ICustomRole public Color RoleColor => new(0.75f, 0.5f, 1.0f); public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate; public RoleOptionsGroup RoleOptionGroup { get; } = RoleOptionsGroup.Crewmate; + public CustomRoleConfiguration Configuration => new(this) { DefaultRoleCount = 2, @@ -27,8 +28,4 @@ public class TheVisionary : CrewmateRole, ICustomRole CanModifyChance = true, RoleHintType = RoleHintType.RoleTab }; - public override bool DidWin(GameOverReason gameOverReason) - { - return gameOverReason == (GameOverReason)NewModEndReasons.TheVisionaryWin; - } } \ No newline at end of file diff --git a/NewMod/Roles/ImpostorRoles/Edgeveil.cs b/NewMod/Roles/ImpostorRoles/Edgeveil.cs index 0d41d53..f6bc876 100644 --- a/NewMod/Roles/ImpostorRoles/Edgeveil.cs +++ b/NewMod/Roles/ImpostorRoles/Edgeveil.cs @@ -11,10 +11,14 @@ public class Edgeveil : ImpostorRole, INewModRole { public string RoleName => "Edgeveil"; public string RoleDescription => "Draw. Cleave. Sheathe."; - public string RoleLongDescription => "Perform a fast iaijutsu slash in a short cone. Anyone caught in the arc is killed."; + + public string RoleLongDescription => + "Perform a fast iaijutsu slash in a short cone. Anyone caught in the arc is killed."; + public Color RoleColor => new(0.90f, 0.20f, 0.35f); public ModdedRoleTeams Team => ModdedRoleTeams.Impostor; public NewModFaction Faction => NewModFaction.Apex; + public CustomRoleConfiguration Configuration => new(this) { AffectedByLightOnAirship = false, @@ -24,9 +28,5 @@ public class Edgeveil : ImpostorRole, INewModRole TasksCountForProgress = false, Icon = NewModAsset.SlashIcon }; - public override bool DidWin(GameOverReason gameOverReason) - { - return gameOverReason is GameOverReason.ImpostorsByKill or GameOverReason.ImpostorsBySabotage; - } } -} +} \ No newline at end of file diff --git a/NewMod/Roles/ImpostorRoles/Necromancer.cs b/NewMod/Roles/ImpostorRoles/Necromancer.cs index 8bc4b0c..fffc1bc 100644 --- a/NewMod/Roles/ImpostorRoles/Necromancer.cs +++ b/NewMod/Roles/ImpostorRoles/Necromancer.cs @@ -6,6 +6,7 @@ using NewMod.Options; using UnityEngine; + namespace NewMod.Roles.ImpostorRoles; public class NecromancerRole : ImpostorRole, ICustomRole @@ -13,34 +14,24 @@ public class NecromancerRole : ImpostorRole, ICustomRole public static Dictionary RevivedPlayers = new(); public string RoleName => "Necromancer"; public string RoleDescription => "You can revive dead players who weren't killed by you"; - public string RoleLongDescription => "As the Necromancer, you possess a unique and powerful ability: the power to bring one dead player back to life. However,\nyou can only revive someone who wasn't killed by you"; + + public string RoleLongDescription => + "As the Necromancer, you possess a unique and powerful ability: the power to bring one dead player back to life. However,\nyou can only revive someone who wasn't killed by you"; + public Color RoleColor => Palette.AcceptedGreen.FindAlternateColor(); public ModdedRoleTeams Team => ModdedRoleTeams.Impostor; public RoleOptionsGroup RoleOptionsGroup { get; } = RoleOptionsGroup.Impostor; + public CustomRoleConfiguration Configuration => new(this) { Icon = NewModAsset.ReviveIcon, OptionsScreenshot = NewModAsset.Banner, MaxRoleCount = 3, }; + public TeamIntroConfiguration TeamConfiguration => new() { IntroTeamDescription = RoleDescription, IntroTeamColor = RoleColor }; - public override bool DidWin(GameOverReason reason) - { - if (reason == (GameOverReason)NewModEndReasons.TyrantWin || - reason == (GameOverReason)NewModEndReasons.ShadeWin || - reason == (GameOverReason)NewModEndReasons.WraithCallerWin || - reason == (GameOverReason)NewModEndReasons.SpecialAgentWin || - reason == (GameOverReason)NewModEndReasons.PranksterWin || - reason == (GameOverReason)NewModEndReasons.EnergyThiefWin || - reason == (GameOverReason)NewModEndReasons.InjectorWin || - reason == (GameOverReason)NewModEndReasons.DoubleAgentWin) - { - return false; - } - return true; - } -} +} \ No newline at end of file diff --git a/NewMod/Roles/ImpostorRoles/PulseBlade.cs b/NewMod/Roles/ImpostorRoles/PulseBlade.cs index 7c78180..68510eb 100644 --- a/NewMod/Roles/ImpostorRoles/PulseBlade.cs +++ b/NewMod/Roles/ImpostorRoles/PulseBlade.cs @@ -1,5 +1,6 @@ using System.Text; using Il2CppInterop.Runtime.Attributes; +using MiraAPI.GameEnd; using MiraAPI.GameOptions; using MiraAPI.Roles; using MiraAPI.Utilities; @@ -13,10 +14,14 @@ public class PulseBlade : ImpostorRole, INewModRole { public string RoleName => "PulseBlade"; public string RoleDescription => "Dash. Strike. Clean."; - public string RoleLongDescription => "Dash to eliminate a target with precision. Victim’s body disappears temporarily"; + + public string RoleLongDescription => + "Dash to eliminate a target with precision. Victim’s body disappears temporarily"; + public Color RoleColor => new(1f, 0.25f, 0.25f); public ModdedRoleTeams Team => ModdedRoleTeams.Impostor; public NewModFaction Faction => NewModFaction.Apex; + public CustomRoleConfiguration Configuration => new(this) { AffectedByLightOnAirship = false, @@ -26,6 +31,7 @@ public class PulseBlade : ImpostorRole, INewModRole TasksCountForProgress = false, Icon = NewModAsset.StrikeIcon }; + [HideFromIl2Cpp] public StringBuilder SetTabText() { @@ -35,50 +41,45 @@ public StringBuilder SetTabText() int threshold = (int)OptionGroupSingleton.Instance.PlayersThreshold; int req = (int)OptionGroupSingleton.Instance.RequiredStrikes; - tabText.AppendLine($"Warning: If your target is far beyond strike range and you strike, you will lose one use."); + tabText.AppendLine( + $"Warning: If your target is far beyond strike range and you strike, you will lose one use."); tabText.AppendLine("\n"); - tabText.AppendLine($"Win: {strikes}/{req} strikes"); + tabText.AppendLine( + $"Win: {strikes}/{req} strikes"); if (strikes >= req) { if (alive <= threshold) { - tabText.AppendLine($"Condition met, players ≤ {threshold}. Victory will trigger."); + tabText.AppendLine( + $"Condition met, players ≤ {threshold}. Victory will trigger."); } else { - tabText.AppendLine($"Armed: stay alive until players ≤ {threshold} to win."); + tabText.AppendLine( + $"Armed: stay alive until players ≤ {threshold} to win."); } } else { int left = req - strikes; - tabText.AppendLine($"{left} more strike{(left == 1 ? "" : "s")} needed to arm your win."); + tabText.AppendLine( + $"{left} more strike{(left == 1 ? "" : "s")} needed to arm your win."); } - string aliveHex = alive <= threshold ? ColorUtility.ToHtmlStringRGBA(Palette.AcceptedGreen) : ColorUtility.ToHtmlStringRGBA(Color.yellow); - tabText.AppendLine($"Current Alive: {alive} • Threshold: {threshold}"); + string aliveHex = alive <= threshold + ? ColorUtility.ToHtmlStringRGBA(Palette.AcceptedGreen) + : ColorUtility.ToHtmlStringRGBA(Color.yellow); + tabText.AppendLine( + $"Current Alive: {alive} • Threshold: {threshold}"); return tabText; } + public override bool DidWin(GameOverReason reason) { - if (reason == (GameOverReason)NewModEndReasons.PulseBladeWin) - return true; - - if (reason == (GameOverReason)NewModEndReasons.TyrantWin || - reason == (GameOverReason)NewModEndReasons.ShadeWin || - reason == (GameOverReason)NewModEndReasons.WraithCallerWin || - reason == (GameOverReason)NewModEndReasons.SpecialAgentWin || - reason == (GameOverReason)NewModEndReasons.PranksterWin || - reason == (GameOverReason)NewModEndReasons.EnergyThiefWin || - reason == (GameOverReason)NewModEndReasons.InjectorWin || - reason == (GameOverReason)NewModEndReasons.DoubleAgentWin) - { - return false; - } - - return true; + return reason == CustomGameOver.GameOverReason() || + GameManager.Instance.DidImpostorsWin(reason); } } } \ No newline at end of file diff --git a/NewMod/Roles/ImpostorRoles/Revenant.cs b/NewMod/Roles/ImpostorRoles/Revenant.cs index b0f13b9..a316345 100644 --- a/NewMod/Roles/ImpostorRoles/Revenant.cs +++ b/NewMod/Roles/ImpostorRoles/Revenant.cs @@ -11,10 +11,14 @@ public class Revenant : ImpostorRole, ICustomRole { public string RoleName => "Revenant"; public string RoleDescription => "Cheat death—exactly once per match. Time it wisely."; - public string RoleLongDescription => "As the Revenant, activate your ghostly form once per game to evade death for 10 seconds.\nIf a meeting is called during this time, your protection is lost permanently—time it wisely!"; + + public string RoleLongDescription => + "As the Revenant, activate your ghostly form once per game to evade death for 10 seconds.\nIf a meeting is called during this time, your protection is lost permanently—time it wisely!"; + public Color RoleColor => new(0.3f, 0f, 0.5f, 1f); public ModdedRoleTeams Team => ModdedRoleTeams.Impostor; public RoleOptionsGroup RoleOptionsGroup { get; } = RoleOptionsGroup.Impostor; + public CustomRoleConfiguration Configuration => new(this) { MaxRoleCount = 2, @@ -31,9 +35,11 @@ public class Revenant : ImpostorRole, ICustomRole GhostRole = (AmongUs.GameOptions.RoleTypes)RoleId.Get(), RoleHintType = RoleHintType.RoleTab }; + public static Dictionary FeignDeathStates = new Dictionary(); public static bool HasUsedFeignDeath = false; public static Dictionary StalkingStates = new Dictionary(); + public class FeignDeathInfo { public float Timer; @@ -53,20 +59,4 @@ public static void OnPlayerExit(PlayerLeaveEvent evt) { ResetAllStates(); } - public override bool DidWin(GameOverReason reason) - { - if (reason == (GameOverReason)NewModEndReasons.TyrantWin || - reason == (GameOverReason)NewModEndReasons.ShadeWin || - reason == (GameOverReason)NewModEndReasons.WraithCallerWin || - reason == (GameOverReason)NewModEndReasons.SpecialAgentWin || - reason == (GameOverReason)NewModEndReasons.PranksterWin || - reason == (GameOverReason)NewModEndReasons.EnergyThiefWin || - reason == (GameOverReason)NewModEndReasons.InjectorWin || - reason == (GameOverReason)NewModEndReasons.DoubleAgentWin) - { - return false; - } - - return true; - } -} +} \ No newline at end of file diff --git a/NewMod/Roles/NeutralRoles/Egoist.cs b/NewMod/Roles/NeutralRoles/Egoist.cs index aedbd14..e1bcd75 100644 --- a/NewMod/Roles/NeutralRoles/Egoist.cs +++ b/NewMod/Roles/NeutralRoles/Egoist.cs @@ -1,6 +1,7 @@ using System.Linq; using MiraAPI.Events; using MiraAPI.Events.Vanilla.Meeting; +using MiraAPI.GameEnd; using MiraAPI.GameOptions; using MiraAPI.Networking; using MiraAPI.Roles; @@ -14,9 +15,11 @@ public class EgoistRole : CrewmateRole, ICustomRole { public string RoleName => "Egoist"; public string RoleDescription => "Crave attention. Earn revenge."; + public string RoleLongDescription => "You are the Egoist, a chaotic neutral entity.\n\n" + "Your goal is to be ejected — if you are, and enough players vote for you, they die and you win."; + public Color RoleColor => new Color(0.8f, 0.3f, 0.6f, 1f); public ModdedRoleTeams Team => ModdedRoleTeams.Custom; public RoleOptionsGroup RoleOptionsGroup => RoleOptionsGroup.Neutral; @@ -75,13 +78,14 @@ public static void OnEjection(EjectionEvent evt) playKillSound: true ); } - GameManager.Instance.RpcEndGame((GameOverReason)NewModEndReasons.EgoistWin, false); + + CustomGameOver.Trigger([egoist.Data]); } } public override bool DidWin(GameOverReason gameOverReason) { - return gameOverReason == (GameOverReason)NewModEndReasons.EgoistWin; + return gameOverReason == CustomGameOver.GameOverReason(); } } -} +} \ No newline at end of file diff --git a/NewMod/Roles/NeutralRoles/EnergyThief.cs b/NewMod/Roles/NeutralRoles/EnergyThief.cs index 3b08482..1e4bc81 100644 --- a/NewMod/Roles/NeutralRoles/EnergyThief.cs +++ b/NewMod/Roles/NeutralRoles/EnergyThief.cs @@ -6,13 +6,17 @@ namespace NewMod.Roles.NeutralRoles; public class EnergyThief : CrewmateRole, ICustomRole -{ +{ public string RoleName => "Energy Thief"; public string RoleDescription => "Drains energy from others, making them weak"; - public string RoleLongDescription => $"The Energy Thief can drain energy from Crewmates or Impostors, weakening them and gaining temporary buffs\nDrain 3 players to win."; + + public string RoleLongDescription => + $"The Energy Thief can drain energy from Crewmates or Impostors, weakening them and gaining temporary buffs\nDrain 3 players to win."; + public Color RoleColor => Color.magenta.FindAlternateColor(); public ModdedRoleTeams Team => ModdedRoleTeams.Custom; public RoleOptionsGroup RoleOptionsGroup { get; } = RoleOptionsGroup.Neutral; + public CustomRoleConfiguration Configuration => new(this) { MaxRoleCount = 5, @@ -22,14 +26,15 @@ public class EnergyThief : CrewmateRole, ICustomRole CanUseVent = false, TasksCountForProgress = false, Icon = MiraAssets.Empty, - OptionsScreenshot = MiraAssets.Empty, + OptionsScreenshot = MiraAssets.Empty, DefaultChance = 50, - DefaultRoleCount = 2, + DefaultRoleCount = 2, CanModifyChance = true, RoleHintType = RoleHintType.RoleTab }; + public override bool DidWin(GameOverReason gameOverReason) { - return gameOverReason == (GameOverReason)NewModEndReasons.EnergyThiefWin; + return gameOverReason == CustomGameOver.GameOverReason(); } } \ No newline at end of file diff --git a/NewMod/Roles/NeutralRoles/Injector.cs b/NewMod/Roles/NeutralRoles/Injector.cs index 5e34771..8c154a3 100644 --- a/NewMod/Roles/NeutralRoles/Injector.cs +++ b/NewMod/Roles/NeutralRoles/Injector.cs @@ -12,6 +12,7 @@ public class InjectorRole : ImpostorRole, ICustomRole public Color RoleColor => new(0.9f, 0.3f, 0.1f); public ModdedRoleTeams Team => ModdedRoleTeams.Custom; public RoleOptionsGroup RoleOptionsGroup { get; } = RoleOptionsGroup.Neutral; + public CustomRoleConfiguration Configuration => new(this) { Icon = NewModAsset.InjectIcon, @@ -25,13 +26,15 @@ public class InjectorRole : ImpostorRole, ICustomRole CanModifyChance = true, RoleHintType = RoleHintType.RoleTab, }; + public TeamIntroConfiguration TeamConfiguration => new() { IntroTeamDescription = RoleDescription, IntroTeamColor = RoleColor }; + public override bool DidWin(GameOverReason gameOverReason) { - return gameOverReason == (GameOverReason)NewModEndReasons.InjectorWin; + return gameOverReason == CustomGameOver.GameOverReason(); } -} +} \ No newline at end of file diff --git a/NewMod/Roles/NeutralRoles/Overload.cs b/NewMod/Roles/NeutralRoles/Overload.cs index c0548ea..b4a263f 100644 --- a/NewMod/Roles/NeutralRoles/Overload.cs +++ b/NewMod/Roles/NeutralRoles/Overload.cs @@ -16,13 +16,17 @@ public class OverloadRole : ImpostorRole, ICustomRole { public string RoleName => "Overload"; public string RoleDescription => "Absorb, Consume, Devour, Overload."; - public string RoleLongDescription => "You are the Overload, an impostor who thrives on the abilities of the fallen. Each ejected player fuels your chaos, granting you their power"; + + public string RoleLongDescription => + "You are the Overload, an impostor who thrives on the abilities of the fallen. Each ejected player fuels your chaos, granting you their power"; + public Color RoleColor => new Color(0.6f, 0.1f, 0.3f, 1f); public ModdedRoleTeams Team => ModdedRoleTeams.Custom; public RoleOptionsGroup RoleOptionsGroup { get; } = RoleOptionsGroup.Neutral; public static int AbsorbedAbilityCount = 0; public static PlayerControl chosenPrey; public static List CachedButtons = new(); + public CustomRoleConfiguration Configuration => new(this) { AffectedByLightOnAirship = false, @@ -36,6 +40,20 @@ public class OverloadRole : ImpostorRole, ICustomRole OptionsScreenshot = null, Icon = null, }; + + public override bool DidWin(GameOverReason gameOverReason) + { + return gameOverReason == CustomGameOver.GameOverReason(); + } + + public static void ResetState() + { + AbsorbedAbilityCount = 0; + chosenPrey = null; + CachedButtons.Clear(); + CustomButtonSingleton.Instance.absorbed = null; + } + [RegisterEvent] public static void OnRoundStart(RoundStartEvent evt) { @@ -50,23 +68,28 @@ public static void OnRoundStart(RoundStartEvent evt) Coroutines.Start(CoShowMenu(1f)); } } + public static IEnumerator CoShowMenu(float delay) { yield return new WaitForSeconds(delay); - if (PlayerControl.LocalPlayer.AmOwner && PlayerControl.LocalPlayer.Data.Role is OverloadRole && chosenPrey == null) + if (PlayerControl.LocalPlayer.AmOwner && PlayerControl.LocalPlayer.Data.Role is OverloadRole && + chosenPrey == null) { CustomPlayerMenu menu = CustomPlayerMenu.Create(); menu.Begin( - player => !player.Data.IsDead && !player.Data.Disconnected && player.PlayerId != PlayerControl.LocalPlayer.PlayerId, + player => !player.Data.IsDead && !player.Data.Disconnected && + player.PlayerId != PlayerControl.LocalPlayer.PlayerId, prey => { chosenPrey = prey; menu.Close(); - Coroutines.Start(CoroutinesHelper.CoNotify($"Chosen prey: {prey?.Data.PlayerName}")); + Coroutines.Start( + CoroutinesHelper.CoNotify($"Chosen prey: {prey?.Data.PlayerName}")); }); } + yield return null; } -} +} \ No newline at end of file diff --git a/NewMod/Roles/NeutralRoles/Prankster.cs b/NewMod/Roles/NeutralRoles/Prankster.cs index d23c09c..272ca1d 100644 --- a/NewMod/Roles/NeutralRoles/Prankster.cs +++ b/NewMod/Roles/NeutralRoles/Prankster.cs @@ -9,10 +9,14 @@ public class Prankster : CrewmateRole, ICustomRole { public string RoleName => "Prankster"; public string RoleDescription => "Set up fake bodies to trick others"; - public string RoleLongDescription => "When reported, each fake body triggers a funny or deadly surprise for the reporter"; + + public string RoleLongDescription => + "When reported, each fake body triggers a funny or deadly surprise for the reporter"; + public Color RoleColor => new Color(1f, 0.55f, 0f); public ModdedRoleTeams Team => ModdedRoleTeams.Custom; public RoleOptionsGroup RoleOptionsGroup { get; } = RoleOptionsGroup.Neutral; + public CustomRoleConfiguration Configuration => new(this) { MaxRoleCount = 3, @@ -32,8 +36,9 @@ public class Prankster : CrewmateRole, ICustomRole HideSettings = false, CanModifyChance = true, }; + public override bool DidWin(GameOverReason gameOverReason) { - return gameOverReason == (GameOverReason)NewModEndReasons.PranksterWin; + return gameOverReason == CustomGameOver.GameOverReason(); } } \ No newline at end of file diff --git a/NewMod/Roles/NeutralRoles/Shade.cs b/NewMod/Roles/NeutralRoles/Shade.cs index 99361ec..0d72e96 100644 --- a/NewMod/Roles/NeutralRoles/Shade.cs +++ b/NewMod/Roles/NeutralRoles/Shade.cs @@ -20,10 +20,14 @@ public class Shade : ImpostorRole, INewModRole public static readonly Dictionary ShadeKills = new(); public string RoleName => "Shade"; public string RoleDescription => "Lurk. Fade. Kill unseen."; - public string RoleLongDescription => "Deploy a shadow field that grants invisibility and lethal power within its darkness."; + + public string RoleLongDescription => + "Deploy a shadow field that grants invisibility and lethal power within its darkness."; + public Color RoleColor => new(0.45f, 0f, 0.8f); public ModdedRoleTeams Team => ModdedRoleTeams.Custom; public NewModFaction Faction => NewModFaction.Entropy; + public CustomRoleConfiguration Configuration => new(this) { AffectedByLightOnAirship = true, @@ -44,16 +48,20 @@ public StringBuilder SetTabText() var mode = OptionGroupSingleton.Instance.Behavior; - tabText.AppendLine($"Within the dark you are unseen."); + tabText.AppendLine( + $"Within the dark you are unseen."); tabText.AppendLine("\n"); - tabText.AppendLine($"Active Shadow Zones: {zonesActive}"); - tabText.AppendLine($"Players inside zones: {playersInZones}"); + tabText.AppendLine( + $"Active Shadow Zones: {zonesActive}"); + tabText.AppendLine( + $"Players inside zones: {playersInZones}"); string effectText = mode switch { ShadeOptions.ShadowMode.Invisible => "Enter a shadow zone to become invisible.", ShadeOptions.ShadowMode.KillEnabled => "Enter a shadow zone to gain the power to kill once.", - ShadeOptions.ShadowMode.Both => "Enter a shadow zone to become invisible and gain the power to kill once.", + ShadeOptions.ShadowMode.Both => + "Enter a shadow zone to become invisible and gain the power to kill once.", _ => "Enter a shadow zone to embrace the darkness." }; @@ -61,11 +69,13 @@ public StringBuilder SetTabText() return tabText; } + public override bool DidWin(GameOverReason gameOverReason) { - return gameOverReason == (GameOverReason)NewModEndReasons.ShadeWin; + return gameOverReason == CustomGameOver.GameOverReason(); } + [RegisterEvent] public static void OnAfterMurder(AfterMurderEvent evt) { @@ -91,6 +101,7 @@ public static void OnAfterMurder(AfterMurderEvent evt) )); } } + [RegisterEvent] public static void OnShadeRoleAssigned(SetRoleEvent evt) { @@ -103,4 +114,4 @@ public static void OnShadeRoleAssigned(SetRoleEvent evt) } } } -} +} \ No newline at end of file diff --git a/NewMod/Roles/NeutralRoles/SpecialAgent.cs b/NewMod/Roles/NeutralRoles/SpecialAgent.cs index 93b1f80..5885cc7 100644 --- a/NewMod/Roles/NeutralRoles/SpecialAgent.cs +++ b/NewMod/Roles/NeutralRoles/SpecialAgent.cs @@ -6,13 +6,14 @@ namespace NewMod.Roles.NeutralRoles; public class SpecialAgent : CrewmateRole, ICustomRole { - public static PlayerControl AssignedPlayer {get; set;} + public static PlayerControl AssignedPlayer { get; set; } public string RoleName => "Special Agent"; public string RoleDescription => "Assigns secret missions to players, who must complete them or face consequences."; public string RoleLongDescription => RoleDescription; public Color RoleColor => Color.gray; public ModdedRoleTeams Team => ModdedRoleTeams.Custom; public RoleOptionsGroup RoleOptionsGroup { get; } = RoleOptionsGroup.Neutral; + public CustomRoleConfiguration Configuration => new(this) { MaxRoleCount = 1, @@ -28,8 +29,9 @@ public class SpecialAgent : CrewmateRole, ICustomRole CanModifyChance = true, RoleHintType = RoleHintType.RoleTab }; + public override bool DidWin(GameOverReason gameOverReason) { - return gameOverReason == (GameOverReason)NewModEndReasons.SpecialAgentWin; + return gameOverReason == CustomGameOver.GameOverReason(); } -} +} \ No newline at end of file diff --git a/NewMod/Roles/NeutralRoles/Tyrant.cs b/NewMod/Roles/NeutralRoles/Tyrant.cs index 49004bc..7883c4f 100644 --- a/NewMod/Roles/NeutralRoles/Tyrant.cs +++ b/NewMod/Roles/NeutralRoles/Tyrant.cs @@ -27,12 +27,15 @@ public sealed class Tyrant : ImpostorRole, INewModRole { public string RoleName => "Tyrant"; public string RoleDescription => "Slow them. Bind them. End them"; + public string RoleLongDescription => "You are the Tyrant. Each kill strengthens your control over the ship:\n"; + public Color RoleColor => new(0.78f, 0.10f, 0.16f, 1f); public ModdedRoleTeams Team => ModdedRoleTeams.Custom; public RoleOptionsGroup RoleOptionsGroup { get; } = RoleOptionsGroup.Neutral; public NewModFaction Faction => NewModFaction.Apex; + public CustomRoleConfiguration Configuration => new(this) { MaxRoleCount = 1, @@ -49,6 +52,7 @@ public sealed class Tyrant : ImpostorRole, INewModRole GhostRole = AmongUs.GameOptions.RoleTypes.Crewmate, RoleHintType = RoleHintType.RoleTab }; + public TeamIntroConfiguration TeamConfiguration => new() { IntroTeamDescription = RoleDescription, @@ -62,7 +66,8 @@ public StringBuilder SetTabText() var green = Palette.AcceptedGreen.ToHtmlStringRGBA(); int kills = GetKillCount(); - string firstKill = "* 1st Kill — Fear Pulse: nearby foes suffer reduced vision and speed for a short time.\n"; + string firstKill = + "* 1st Kill — Fear Pulse: nearby foes suffer reduced vision and speed for a short time.\n"; string secondKill = "* 2nd Kill — Zone of Suppression: a dome that disables buttons for those inside.\n"; string thirdKill = "* 3rd Kill — Intimidation Protocol: the next witness is frozen briefly.\n"; string fourthKill = "* 4th Kill — Apex Throne: designate a Champion who cannot oppose you.\n"; @@ -87,6 +92,7 @@ void AppendAbilityLine(int index, string text) tabText.AppendLine($"{text}"); } } + AppendAbilityLine(1, firstKill); AppendAbilityLine(2, secondKill); AppendAbilityLine(3, thirdKill); @@ -94,35 +100,41 @@ void AppendAbilityLine(int index, string text) return tabText; } + public override bool DidWin(GameOverReason reason) { - if (reason == (GameOverReason)NewModEndReasons.TyrantWin) - return true; - - if (reason == (GameOverReason)NewModEndReasons.ShadeWin || - reason == (GameOverReason)NewModEndReasons.WraithCallerWin || - reason == (GameOverReason)NewModEndReasons.SpecialAgentWin || - reason == (GameOverReason)NewModEndReasons.PranksterWin || - reason == (GameOverReason)NewModEndReasons.EnergyThiefWin || - reason == (GameOverReason)NewModEndReasons.InjectorWin || - reason == (GameOverReason)NewModEndReasons.DoubleAgentWin) - { - return false; - } - return false; + return reason == CustomGameOver.GameOverReason(); } + public int _kills; public static byte _championId; public static bool ApexThroneReady; public static bool ApexThroneOutcomeSet; - public enum ThroneOutcome { None, ChampionSideWin } + + public enum ThroneOutcome + { + None, + ChampionSideWin + } + public static ThroneOutcome Outcome = ThroneOutcome.None; public static readonly HashSet PendingBetrayals = new(); public int GetKillCount() => _kills; public byte GetChampion() => _championId; + public static byte ChampionId => _championId; public void SetChampion(byte playerId) => _championId = playerId; public static void ClearChampion() => _championId = byte.MaxValue; + public static void ResetState() + { + CustomRoleSingleton.Instance._kills = 0; + ApexThroneReady = false; + ApexThroneOutcomeSet = false; + Outcome = ThroneOutcome.None; + PendingBetrayals.Clear(); + ClearChampion(); + } + [RegisterEvent] public static void OnAfterMurderEvent(AfterMurderEvent evt) { @@ -153,25 +165,27 @@ public static void OnAfterMurderEvent(AfterMurderEvent evt) menu.Begin( player => !player.Data.IsDead && !player.Data.Disconnected && - player.PlayerId != PlayerControl.LocalPlayer.PlayerId, + player.PlayerId != PlayerControl.LocalPlayer.PlayerId, player => { tyrant.SetChampion(player.PlayerId); menu.Close(); if (tyrant.Player.AmOwner) - Coroutines.Start(CoroutinesHelper.CoNotify("Apex Throne is armed. You have chosen a Champion.")); + Coroutines.Start(CoroutinesHelper.CoNotify( + "Apex Throne is armed. You have chosen a Champion.")); RpcNotifyChampion(tyrant.Player, player); - }); } } + [RegisterEvent] public static void OnMeetingStart(StartMeetingEvent evt) { Coroutines.Start(CoShowTyrantForChampion(evt.MeetingHud)); } + public static IEnumerator CoShowTyrantForChampion(MeetingHud hud) { yield return null; @@ -198,6 +212,7 @@ public static IEnumerator CoShowTyrantForChampion(MeetingHud hud) NewMod.Instance.Log.LogMessage("No Tyrant in this match skipping..."); } } + NewMod.Instance.Log.LogMessage("NO CRASH"); } @@ -246,6 +261,7 @@ public static void OnHandleVote(HandleVoteEvent evt) : "Betrayal detected. You will be punished."; Coroutines.Start(CoroutinesHelper.CoNotify(msg)); } + break; } } @@ -258,7 +274,12 @@ public static void OnProcessVotes(ProcessVotesEvent evt) if (PendingBetrayals.Count == 0) return; var first = default(byte); - foreach (var id in PendingBetrayals) { first = id; break; } + foreach (var id in PendingBetrayals) + { + first = id; + break; + } + PendingBetrayals.Clear(); var info = GameData.Instance.GetPlayerById(first); @@ -268,25 +289,22 @@ public static void OnProcessVotes(ProcessVotesEvent evt) evt.ExiledPlayer = info; } } - [RegisterEvent] - public static void OnGameEnd(GameEndEvent evt) - { - ApexThroneReady = false; - ApexThroneOutcomeSet = false; - Outcome = ThroneOutcome.None; - ClearChampion(); - } + public void SpawnSuppressionDome(Vector3 pos) { var go = new GameObject("Supression_Dome"); go.transform.position = pos; var area = go.AddComponent(); - area.Init(Player.PlayerId, radius: OptionGroupSingleton.Instance.DomeRadius, OptionGroupSingleton.Instance.DomeDuration); + area.Init(Player.PlayerId, radius: OptionGroupSingleton.Instance.DomeRadius, + OptionGroupSingleton.Instance.DomeDuration); if (Player.AmOwner) - Utils.CreateCircle("SupressionDome", Player.GetTruePosition(), OptionGroupSingleton.Instance.DomeRadius, Palette.AcceptedGreen, OptionGroupSingleton.Instance.DomeDuration); + Utils.CreateCircle("SupressionDome", Player.GetTruePosition(), + OptionGroupSingleton.Instance.DomeRadius, Palette.AcceptedGreen, + OptionGroupSingleton.Instance.DomeDuration); } + public void ArmWitnessTrap(Vector3 pos) { var go = new GameObject("WitnessTrap"); @@ -301,8 +319,11 @@ public void ArmWitnessTrap(Vector3 pos) ); if (Player.AmOwner) - Utils.CreateCircle("ArmWitnessTrap", Player.GetTruePosition(), OptionGroupSingleton.Instance.WitnessRange, Color.cyan, OptionGroupSingleton.Instance.WitnessArmWindow); + Utils.CreateCircle("ArmWitnessTrap", Player.GetTruePosition(), + OptionGroupSingleton.Instance.WitnessRange, Color.cyan, + OptionGroupSingleton.Instance.WitnessArmWindow); } + public void SpawnFearPulse(Vector3 pos) { var go = new GameObject("FearPulseArea"); @@ -317,16 +338,26 @@ public void SpawnFearPulse(Vector3 pos) ); if (Player.AmOwner) - Utils.CreateCircle("FearPulse", Player.GetTruePosition(), OptionGroupSingleton.Instance.FearPulseRadius, new Color(1f, 0.35f, 0.2f, 0.6f), OptionGroupSingleton.Instance.FearPulseDuration); + Utils.CreateCircle("FearPulse", Player.GetTruePosition(), + OptionGroupSingleton.Instance.FearPulseRadius, new Color(1f, 0.35f, 0.2f, 0.6f), + OptionGroupSingleton.Instance.FearPulseDuration); } + [MethodRpc((uint)CustomRPC.NotifyChampion)] public static void RpcNotifyChampion(PlayerControl source, PlayerControl target) { + if (source.Data.Role is Tyrant tyrant) + { + tyrant.SetChampion(target.PlayerId); + } + if (target.AmOwner) { - Coroutines.Start(CoroutinesHelper.CoNotify($"{source.Data.PlayerName} is your Tyrant. Obey or be exiled.")); + Coroutines.Start(CoroutinesHelper.CoNotify( + $"{source.Data.PlayerName} is your Tyrant. Obey or be exiled.")); } } + [MethodRpc((uint)CustomRPC.FearPulse)] public static void RpcSpawnFearPulse(PlayerControl source, float x, float y) { @@ -334,12 +365,14 @@ public static void RpcSpawnFearPulse(PlayerControl source, float x, float y) tyrant.SpawnFearPulse(new Vector2(x, y)); } + [MethodRpc((uint)CustomRPC.SuppressionDome)] public static void RpcSpawnSuppressionDome(PlayerControl source, float x, float y) { var tyrant = source.Data.Role as Tyrant; tyrant.SpawnSuppressionDome(new Vector2(x, y)); } + [MethodRpc((uint)CustomRPC.WitnessTrap)] public static void RpcArmWitnessTrap(PlayerControl source, float x, float y) { @@ -347,4 +380,4 @@ public static void RpcArmWitnessTrap(PlayerControl source, float x, float y) tyrant.ArmWitnessTrap(new Vector2(x, y)); } } -} +} \ No newline at end of file diff --git a/NewMod/Roles/NeutralRoles/WraithCaller.cs b/NewMod/Roles/NeutralRoles/WraithCaller.cs index 7628a8a..35a5ab7 100644 --- a/NewMod/Roles/NeutralRoles/WraithCaller.cs +++ b/NewMod/Roles/NeutralRoles/WraithCaller.cs @@ -12,10 +12,14 @@ public class WraithCaller : ImpostorRole, INewModRole { public string RoleName => "Wraith Caller"; public string RoleDescription => "Summon. Lurk. Reap."; - public string RoleLongDescription => "Summon spectral NPCs that slip through walls and hunt down your marked target."; + + public string RoleLongDescription => + "Summon spectral NPCs that slip through walls and hunt down your marked target."; + public Color RoleColor => new(0.58f, 0.20f, 0.90f); public ModdedRoleTeams Team => ModdedRoleTeams.Custom; public NewModFaction Faction => NewModFaction.Entropy; + public CustomRoleConfiguration Configuration => new(this) { AffectedByLightOnAirship = false, @@ -45,28 +49,34 @@ public StringBuilder SetTabText() tab.AppendLine(); tab.AppendLine($"Sent: {sent}"); - tab.AppendLine($"Kills: = required ? green : cyan)}>{kills}/{required}"); + tab.AppendLine( + $"Kills: = required ? green : cyan)}>{kills}/{required}"); if (kills < required) { int left = required - kills; - tab.AppendLine($"{left} more successful kill{(left == 1 ? "" : "s")} to win."); + tab.AppendLine( + $"{left} more successful kill{(left == 1 ? "" : "s")} to win."); } else { - tab.AppendLine($"Win condition armed. Survive to claim victory."); + tab.AppendLine( + $"Win condition armed. Survive to claim victory."); } if (showWarn) { tab.AppendLine(); - tab.AppendLine($"Tip: Time your summons. Meetings cancel hunts."); + tab.AppendLine( + $"Tip: Time your summons. Meetings cancel hunts."); } + return tab; } + public override bool DidWin(GameOverReason gameOverReason) { - return gameOverReason == (GameOverReason)NewModEndReasons.WraithCallerWin; + return gameOverReason == CustomGameOver.GameOverReason(); } } -} +} \ No newline at end of file diff --git a/NewMod/Utilities/CoroutinesHelper.cs b/NewMod/Utilities/CoroutinesHelper.cs index b7433c3..1f30193 100644 --- a/NewMod/Utilities/CoroutinesHelper.cs +++ b/NewMod/Utilities/CoroutinesHelper.cs @@ -57,6 +57,7 @@ public static IEnumerator CoNotify(string message) textComponent.text = message; textComponent.fontSize = Mathf.Clamp(3.5f - (message.Length / 20f), 2f, 3.5f); } + obj.gameObject.SetActive(true); yield return new WaitForEndOfFrame(); @@ -80,6 +81,7 @@ public static IEnumerator CoNotify(string message) GameObject.Destroy(obj); } + /// /// Starts and displays a countdown timer for a mission, then fails the mission if time expires. /// @@ -116,6 +118,7 @@ public static IEnumerator CoMissionTimer(PlayerControl target, float duration) Object.Destroy(timerLabel.gameObject); yield break; } + yield return new WaitForSeconds(1f); timeRemaining -= 1f; @@ -129,6 +132,7 @@ public static IEnumerator CoMissionTimer(PlayerControl target, float duration) { SoundManager.Instance.PlaySound(ShipStatus.Instance.SabotageSound, false, 0.8f); } + HudManager.Instance.FullScreen.color = new Color(1f, 0f, 0f, 0.1f); HudManager.Instance.FullScreen.gameObject.SetActive(true); } @@ -145,12 +149,14 @@ public static IEnumerator CoMissionTimer(PlayerControl target, float duration) HudManager.Instance.FullScreen.gameObject.SetActive(false); } } + // Time has expired, destroy the timer and fail the mission Object.Destroy(timerLabel.gameObject); SoundManager.Instance.StopSound(ShipStatus.Instance.SabotageSound); HudManager.Instance.FullScreen.gameObject.SetActive(false); Utils.RpcMissionFails(PlayerControl.LocalPlayer, target); } + /// /// Allows a Prankster to create fake dead bodies by pressing F5, fulfilling a mission if enough bodies are created. /// @@ -163,6 +169,7 @@ public static IEnumerator UsePranksterAbilities(PlayerControl target) { bodiesCreated[target.PlayerId] = 0; } + while (true) { // If the player dies mid-mission, fail the mission @@ -179,8 +186,10 @@ public static IEnumerator UsePranksterAbilities(PlayerControl target) bodiesCreated[target.PlayerId]++; if (target.AmOwner) { - Coroutines.Start(CoNotify($"Bodies created: {bodiesCreated[target.PlayerId]}/2")); + Coroutines.Start( + CoNotify($"Bodies created: {bodiesCreated[target.PlayerId]}/2")); } + // Once enough bodies are created, succeed the mission if (bodiesCreated[target.PlayerId] >= 2) { @@ -188,9 +197,11 @@ public static IEnumerator UsePranksterAbilities(PlayerControl target) yield break; } } + yield return null; } } + /// /// Allows an Energy Thief to drain nearby players' energy by pressing F5, fulfilling a mission after enough drains. /// @@ -205,6 +216,7 @@ public static IEnumerator UseEnergyThiefAbilities(PlayerControl target) { drainCount[target.PlayerId] = 0; } + while (true) { // If the player dies mid-mission, fail the mission @@ -213,17 +225,18 @@ public static IEnumerator UseEnergyThiefAbilities(PlayerControl target) Utils.RpcMissionFails(PlayerControl.LocalPlayer, target); yield break; } + // Press F5 to drain energy from a nearby player if (Input.GetKeyDown(KeyCode.F5)) { var playersInRange = Helpers.GetClosestPlayers( - target, - drainRange, - ignoreColliders: true, - ignoreSource: true - ) - .Where(p => !p.Data.IsDead && !p.Data.Disconnected) - .ToList(); + target, + drainRange, + ignoreColliders: true, + ignoreSource: true + ) + .Where(p => !p.Data.IsDead && !p.Data.Disconnected) + .ToList(); if (playersInRange.Count > 0) { @@ -235,11 +248,14 @@ public static IEnumerator UseEnergyThiefAbilities(PlayerControl target) // Notify both the drainer and the drained player if (target.AmOwner) { - Coroutines.Start(CoNotify($"You have drained energy from {victim.Data.PlayerName}!")); + Coroutines.Start(CoNotify( + $"You have drained energy from {victim.Data.PlayerName}!")); } + if (victim.AmOwner) { - Coroutines.Start(CoNotify("Your energy has been drained!")); + Coroutines.Start( + CoNotify("Your energy has been drained!")); } // After enough drains, succeed the mission @@ -253,10 +269,12 @@ public static IEnumerator UseEnergyThiefAbilities(PlayerControl target) { if (target.AmOwner) { - Coroutines.Start(CoNotify("No players nearby to drain energy from.")); + Coroutines.Start(CoNotify( + "No players nearby to drain energy from.")); } } } + yield return null; } } @@ -276,6 +294,7 @@ public static IEnumerator CoReviveAndKill(PlayerControl target) { Coroutines.Start(CoNotify("Press F5 to revive a dead player!")); } + while (true) { if (target.Data.IsDead) @@ -283,6 +302,7 @@ public static IEnumerator CoReviveAndKill(PlayerControl target) Utils.RpcMissionFails(PlayerControl.LocalPlayer, target); yield break; } + if (Input.GetKeyDown(KeyCode.F5)) { // Perform the revive if not yet done @@ -291,17 +311,20 @@ public static IEnumerator CoReviveAndKill(PlayerControl target) var deadBody = Utils.GetClosestBody(); if (deadBody == null && target.AmOwner) { - Coroutines.Start(CoNotify("No dead body found! Move closer and press F5 again.")); + Coroutines.Start(CoNotify( + "No dead body found! Move closer and press F5 again.")); } else { revivedParentId = deadBody.ParentId; - Utils.HandleRevive(target, deadBody.ParentId, RoleTypes.Crewmate, deadBody.transform.position.x, deadBody.transform.position.y); + Utils.HandleRevive(target, deadBody.ParentId, RoleTypes.Crewmate, + deadBody.transform.position.x, deadBody.transform.position.y); yield return new WaitForSeconds(0.5f); - Coroutines.Start(CoNotify("Player revived! Press F5 to kill them again!")); + Coroutines.Start(CoNotify( + "Player revived! Press F5 to kill them again!")); revived = true; } @@ -325,6 +348,7 @@ public static IEnumerator CoReviveAndKill(PlayerControl target) } } } + yield return null; } } @@ -336,7 +360,8 @@ public static IEnumerator CoReviveAndKill(PlayerControl target) /// The most wanted target player. /// The player assigned to eliminate the most wanted target. /// An for coroutine control. - public static IEnumerator CoHandleWantedTarget(ArrowBehaviour arrow, PlayerControl mostwantedTarget, PlayerControl target) + public static IEnumerator CoHandleWantedTarget(ArrowBehaviour arrow, PlayerControl mostwantedTarget, + PlayerControl target) { // Keep updating the arrow's position as long as the target is alive while (!mostwantedTarget.Data.IsDead && !mostwantedTarget.Data.Disconnected) @@ -344,6 +369,7 @@ public static IEnumerator CoHandleWantedTarget(ArrowBehaviour arrow, PlayerContr arrow.target = mostwantedTarget.transform.position; yield return null; } + Object.Destroy(arrow.gameObject); yield return new WaitForSeconds(0.5f); @@ -358,8 +384,10 @@ public static IEnumerator CoHandleWantedTarget(ArrowBehaviour arrow, PlayerContr { Utils.RpcMissionFails(PlayerControl.LocalPlayer, target); } + yield break; } + /// /// Resets the player's movement speed after the given delay. /// Used to revert Adrenaline serum effect. @@ -387,12 +415,12 @@ public static IEnumerator EnableMovementAfterDelay(PlayerControl target, float d { yield return new WaitForSeconds(delay); - if (target != null && !target.Data.IsDead) + if (target && !target.Data.IsDead) { target.moveable = true; - target.MyPhysics.inputHandler.enabled = true; } } + /// /// Resets the player's rotation after a specified delay. /// Useful for restoring normal orientation after bounce/spin effects (e.g. Bounce Serum). @@ -423,6 +451,7 @@ public static IEnumerator ResetRepelEffect(PlayerControl target, float delay) target.MyPhysics.body.velocity = Vector2.zero; } } + /// /// Coroutine that waits for a given duration before destroying a specified GameObject. /// @@ -458,4 +487,4 @@ public static IEnumerator RemoveCameraEffect(Camera cam, float duration) Object.Destroy(sf); } } -} +} \ No newline at end of file diff --git a/NewMod/Utilities/Utils.cs b/NewMod/Utilities/Utils.cs index c6dff3e..8a83377 100644 --- a/NewMod/Utilities/Utils.cs +++ b/NewMod/Utilities/Utils.cs @@ -68,12 +68,14 @@ public static class Utils /// /// Maintains saved roles for players, keyed by their ID. /// - public static Dictionary> savedPlayerRoles = new Dictionary>(); + public static Dictionary> savedPlayerRoles = + new Dictionary>(); /// /// Maps a player ID to a TextMeshPro timer display for missions. /// public static Dictionary MissionTimer = new Dictionary(); + /// /// A dictionary holding the strike kill counts for each player, indexed by their player ID. /// @@ -105,6 +107,7 @@ public static void RecordOnKill(PlayerControl killer, PlayerControl victim) { PlayerKiller[victim.PlayerId] = killer.PlayerId; } + public static void ResetKillTracking() { PlayerKiller.Clear(); @@ -142,12 +145,14 @@ public static DeadBody GetClosestBody() var component = collider2D.GetComponent(); var distance = Vector2.Distance(PlayerControl.LocalPlayer.GetTruePosition(), component.TruePosition); - if (distance <= GameOptionsManager.Instance.currentNormalGameOptions.KillDistance && distance < closestDistance) + if (distance <= GameOptionsManager.Instance.currentNormalGameOptions.KillDistance && + distance < closestDistance) { closestBody = component; closestDistance = distance; } } + return closestBody; } @@ -165,6 +170,7 @@ public static bool IsActive(SystemTypes type) { return false; } + switch (type) { case SystemTypes.Electrical: @@ -203,7 +209,8 @@ public static bool IsActive(SystemTypes type) } case SystemTypes.MushroomMixupSabotage: if (mapId != 5) return false; - var MushroomMixupSabotageSystem = ShipStatus.Instance.Systems[type].TryCast(); + var MushroomMixupSabotageSystem = + ShipStatus.Instance.Systems[type].TryCast(); return MushroomMixupSabotageSystem != null && MushroomMixupSabotageSystem.IsActive; default: return false; @@ -336,6 +343,7 @@ public static void RegisterStrikeKill(PlayerControl killer, PlayerControl victim var playerId = killer.PlayerId; StrikeKills[playerId] = StrikeKills.GetValueOrDefault(playerId) + 1; } + /// /// Retrieves the total number of strike kills for a specific player. /// @@ -373,7 +381,8 @@ public static void ResetInjections() // Inspired By: https://github.com/AU-Avengers/TOU-Mira/blob/dev/TownOfUs/Modules/ReviveUtilities.cs#L40 [MethodRpc((uint)CustomRPC.HandleRevive)] - public static IEnumerator HandleRevive(PlayerControl source, byte revivedId, RoleTypes roleToSet, float reviveX, float reviveY) + public static IEnumerator HandleRevive(PlayerControl source, byte revivedId, RoleTypes roleToSet, float reviveX, + float reviveY) { var revived = PlayerById(revivedId); @@ -451,6 +460,7 @@ public static void SavePlayerRole(byte playerId, RoleBehaviour role) { savedPlayerRoles[playerId] = new List(); } + savedPlayerRoles[playerId].Add(role); } @@ -466,6 +476,7 @@ public static List GetPlayerRolesHistory(byte playerId) { return savedPlayerRoles[playerId]; } + return new List(); } @@ -482,6 +493,7 @@ public static PlayerControl GetRandomPlayer(System.Predicate matc { return players[Random.RandomRange(0, players.Count)]; } + return null; } @@ -498,6 +510,7 @@ public static PlayerControl AnyDeadPlayer() return player; } } + return null; } @@ -516,19 +529,23 @@ public static void RpcRandomDrainActions(PlayerControl source, PlayerControl tar target.MyPhysics.Speed *= 0.5f; if (source.AmOwner) { - HudManager.Instance.ShowPopUp($"{target.Data.PlayerName} speed was reduced by 50%!"); + HudManager.Instance.ShowPopUp( + $"{target.Data.PlayerName} speed was reduced by 50%!"); } }, () => { if (target.AmOwner) { - HudManager.Instance.StartCoroutine(HudManager.Instance.CoFadeFullScreen(Color.black, Color.black, 0.5f, false)); + HudManager.Instance.StartCoroutine( + HudManager.Instance.CoFadeFullScreen(Color.black, Color.black, 0.5f, false)); target.NetTransform.Halt(); } + if (source.AmOwner) { - HudManager.Instance.ShowPopUp($"Movement is disabled for {target.Data.PlayerName}, and their screen is black!"); + HudManager.Instance.ShowPopUp( + $"Movement is disabled for {target.Data.PlayerName}, and their screen is black!"); } }, () => @@ -536,7 +553,8 @@ public static void RpcRandomDrainActions(PlayerControl source, PlayerControl tar target.myTasks.Clear(); if (source.AmOwner) { - HudManager.Instance.ShowPopUp($"{target.Data.PlayerName} had all of their tasks cleared!"); + HudManager.Instance.ShowPopUp( + $"{target.Data.PlayerName} had all of their tasks cleared!"); } }, () => @@ -544,7 +562,8 @@ public static void RpcRandomDrainActions(PlayerControl source, PlayerControl tar target.RemainingEmergencies = 0; if (source.AmOwner) { - HudManager.Instance.ShowPopUp($"{target.Data.PlayerName} can no longer call emergency meetings!"); + HudManager.Instance.ShowPopUp( + $"{target.Data.PlayerName} can no longer call emergency meetings!"); } }, () => @@ -555,7 +574,8 @@ public static void RpcRandomDrainActions(PlayerControl source, PlayerControl tar target.NetTransform.RpcSnapTo(randomPlayer.GetTruePosition()); if (source.AmOwner) { - HudManager.Instance.ShowPopUp($"{target.Data.PlayerName} has been teleported!"); + HudManager.Instance.ShowPopUp( + $"{target.Data.PlayerName} has been teleported!"); } } } @@ -578,7 +598,8 @@ public static string GetMission(PlayerControl target, MissionType mission) { MissionType.KillMostWanted => $"Kill the Most Wanted Target: {mostwantedTarget.Data.PlayerName}", MissionType.DrainEnergy => "Drain one player using Energy Thief abilities", - MissionType.CreateFakeBodies => "Disguise yourself as a random player and create fake dead bodies around the map using Prankster abilities!", + MissionType.CreateFakeBodies => + "Disguise yourself as a random player and create fake dead bodies around the map using Prankster abilities!", MissionType.ReviveAndKill => "Revive a dead player using Necromancer powers and kill them again", _ => "Unknown mission." }; @@ -611,14 +632,17 @@ public static string GetMission(PlayerControl target, MissionType mission) rolesHistory.RemoveAt(lastIndex); target.RpcSetRole(originalRole.Role, true); } + break; case MissionType.CreateFakeBodies: NewMod.Instance.Log.LogMessage("[SpecialAgent] Mission assigned: CreateFakeBodies"); if (target.AmOwner) { - Coroutines.Start(CoroutinesHelper.CoNotify("Press F5 to Create Dead Bodies")); + Coroutines.Start(CoroutinesHelper.CoNotify( + "Press F5 to Create Dead Bodies")); } + Coroutines.Start(CoroutinesHelper.UsePranksterAbilities(target)); break; @@ -626,8 +650,10 @@ public static string GetMission(PlayerControl target, MissionType mission) NewMod.Instance.Log.LogMessage("[SpecialAgent] Mission assigned: DrainEnergy"); if (target.AmOwner) { - Coroutines.Start(CoroutinesHelper.CoNotify("Press F5 to drain nearby players'energy")); + Coroutines.Start(CoroutinesHelper.CoNotify( + "Press F5 to drain nearby players'energy")); } + Coroutines.Start(CoroutinesHelper.UseEnergyThiefAbilities(target)); break; @@ -639,8 +665,10 @@ public static string GetMission(PlayerControl target, MissionType mission) } catch (System.Exception ex) { - NewMod.Instance.Log.LogError($"Failed to assign mission to {target.Data.PlayerName}. Reason: {ex.Message} | StackTrace: {ex.StackTrace}"); + NewMod.Instance.Log.LogError( + $"Failed to assign mission to {target.Data.PlayerName}. Reason: {ex.Message} | StackTrace: {ex.StackTrace}"); } + return selectedMission; } @@ -653,21 +681,26 @@ public static void RpcMissionSuccess(PlayerControl source, PlayerControl target) { int currentSuccessCount = GetMissionSuccessCount(source.PlayerId); int netScore = currentSuccessCount - GetMissionFailureCount(source.PlayerId); - Coroutines.Start(CoroutinesHelper.CoNotify($"Target {target.Data.PlayerName} has completed their mission!\nCurrent net score: {netScore}/3")); + Coroutines.Start(CoroutinesHelper.CoNotify( + $"Target {target.Data.PlayerName} has completed their mission!\nCurrent net score: {netScore}/3")); } else { - Coroutines.Start(CoroutinesHelper.CoNotify("Mission Completed! You are free to go!")); + Coroutines.Start( + CoroutinesHelper.CoNotify("Mission Completed! You are free to go!")); } + if (savedTasks.ContainsKey(target)) { target.myTasks = savedTasks[target]; savedTasks.Remove(target); } + if (SpecialAgent.AssignedPlayer == target) { SpecialAgent.AssignedPlayer = null; } + if (target.Data.Role is ICustomRole role) { if (RoleToButtonsMap.TryGetValue(role.GetType(), out var buttonTypes)) @@ -691,23 +724,30 @@ public static void RpcMissionFails(PlayerControl source, PlayerControl target) { int currentFailureCount = GetMissionFailureCount(source.PlayerId); int netScore = GetMissionSuccessCount(source.PlayerId) - currentFailureCount; - Coroutines.Start(CoroutinesHelper.CoNotify($"Target {target.Data.PlayerName} has failed their mission! Current net score: {netScore}/3")); + Coroutines.Start(CoroutinesHelper.CoNotify( + $"Target {target.Data.PlayerName} has failed their mission! Current net score: {netScore}/3")); } else { - Coroutines.Start(CoroutinesHelper.CoNotify("Mission Failed! You will face the consequences!")); + Coroutines.Start( + CoroutinesHelper.CoNotify( + "Mission Failed! You will face the consequences!")); } - source.RpcCustomMurder(target, createDeadBody: false, didSucceed: true, showKillAnim: false, playKillSound: true, teleportMurderer: false); + + source.RpcCustomMurder(target, createDeadBody: false, didSucceed: true, showKillAnim: false, + playKillSound: true, teleportMurderer: false); if (savedTasks.ContainsKey(target)) { target.myTasks = savedTasks[target]; savedTasks.Remove(target); } + if (SpecialAgent.AssignedPlayer == target) { SpecialAgent.AssignedPlayer = null; } + if (target.Data.Role is ICustomRole role) { if (RoleToButtonsMap.TryGetValue(role.GetType(), out var buttonTypes)) @@ -721,6 +761,7 @@ public static void RpcMissionFails(PlayerControl source, PlayerControl target) } } } + public static string GetFactionDisplay(INewModRole role) { return role.Faction switch @@ -735,7 +776,10 @@ public static string GetFactionDisplay(INewModRole role) /// /// Stores tasks that have been saved for a given player, allowing restoration after missions. /// - public static Il2CppSystem.Collections.Generic.Dictionary> savedTasks = new(); + public static + Il2CppSystem.Collections.Generic.Dictionary> + savedTasks = new(); /// /// Assigns a random mission to the target player as a custom RPC. @@ -754,6 +798,7 @@ public static void RpcAssignMission(PlayerControl source, PlayerControl target) { newTaskList.Add(task); } + savedTasks[target] = newTaskList; } @@ -769,8 +814,8 @@ public static void RpcAssignMission(PlayerControl source, PlayerControl target) ImportantTextTask Missionmessage = new GameObject("MissionMessage").AddComponent(); Missionmessage.transform.SetParent(AmongUsClient.Instance.transform, false); Missionmessage.Text = $"Special Agent has given you a mission!\n" + - $"Mission: {GetMission(target, randomMission)}\n" + - $"Complete it or face the consequences!"; + $"Mission: {GetMission(target, randomMission)}\n" + + $"Complete it or face the consequences!"; target.myTasks.Insert(0, Missionmessage); // Disable the Role Player's Ability @@ -786,6 +831,7 @@ public static void RpcAssignMission(PlayerControl source, PlayerControl target) } } } + Coroutines.Start(CoroutinesHelper.CoMissionTimer(target, 60f)); } @@ -845,7 +891,8 @@ public static IEnumerator StartFeignDeath(PlayerControl player) }; Revenant.FeignDeathStates[player.PlayerId] = info; - Coroutines.Start(CoroutinesHelper.CoNotify("You are now feigning death.\nYou will be revived in 10 seconds if unreported.")); + Coroutines.Start(CoroutinesHelper.CoNotify( + "You are now feigning death.\nYou will be revived in 10 seconds if unreported.")); if (player.AmOwner) { @@ -861,12 +908,14 @@ public static IEnumerator StartFeignDeath(PlayerControl player) if (info.Reported) { - yield return CoroutinesHelper.CoNotify("Your feign death has been reported. You remain dead."); + yield return CoroutinesHelper.CoNotify( + "Your feign death has been reported. You remain dead."); Revenant.FeignDeathStates.Remove(player.PlayerId); SoundManager.Instance.StopSound(clip); yield break; } } + Revenant.HasUsedFeignDeath = true; Revenant.StalkingStates[player.PlayerId] = true; @@ -883,6 +932,7 @@ public static IEnumerator StartFeignDeath(PlayerControl player) { HudManager.Instance.SetHudActive(player, player.Data.Role, true); } + SoundManager.Instance.StopSound(clip); } @@ -903,8 +953,10 @@ public static IEnumerator FadeAndDestroy(GameObject ghost, float fadeDuration) { ghostRenderer.color = new Color(1f, 0f, 0f, alpha); } + yield return null; } + Object.Destroy(ghost); } @@ -914,14 +966,14 @@ public static IEnumerator FadeAndDestroy(GameObject ghost, float fadeDuration) /// public static readonly Dictionary> RoleToButtonsMap = new() { - { typeof(EnergyThief), new() { typeof(DrainButton) } }, + { typeof(EnergyThief), new() { typeof(DrainButton) } }, { typeof(NecromancerRole), new() { typeof(ReviveButton) } }, - { typeof(Prankster), new() { typeof(FakeBodyButton) } }, - { typeof(Revenant), new() { typeof(FeignDeathButton), typeof(DoomAwakening) } }, - { typeof(SpecialAgent), new() { typeof(AssignButton) } }, - { typeof(TheVisionary), new() { typeof(CaptureButton), typeof(ShowScreenshotButton) } }, - { typeof(PulseBlade), new() { typeof(StrikeButton)}}, - { typeof(WraithCaller), new() {typeof(CallWraithButton) } } + { typeof(Prankster), new() { typeof(FakeBodyButton) } }, + { typeof(Revenant), new() { typeof(FeignDeathButton), typeof(DoomAwakening) } }, + { typeof(SpecialAgent), new() { typeof(AssignButton) } }, + { typeof(TheVisionary), new() { typeof(CaptureButton), typeof(ShowScreenshotButton) } }, + { typeof(PulseBlade), new() { typeof(StrikeButton) } }, + { typeof(WraithCaller), new() { typeof(CallWraithButton) } } // TODO: Add Launchpad roles and their associated buttons here }; @@ -964,77 +1016,84 @@ public static void RpcApplySerum(PlayerControl source, PlayerControl target, Ser switch (serumType) { case SerumType.Adrenaline: - { - float boostPercent = OptionGroupSingleton.Instance.AdrenalineSpeedBoost; - float multiplier = 1f + (boostPercent / 100f); - float originalSpeed = target.MyPhysics.Speed; + { + float boostPercent = OptionGroupSingleton.Instance.AdrenalineSpeedBoost; + float multiplier = 1f + (boostPercent / 100f); + float originalSpeed = target.MyPhysics.Speed; - target.MyPhysics.Speed *= multiplier; + target.MyPhysics.Speed *= multiplier; - Coroutines.Start(CoroutinesHelper.ResetSpeedAfterDelay(target, originalSpeed, 10f)); - break; - } + Coroutines.Start(CoroutinesHelper.ResetSpeedAfterDelay(target, originalSpeed, 10f)); + break; + } case SerumType.Paralysis: - { - float duration = OptionGroupSingleton.Instance.ParalysisDuration; - - target.moveable = false; - target.MyPhysics.inputHandler.enabled = false; + { + float duration = OptionGroupSingleton.Instance.ParalysisDuration; - Coroutines.Start(CoroutinesHelper.EnableMovementAfterDelay(target, duration)); - break; + target.moveable = false; + if (target.AmOwner) + { + target.MyPhysics.SetNormalizedVelocity(Vector2.zero); } + + Coroutines.Start(CoroutinesHelper.EnableMovementAfterDelay(target, duration)); + break; + } case SerumType.BounceSerum: - { - float bounceDuration = OptionGroupSingleton.Instance.BounceDuration; - float h = OptionGroupSingleton.Instance.BounceForceHorizontal; - //float v = OptionGroupSingleton.Instance.BounceForceVertical; - float maxRotate = OptionGroupSingleton.Instance.BounceRotateEffect.Value; + { + float bounceDuration = OptionGroupSingleton.Instance.BounceDuration; + float h = OptionGroupSingleton.Instance.BounceForceHorizontal; + //float v = OptionGroupSingleton.Instance.BounceForceVertical; + float maxRotate = OptionGroupSingleton.Instance.BounceRotateEffect.Value; - //Vector2 force = new(Random.Range(-h, h), Random.Range(-v, v)); + //Vector2 force = new(Random.Range(-h, h), Random.Range(-v, v)); - //target.MyPhysics.body.AddForce(force); + //target.MyPhysics.body.AddForce(force); - Effects.Bounce(target.transform, bounceDuration, h); + Effects.Bounce(target.transform, bounceDuration, h); - if (OptionGroupSingleton.Instance.EnableBounceVariants) + if (OptionGroupSingleton.Instance.EnableBounceVariants) + { + if (Helpers.CheckChance(OptionGroupSingleton.Instance.BounceRotateEffect)) { - if (Helpers.CheckChance(OptionGroupSingleton.Instance.BounceRotateEffect)) - { - target.transform.Rotate(0, 0, Random.Range(-maxRotate, maxRotate)); - } - Coroutines.Start(CoroutinesHelper.ResetRotationAfterDelay(target, bounceDuration)); + target.transform.Rotate(0, 0, Random.Range(-maxRotate, maxRotate)); } + + Coroutines.Start(CoroutinesHelper.ResetRotationAfterDelay(target, bounceDuration)); } + } break; case SerumType.RepelSerum: - { - float RepelDuration = OptionGroupSingleton.Instance.RepelDuration; - float RepelRange = OptionGroupSingleton.Instance.RepelRange; - float RepelForce = OptionGroupSingleton.Instance.RepelForce; + { + float RepelDuration = OptionGroupSingleton.Instance.RepelDuration; + float RepelRange = OptionGroupSingleton.Instance.RepelRange; + float RepelForce = OptionGroupSingleton.Instance.RepelForce; - foreach (var other in PlayerControl.AllPlayerControls) - { - if (other == target || other.Data.IsDead || other.Data.Disconnected) continue; + foreach (var other in PlayerControl.AllPlayerControls) + { + if (other == target || other.Data.IsDead || other.Data.Disconnected) continue; - float dist = Vector2.Distance(other.GetTruePosition(), target.GetTruePosition()); + float dist = Vector2.Distance(other.GetTruePosition(), target.GetTruePosition()); - if (dist < RepelRange) - { - Vector2 dir = (other.GetTruePosition() - target.GetTruePosition()).normalized; - other.MyPhysics.body.velocity += dir * RepelForce; - } + if (dist < RepelRange) + { + Vector2 dir = (other.GetTruePosition() - target.GetTruePosition()).normalized; + other.MyPhysics.body.velocity += dir * RepelForce; } - Coroutines.Start(CoroutinesHelper.ResetRepelEffect(target, RepelDuration)); } + + Coroutines.Start(CoroutinesHelper.ResetRepelEffect(target, RepelDuration)); + } break; } + RegisterPlayerInjection(target); if (source.AmOwner) { - Helpers.CreateAndShowNotification($"Injected {target.Data.PlayerName} with {serumType}", new(0.9f, 0.3f, 0.1f), spr: NewModAsset.InjectIcon.LoadAsset()); + Helpers.CreateAndShowNotification($"Injected {target.Data.PlayerName} with {serumType}", + new(0.9f, 0.3f, 0.1f), spr: NewModAsset.InjectIcon.LoadAsset()); } } @@ -1067,8 +1126,10 @@ public static IEnumerator CoShakeCamera(FollowerCamera cam, float duration) { cam.transform.localPosition = originalPos; } + yield return null; } + cam.transform.localPosition = originalPos; } @@ -1085,6 +1146,7 @@ public static string FormatSpan(System.TimeSpan t) int ss = Mathf.Clamp(t.Seconds, 0, 59); return $"{dd:D1}:{hh:D2}:{mm:D2}:{ss:D2}"; } + /// /// Finds the surveillance console on the current ship. /// @@ -1094,13 +1156,14 @@ public static string FormatSpan(System.TimeSpan t) public static SystemConsole FindSurveillanceConsole() { var all = ShipStatus.Instance?.AllConsoles; - var sys = all.OfType().FirstOrDefault(c => c && c.MinigamePrefab && c.MinigamePrefab is SurveillanceMinigame); + var sys = all.OfType() + .FirstOrDefault(c => c && c.MinigamePrefab && c.MinigamePrefab is SurveillanceMinigame); return all.OfType().FirstOrDefault(c => { var n = c.name; return n.Contains("Surv", System.StringComparison.OrdinalIgnoreCase) - || n.Contains("Lookout", System.StringComparison.OrdinalIgnoreCase); + || n.Contains("Lookout", System.StringComparison.OrdinalIgnoreCase); }); } @@ -1132,7 +1195,8 @@ public static Material GetCircleMat() /// /// The created representing the circle. /// - public static GameObject CreateCircle(string name, Vector3 pos, float radius, Color color, float duration, int segments = 64) + public static GameObject CreateCircle(string name, Vector3 pos, float radius, Color color, float duration, + int segments = 64) { var go = new GameObject(name); go.transform.position = pos; @@ -1171,18 +1235,21 @@ public static GameObject CreateCircle(string name, Vector3 pos, float radius, Co Coroutines.Start(CoroutinesHelper.DespawnCircle(go, duration)); return go; } + public static bool IsRoleActive(string roleName) { foreach (var roles in RoleManager.Instance.AllRoles) { CustomRoleManager.GetCustomRoleBehaviour(roles.Role, out var customRole); - if (customRole != null && customRole.RoleName.Equals(roleName, System.StringComparison.OrdinalIgnoreCase)) + if (customRole != null && + customRole.RoleName.Equals(roleName, System.StringComparison.OrdinalIgnoreCase)) { return customRole.GetChance() > 0 && customRole.GetCount() > 0; } } + return false; } } -} +} \ No newline at end of file