diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..017cd3a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[*.{cs,vb}] +dotnet_diagnostic.CA1707.severity = none \ No newline at end of file diff --git a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs index f8de0ab..d357932 100644 --- a/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs +++ b/MiraAPI.Example/Buttons/Freezer/FreezeButton.cs @@ -1,5 +1,7 @@ using MiraAPI.Example.Modifiers.Freezer; +using MiraAPI.Example.Options.Roles; using MiraAPI.Example.Roles; +using MiraAPI.GameOptions; using MiraAPI.Hud; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; @@ -11,9 +13,9 @@ namespace MiraAPI.Example.Buttons.Freezer; public class FreezeButton : CustomActionButton { public override string Name => "Freeze"; - public override float Cooldown => 5f; + public override float Cooldown => OptionGroupSingleton.Instance.FreezeDuration; public override float EffectDuration => 0f; - public override int MaxUses => 0; + public override int MaxUses => (int)OptionGroupSingleton.Instance.FreezeUses; public override LoadableAsset Sprite => ExampleAssets.ExampleButton; protected override void OnClick() @@ -23,7 +25,7 @@ public class FreezeButton : CustomActionButton public override PlayerControl? GetTarget() { - return PlayerControl.LocalPlayer.GetClosestPlayer(true, Distance, false); + return PlayerControl.LocalPlayer.GetClosestPlayer(true, Distance); } public override void SetOutline(bool active) diff --git a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs index 99199b8..3a8d810 100644 --- a/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs +++ b/MiraAPI.Example/Buttons/Teleporter/TeleportButton.cs @@ -13,7 +13,7 @@ public class TeleportButton : CustomActionButton { public override string Name => "Teleport"; - public override float Cooldown => OptionGroupSingleton.Instance.TeleportCooldown; + public override float Cooldown => OptionGroupSingleton.Instance.TeleportCooldown.Value; public override float EffectDuration => OptionGroupSingleton.Instance.TeleportDuration; @@ -22,7 +22,7 @@ public class TeleportButton : CustomActionButton public override LoadableAsset Sprite => ExampleAssets.TeleportButton; public static bool IsZoom { get; private set; } - public override bool Enabled(RoleBehaviour role) + public override bool Enabled(RoleBehaviour? role) { return role is TeleporterRole; } diff --git a/MiraAPI.Example/ExampleAssets.cs b/MiraAPI.Example/ExampleAssets.cs index a96dfc8..732d9f7 100644 --- a/MiraAPI.Example/ExampleAssets.cs +++ b/MiraAPI.Example/ExampleAssets.cs @@ -6,7 +6,7 @@ public static class ExampleAssets { public static LoadableResourceAsset ExampleButton { get; } = new("MiraAPI.Example.Resources.ExampleButton.png"); - // Credit to EpicHorrors for the teleport button asset. + // Credit to EpicHorrors for the teleport button asset. public static LoadableResourceAsset TeleportButton { get; } = new("MiraAPI.Example.Resources.TeleportButton.png"); public static LoadableResourceAsset Banner { get; } = new("MiraAPI.Example.Resources.FortniteBanner.jpeg"); -} \ No newline at end of file +} diff --git a/MiraAPI.Example/ExampleColors.cs b/MiraAPI.Example/ExampleColors.cs index 3212093..6833d3c 100644 --- a/MiraAPI.Example/ExampleColors.cs +++ b/MiraAPI.Example/ExampleColors.cs @@ -6,9 +6,9 @@ namespace MiraAPI.Example; [RegisterCustomColors] public static class ExampleColors { - public static CustomColor Cerulean { get; } = new("Cerulean", new Color(0.0f, 0.48f, 0.65f)); + public static CustomColor Cerulean { get; } = new("Cerulean", new Color(0.0f, 0.48f, 0.65f)); public static CustomColor Rose { get; } = new("Rose", new Color(0.98f, 0.26f, 0.62f)); - + public static CustomColor Gold { get; } = new("Gold", new Color(1.0f, 0.84f, 0.0f)); -} \ No newline at end of file +} diff --git a/MiraAPI.Example/MiraDebugWindow.cs b/MiraAPI.Example/MiraDebugWindow.cs index 9b0fa7e..d0726e7 100644 --- a/MiraAPI.Example/MiraDebugWindow.cs +++ b/MiraAPI.Example/MiraDebugWindow.cs @@ -1,23 +1,21 @@ -using Reactor.Utilities.Attributes; +using System; +using Il2CppInterop.Runtime.Attributes; +using Reactor.Utilities.Attributes; using Reactor.Utilities.ImGui; -using System; using UnityEngine; namespace MiraAPI.Example; + [RegisterInIl2Cpp] -public class MiraDebugWindow(IntPtr ptr) : MonoBehaviour(ptr) +public class MiraDebugWindow(IntPtr cppPtr) : MonoBehaviour(cppPtr) { - public readonly DragWindow DebuggingWindow = new(new Rect(10, 10, 0, 0), "MIRA API DEBUGGING", () => - { - if (GUILayout.Button("Test modifier")) + [HideFromIl2Cpp] + public DragWindow DebuggingWindow { get; } = new( + new Rect(10, 10, 0, 0), + "MIRA API DEBUGGING", + () => { - //PlayerControl.LocalPlayer.AddModifier(); - } - if (GUILayout.Button("Remove modifier")) - { - //PlayerControl.LocalPlayer.RemoveModifier(); - } - }) + }) { Enabled = true, }; diff --git a/MiraAPI.Example/Options/ExampleOptions2.cs b/MiraAPI.Example/Options/ExampleOptions2.cs index c521ea8..56dd209 100644 --- a/MiraAPI.Example/Options/ExampleOptions2.cs +++ b/MiraAPI.Example/Options/ExampleOptions2.cs @@ -8,6 +8,8 @@ public class ExampleOptions2 : AbstractOptionGroup { public override string GroupName => "Example Options 2"; + public override uint GroupPriority => 0; // This group will be displayed first. The default value is uint.MaxValue. + public ModdedToggleOption ToggleOpt1 { get; } = new("Toggle Option 1", false); public ModdedToggleOption ToggleOpt2 { get; } = new("Toggle Option 2", false) diff --git a/MiraAPI.Example/Options/Roles/CustomRole2Settings.cs b/MiraAPI.Example/Options/Roles/CustomRole2Settings.cs deleted file mode 100644 index e10931b..0000000 --- a/MiraAPI.Example/Options/Roles/CustomRole2Settings.cs +++ /dev/null @@ -1,16 +0,0 @@ -using MiraAPI.Example.Roles; -using MiraAPI.GameOptions; -using MiraAPI.GameOptions.Attributes; -using System; - -namespace MiraAPI.Example.Options.Roles; - -public class CustomRole2Settings : AbstractOptionGroup -{ - public override string GroupName => "Custom Role"; - - public override Type AdvancedRole => typeof(FreezerRole); - - [ModdedToggleOption("Teleportation ability")] public bool Teleport { get; set; } = true; - -} \ No newline at end of file diff --git a/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs new file mode 100644 index 0000000..0e26665 --- /dev/null +++ b/MiraAPI.Example/Options/Roles/FreezerRoleSettings.cs @@ -0,0 +1,20 @@ +using MiraAPI.Example.Roles; +using MiraAPI.GameOptions; +using MiraAPI.GameOptions.Attributes; +using System; +using MiraAPI.Utilities; + +namespace MiraAPI.Example.Options.Roles; + +public class FreezerRoleSettings : AbstractOptionGroup +{ + public override string GroupName => "Custom Role"; + + public override Type AdvancedRole => typeof(FreezerRole); + + [ModdedNumberOption("Freeze Duration", 1, 15, 1, MiraNumberSuffixes.Seconds)] + public float FreezeDuration { get; set; } = 5; + + [ModdedNumberOption("Freeze Uses", 1, 5)] + public float FreezeUses { get; set; } = 1; +} diff --git a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs index b8bb823..34ccb94 100644 --- a/MiraAPI.Example/Options/Roles/TeleporterOptions.cs +++ b/MiraAPI.Example/Options/Roles/TeleporterOptions.cs @@ -1,8 +1,9 @@ -using MiraAPI.Example.Roles; +using System; +using MiraAPI.Example.Roles; using MiraAPI.GameOptions; using MiraAPI.GameOptions.Attributes; +using MiraAPI.GameOptions.OptionTypes; using MiraAPI.Utilities; -using System; namespace MiraAPI.Example.Options.Roles; @@ -12,8 +13,7 @@ public class TeleporterOptions : AbstractOptionGroup public override Type AdvancedRole => typeof(TeleporterRole); - [ModdedNumberOption("Teleport Cooldown", 5, 60, 2.5f, MiraNumberSuffixes.Seconds)] - public float TeleportCooldown { get; set; } = 5; + public ModdedNumberOption TeleportCooldown { get; set; } = new("Teleport Cooldown", 10, 5, 60, 2.5f, MiraNumberSuffixes.Seconds); [ModdedNumberOption("Teleport Duration", 5, 25, 1, MiraNumberSuffixes.Seconds)] public float TeleportDuration { get; set; } = 10; diff --git a/MiraAPI.Example/Roles/NeutralKillerRole.cs b/MiraAPI.Example/Roles/NeutralKillerRole.cs new file mode 100644 index 0000000..820c334 --- /dev/null +++ b/MiraAPI.Example/Roles/NeutralKillerRole.cs @@ -0,0 +1,27 @@ +using MiraAPI.Roles; +using UnityEngine; + +namespace MiraAPI.Example.Roles; + +[RegisterCustomRole] +public class NeutralKillerRole : ImpostorRole, ICustomRole +{ + public string RoleName => "Neutral Killer"; + public string RoleDescription => "Neutral who can kill."; + public string RoleLongDescription => RoleDescription; + public Color RoleColor => Color.magenta; + public ModdedRoleTeams Team => ModdedRoleTeams.Neutral; + public bool UseVanillaKillButton => true; + public bool CanGetKilled => true; + public bool CanUseVent => true; + + public override void SpawnTaskHeader(PlayerControl playerControl) + { + // remove existing task header. + } + + public override bool DidWin(GameOverReason gameOverReason) + { + return GameManager.Instance.DidHumansWin(gameOverReason); + } +} diff --git a/MiraAPI/GameModes/CustomGameModeManager.cs b/MiraAPI/GameModes/CustomGameModeManager.cs index 028eb94..388eb5d 100644 --- a/MiraAPI/GameModes/CustomGameModeManager.cs +++ b/MiraAPI/GameModes/CustomGameModeManager.cs @@ -6,12 +6,12 @@ using System.Linq; namespace MiraAPI.GameModes; /// -/// Manages custom gamemodes +/// Manages custom gamemodes. /// public static class CustomGameModeManager { /// - /// List of registered gamemodes + /// List of registered gamemodes. /// internal static readonly Dictionary GameModes = []; @@ -21,14 +21,14 @@ public static class CustomGameModeManager } /// - /// Current gamemode + /// Current gamemode. /// public static CustomGameMode? ActiveMode { get; internal set; } = new DefaultMode(); /// - /// Set current gamemode + /// Set current gamemode. /// - /// gamemode ID + /// gamemode ID. public static void SetGameMode(int id) { if (GameModes.TryGetValue(id, out var gameMode)) @@ -41,9 +41,9 @@ public static class CustomGameModeManager } /// - /// Register gamemode from type + /// Register gamemode from type. /// - /// Type of gamemode class, should inherit from + /// Type of gamemode class, should inherit from . internal static void RegisterGameMode(Type gameModeType) { if (!typeof(CustomGameMode).IsAssignableFrom(gameModeType)) @@ -59,7 +59,7 @@ public static class CustomGameModeManager Logger.Error($"Failed to create instance of {gameModeType.Name}"); return; } - + if (GameModes.Any(x => x.Key == gameMode.Id)) { Logger.Error($"ID for gamemode {gameMode.Name} already exists!"); @@ -68,4 +68,4 @@ public static class CustomGameModeManager GameModes.Add(gameMode.Id, gameMode); } -} \ No newline at end of file +} diff --git a/MiraAPI/GameModes/DefaultMode.cs b/MiraAPI/GameModes/DefaultMode.cs index 2e7fcb0..73c4ca8 100644 --- a/MiraAPI/GameModes/DefaultMode.cs +++ b/MiraAPI/GameModes/DefaultMode.cs @@ -6,4 +6,4 @@ public class DefaultMode : CustomGameMode public override string Name => "Default"; public override string Description => "Default Among Us GameMode"; public override int Id => 0; -} \ No newline at end of file +} diff --git a/MiraAPI/GameModes/RegisterGameModeAttribute.cs b/MiraAPI/GameModes/RegisterGameModeAttribute.cs index b329d53..4c66c35 100644 --- a/MiraAPI/GameModes/RegisterGameModeAttribute.cs +++ b/MiraAPI/GameModes/RegisterGameModeAttribute.cs @@ -25,4 +25,4 @@ public class RegisterGameModeAttribute : Attribute } } } -} \ No newline at end of file +} diff --git a/MiraAPI/GameOptions/AbstractOptionGroup.cs b/MiraAPI/GameOptions/AbstractOptionGroup.cs index aeae257..418f25b 100644 --- a/MiraAPI/GameOptions/AbstractOptionGroup.cs +++ b/MiraAPI/GameOptions/AbstractOptionGroup.cs @@ -26,6 +26,12 @@ public abstract class AbstractOptionGroup /// public virtual Color GroupColor => Color.clear; + /// + /// Gets the group priority. This is used to determine the order in which groups are displayed in the options menu. + /// Zero is the highest priority, and the default value is the max uint value. + /// + public virtual uint GroupPriority => uint.MaxValue; + /// /// Gets the role the group is associated with. This is used for the advanced role options menu. /// diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs index 4f7e950..544d64b 100644 --- a/MiraAPI/GameOptions/IModdedOption.cs +++ b/MiraAPI/GameOptions/IModdedOption.cs @@ -17,7 +17,7 @@ internal interface IModdedOption OptionBehaviour? OptionBehaviour { get; } Func Visible { get; set; } ConfigDefinition? ConfigDefinition { get; set; } - OptionBehaviour? CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container); + OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container); float GetFloatData(); NetData GetNetData(); void HandleNetData(byte[] data); diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs index b57528e..1300d61 100644 --- a/MiraAPI/GameOptions/ModdedOptionsManager.cs +++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs @@ -18,10 +18,10 @@ namespace MiraAPI.GameOptions; /// public static class ModdedOptionsManager { - private static readonly Dictionary OptionAttributes = new(); - private static readonly Dictionary TypeToGroup = new(); + private static readonly Dictionary OptionAttributes = []; + private static readonly Dictionary TypeToGroup = []; - internal static readonly Dictionary ModdedOptions = new(); + internal static readonly Dictionary ModdedOptions = []; internal static readonly List Groups = []; internal static uint NextId => _nextId++; diff --git a/MiraAPI/GameOptions/OptionGroupSingleton.cs b/MiraAPI/GameOptions/OptionGroupSingleton.cs index 03a11fa..53b2029 100644 --- a/MiraAPI/GameOptions/OptionGroupSingleton.cs +++ b/MiraAPI/GameOptions/OptionGroupSingleton.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; namespace MiraAPI.GameOptions; @@ -7,13 +6,14 @@ namespace MiraAPI.GameOptions; /// Singleton for option groups. /// /// The option group type. -public class OptionGroupSingleton where T : AbstractOptionGroup +public static class OptionGroupSingleton where T : AbstractOptionGroup { private static T? _instance; /// /// Gets the instance of the option group. /// - /// Can't +#pragma warning disable CA1000 public static T Instance => _instance ??= ModdedOptionsManager.Groups.OfType().Single(); +#pragma warning restore CA1000 } diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index c3f0f5e..7a01f19 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -1,8 +1,8 @@ -using MiraAPI.Networking; -using Reactor.Localization.Utilities; -using System; +using System; using System.Linq; using Il2CppInterop.Runtime.InteropTypes.Arrays; +using MiraAPI.Networking; +using Reactor.Localization.Utilities; using UnityEngine; using Object = UnityEngine.Object; diff --git a/MiraAPI/Hud/CustomButtonSingleton.cs b/MiraAPI/Hud/CustomButtonSingleton.cs index 3e732be..b338e61 100644 --- a/MiraAPI/Hud/CustomButtonSingleton.cs +++ b/MiraAPI/Hud/CustomButtonSingleton.cs @@ -13,5 +13,7 @@ public static class CustomButtonSingleton where T : CustomActionButton /// /// Gets the instance of the button. /// +#pragma warning disable CA1000 public static T Instance => _instance ??= CustomButtonManager.CustomButtons.OfType().Single(); +#pragma warning restore CA1000 } diff --git a/MiraAPI/Hud/RegisterButtonAttribute.cs b/MiraAPI/Hud/RegisterButtonAttribute.cs index feda3a8..82e0ca5 100644 --- a/MiraAPI/Hud/RegisterButtonAttribute.cs +++ b/MiraAPI/Hud/RegisterButtonAttribute.cs @@ -6,4 +6,4 @@ namespace MiraAPI.Hud; /// Attribute to register a button in the HUD. Necessary for the button to be recognized by Mira. /// [AttributeUsage(AttributeTargets.Class)] -public class RegisterButtonAttribute : Attribute; \ No newline at end of file +public class RegisterButtonAttribute : Attribute; diff --git a/MiraAPI/MiraApiPlugin.cs b/MiraAPI/MiraApiPlugin.cs index b928565..cfe314d 100644 --- a/MiraAPI/MiraApiPlugin.cs +++ b/MiraAPI/MiraApiPlugin.cs @@ -16,10 +16,10 @@ namespace MiraAPI; [ReactorModFlags(ModFlags.RequireOnAllClients)] public partial class MiraApiPlugin : BasePlugin { - public Harmony Harmony { get; } = new(Id); - private static MiraPluginManager PluginManager { get; set; } + private static MiraPluginManager? PluginManager { get; set; } - public static Color MiraColor = new Color32(238, 154, 112, 255); + public Harmony Harmony { get; } = new(Id); + public static Color MiraColor { get; } = new Color32(238, 154, 112, 255); public override void Load() { diff --git a/MiraAPI/Modifiers/BaseModifier.cs b/MiraAPI/Modifiers/BaseModifier.cs index af9b491..9d6e82c 100644 --- a/MiraAPI/Modifiers/BaseModifier.cs +++ b/MiraAPI/Modifiers/BaseModifier.cs @@ -10,6 +10,11 @@ public abstract class BaseModifier /// public PlayerControl? Player { get; internal set; } + /// + /// Gets the modifier component that the modifier is attached to. + /// + public ModifierComponent? ModifierComponent { get; internal set; } + /// /// Gets the modifier id. /// @@ -62,7 +67,8 @@ public abstract class BaseModifier /// /// Called when the player dies. /// - public virtual void OnDeath() + /// The Death Reason. + public virtual void OnDeath(DeathReason reason) { } diff --git a/MiraAPI/Modifiers/ModifierComponent.cs b/MiraAPI/Modifiers/ModifierComponent.cs index eecda1a..8b32dc2 100644 --- a/MiraAPI/Modifiers/ModifierComponent.cs +++ b/MiraAPI/Modifiers/ModifierComponent.cs @@ -18,7 +18,7 @@ namespace MiraAPI.Modifiers; /// The component for handling modifiers. /// [RegisterInIl2Cpp] -public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) +public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr) { /// /// Gets the active modifiers on the player. @@ -213,6 +213,7 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr) _toAdd.Add(modifier); modifier.Player = _player; + modifier.ModifierComponent = this; modifier.ModifierId = modifierId.Value; return modifier; } diff --git a/MiraAPI/Modifiers/RegisterModifierAttribute.cs b/MiraAPI/Modifiers/RegisterModifierAttribute.cs index 4c2fb56..4858b96 100644 --- a/MiraAPI/Modifiers/RegisterModifierAttribute.cs +++ b/MiraAPI/Modifiers/RegisterModifierAttribute.cs @@ -2,5 +2,8 @@ namespace MiraAPI.Modifiers; +/// +/// Marks a class as a modifier that can be registered with the . +/// [AttributeUsage(AttributeTargets.Class)] -public class RegisterModifierAttribute : Attribute; \ No newline at end of file +public class RegisterModifierAttribute : Attribute; diff --git a/MiraAPI/Modifiers/Types/TimedModifier.cs b/MiraAPI/Modifiers/Types/TimedModifier.cs index f18b136..b189b47 100644 --- a/MiraAPI/Modifiers/Types/TimedModifier.cs +++ b/MiraAPI/Modifiers/Types/TimedModifier.cs @@ -1,5 +1,4 @@ using System; -using MiraAPI.Utilities; using Reactor.Utilities; using UnityEngine; @@ -71,7 +70,7 @@ public abstract class TimedModifier : BaseModifier if (RemoveOnComplete) { - Player?.RpcRemoveModifier(ModifierId); + ModifierComponent?.RemoveModifier(ModifierId); } } } diff --git a/MiraAPI/Networking/CustomMurderRpc.cs b/MiraAPI/Networking/CustomMurderRpc.cs new file mode 100644 index 0000000..619a0ab --- /dev/null +++ b/MiraAPI/Networking/CustomMurderRpc.cs @@ -0,0 +1,271 @@ +using System.Collections; +using System.Linq; +using AmongUs.GameOptions; +using Assets.CoreScripts; +using BepInEx.Unity.IL2CPP.Utils; +using Reactor.Networking.Attributes; +using Reactor.Utilities; +using Reactor.Utilities.Extensions; +using UnityEngine; + +namespace MiraAPI.Networking; + +/// +/// Custom murder RPCs to fix issues with default ones. +/// +public static class CustomMurderRpc +{ + /// + /// Networked Custom Murder method. + /// + /// The killer. + /// The player to murder. + /// Whether the murder was successful or not. + /// Should the kill timer be reset. + /// Should a dead body be created. + /// Should the killer be snapped to the dead player. + /// Should the kill animation be shown. + /// Should the kill sound be played. + [MethodRpc((uint)MiraRpc.CustomMurder)] + public static void RpcCustomMurder( + this PlayerControl source, + PlayerControl target, + bool didSucceed = true, + bool resetKillTimer = true, + bool createDeadBody = true, + bool teleportMurderer = true, + bool showKillAnim = true, + bool playKillSound = true) + { + var murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError; + var murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags; + + source.CustomMurder( + target, + murderResultFlags2, + resetKillTimer, + createDeadBody, + teleportMurderer, + showKillAnim, + playKillSound); + } + + /// + /// Custom Murder method without networking. If you need a networked version, use . + /// + /// The killer. + /// The player to murder. + /// Murder result flags. + /// Should the kill timer be reset. + /// Should a dead body be created. + /// Should the killer be snapped to the dead player. + /// Should the kill animation be shown. + /// Should the kill sound be played. + public static void CustomMurder( + this PlayerControl source, + PlayerControl target, + MurderResultFlags resultFlags, + bool resetKillTimer = true, + bool createDeadBody = true, + bool teleportMurderer = true, + bool showKillAnim = true, + bool playKillSound = true) + { + source.isKilling = false; + Logger.Error($"{source.PlayerId} trying to murder {target.PlayerId}"); + var data = target.Data; + if (resultFlags.HasFlag(MurderResultFlags.FailedError)) + { + return; + } + + if (resultFlags.HasFlag(MurderResultFlags.FailedProtected) || + (resultFlags.HasFlag(MurderResultFlags.DecisionByHost) && target.protectedByGuardianId > -1)) + { + target.protectedByGuardianThisRound = true; + var flag = PlayerControl.LocalPlayer.Data.Role.Role == RoleTypes.GuardianAngel; + if (flag && PlayerControl.LocalPlayer.Data.PlayerId == target.protectedByGuardianId) + { + StatsManager.Instance.IncrementStat(StringNames.StatsGuardianAngelCrewmatesProtected); + DestroyableSingleton.Instance.OnProtectACrewmate(); + } + + if (source.AmOwner || flag) + { + target.ShowFailedMurder(); + + if (resetKillTimer) + { + source.SetKillTimer( + GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown) / 2f); + } + } + else + { + target.RemoveProtection(); + } + + Logger.Error($"{source.PlayerId} failed to murder {target.PlayerId} due to guardian angel protection"); + return; + } + + if (!resultFlags.HasFlag(MurderResultFlags.Succeeded) && + !resultFlags.HasFlag(MurderResultFlags.DecisionByHost)) + { + return; + } + + DestroyableSingleton.Instance.Analytics.Kill(target.Data, source.Data); + if (source.AmOwner) + { + StatsManager.Instance.IncrementStat( + GameManager.Instance.IsHideAndSeek() + ? StringNames.StatsImpostorKills_HideAndSeek + : StringNames.StatsImpostorKills); + + if (source.CurrentOutfitType == PlayerOutfitType.Shapeshifted) + { + StatsManager.Instance.IncrementStat(StringNames.StatsShapeshifterShiftedKills); + } + + if (Constants.ShouldPlaySfx() && playKillSound) + { + SoundManager.Instance.PlaySound(source.KillSfx, false, 0.8f); + } + + if (resetKillTimer) + { + source.SetKillTimer(GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown)); + } + } + + DestroyableSingleton.Instance.WriteMurder(); + target.gameObject.layer = LayerMask.NameToLayer("Ghost"); + if (target.AmOwner) + { + StatsManager.Instance.IncrementStat(StringNames.StatsTimesMurdered); + if (Minigame.Instance) + { + try + { + Minigame.Instance.Close(); + Minigame.Instance.Close(); + } + catch + { + // ignored + } + } + + if (showKillAnim) + { + DestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(source.Data, data); + } + + target.cosmetics.SetNameMask(false); + target.RpcSetScanner(false); + } + + DestroyableSingleton.Instance.OnMurder( + source.AmOwner, + target.AmOwner, + source.CurrentOutfitType == PlayerOutfitType.Shapeshifted, + source.shapeshiftTargetPlayerId, + target.PlayerId); + source.MyPhysics.StartCoroutine(source.KillAnimations.Random()?.CoPerformCustomKill(source, target, createDeadBody, teleportMurderer)); + Logger.Error($"{source.PlayerId} succeeded in murdering {target.PlayerId}"); + } + + /// + /// Perform a custom kill animation. + /// + /// The kill animation. + /// The murderer. + /// The murdered player. + /// Should a dead body be created. + /// Should the murder be teleported. + /// Coroutine. + public static IEnumerator CoPerformCustomKill( + this KillAnimation anim, + PlayerControl source, + PlayerControl target, + bool createDeadBody = true, + bool teleportMurderer = true) + { + var cam = Camera.main?.GetComponent(); + var isParticipant = PlayerControl.LocalPlayer == source || PlayerControl.LocalPlayer == target; + var sourcePhys = source.MyPhysics; + + if (teleportMurderer) + { + KillAnimation.SetMovement(source, false); + } + + KillAnimation.SetMovement(target, false); + + if (isParticipant) + { + PlayerControl.LocalPlayer.isKilling = true; + source.isKilling = true; + } + + DeadBody? deadBody = null; + + if (createDeadBody) + { + deadBody = Object.Instantiate(GameManager.Instance.DeadBodyPrefab); + deadBody.enabled = false; + deadBody.ParentId = target.PlayerId; + deadBody.bodyRenderers.ToList().ForEach(target.SetPlayerMaterialColors); + + target.SetPlayerMaterialColors(deadBody.bloodSplatter); + var vector = target.transform.position + anim.BodyOffset; + vector.z = vector.y / 1000f; + deadBody.transform.position = vector; + } + + if (isParticipant) + { + if (cam != null) + { + cam.Locked = true; + } + + ConsoleJoystick.SetMode_Task(); + if (PlayerControl.LocalPlayer.AmOwner) + { + PlayerControl.LocalPlayer.MyPhysics.inputHandler.enabled = true; + } + } + + target.Die(DeathReason.Kill, true); + yield return source.MyPhysics.Animations.CoPlayCustomAnimation(anim.BlurAnim); + sourcePhys.Animations.PlayIdleAnimation(); + + if (teleportMurderer) + { + source.NetTransform.SnapTo(target.transform.position); + KillAnimation.SetMovement(source, true); + } + + KillAnimation.SetMovement(target, true); + + if (deadBody != null) + { + deadBody.enabled = true; + } + + if (!isParticipant) + { + yield break; + } + + if (cam != null) + { + cam.Locked = false; + } + + PlayerControl.LocalPlayer.isKilling = false; + source.isKilling = false; + } +} diff --git a/MiraAPI/Networking/CustomMurderRpcs.cs b/MiraAPI/Networking/CustomMurderRpcs.cs deleted file mode 100644 index 17fb4ae..0000000 --- a/MiraAPI/Networking/CustomMurderRpcs.cs +++ /dev/null @@ -1,186 +0,0 @@ -using AmongUs.GameOptions; -using Assets.CoreScripts; -using Reactor.Networking.Attributes; -using Reactor.Utilities; -using Reactor.Utilities.Extensions; -using System.Collections; -using System.Linq; -using UnityEngine; - -namespace MiraAPI.Networking; -public static class CustomMurderRpcs -{ - [MethodRpc((uint)MiraRpc.CustomMurder)] - public static void RpcCustomMurder(this PlayerControl source, PlayerControl target, bool didSucceed = true, - bool resetKillTimer = true, bool createDeadBody = true, bool teleportMurderer = true, bool showKillAnim = true, bool playKillSound = true) - { - var murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError; - var murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags; - - source.CustomMurder(target, murderResultFlags2, resetKillTimer, createDeadBody, teleportMurderer, showKillAnim, playKillSound); - } - - public static void CustomMurder(this PlayerControl source, PlayerControl target, MurderResultFlags resultFlags, - bool resetKillTimer = true, bool createDeadBody = true, bool teleportMurderer = true, bool showKillAnim = true, bool playKillSound = true) - { - source.isKilling = false; - source.logger.Debug(string.Format("{0} trying to murder {1}", source.PlayerId, target.PlayerId), null); - var data = target.Data; - if (resultFlags.HasFlag(MurderResultFlags.FailedError)) - { - return; - } - if (resultFlags.HasFlag(MurderResultFlags.FailedProtected) || (resultFlags.HasFlag(MurderResultFlags.DecisionByHost) && target.protectedByGuardianId > -1)) - { - target.protectedByGuardianThisRound = true; - var flag = PlayerControl.LocalPlayer.Data.Role.Role == RoleTypes.GuardianAngel; - if (flag && PlayerControl.LocalPlayer.Data.PlayerId == target.protectedByGuardianId) - { - StatsManager.Instance.IncrementStat(StringNames.StatsGuardianAngelCrewmatesProtected); - DestroyableSingleton.Instance.OnProtectACrewmate(); - } - if (source.AmOwner || flag) - { - target.ShowFailedMurder(); - - if (resetKillTimer) - { - source.SetKillTimer(GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown) / 2f); - } - } - else - { - target.RemoveProtection(); - } - source.logger.Debug(string.Format("{0} failed to murder {1} due to guardian angel protection", source.PlayerId, target.PlayerId), null); - return; - } - if (resultFlags.HasFlag(MurderResultFlags.Succeeded) || resultFlags.HasFlag(MurderResultFlags.DecisionByHost)) - { - DestroyableSingleton.Instance.Analytics.Kill(target.Data, source.Data); - if (source.AmOwner) - { - if (GameManager.Instance.IsHideAndSeek()) - { - StatsManager.Instance.IncrementStat(StringNames.StatsImpostorKills_HideAndSeek); - } - else - { - StatsManager.Instance.IncrementStat(StringNames.StatsImpostorKills); - } - if (source.CurrentOutfitType == PlayerOutfitType.Shapeshifted) - { - StatsManager.Instance.IncrementStat(StringNames.StatsShapeshifterShiftedKills); - } - if (Constants.ShouldPlaySfx() && playKillSound) - { - SoundManager.Instance.PlaySound(source.KillSfx, false, 0.8f, null); - } - - if (resetKillTimer) - { - source.SetKillTimer(GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown)); - } - } - DestroyableSingleton.Instance.WriteMurder(); - target.gameObject.layer = LayerMask.NameToLayer("Ghost"); - if (target.AmOwner) - { - StatsManager.Instance.IncrementStat(StringNames.StatsTimesMurdered); - if (Minigame.Instance) - { - try - { - Minigame.Instance.Close(); - Minigame.Instance.Close(); - } - catch - { - } - } - - if (showKillAnim) - { - DestroyableSingleton.Instance.KillOverlay.ShowKillAnimation(source.Data, data); - } - - target.cosmetics.SetNameMask(false); - target.RpcSetScanner(false); - } - DestroyableSingleton.Instance.OnMurder(source.AmOwner, target.AmOwner, source.CurrentOutfitType == PlayerOutfitType.Shapeshifted, source.shapeshiftTargetPlayerId, (int)target.PlayerId); - Coroutines.Start(source.KillAnimations.Random().CoPerformCustomKill(source, target, createDeadBody, teleportMurderer)); - source.logger.Debug(string.Format("{0} succeeded in murdering {1}", source.PlayerId, target.PlayerId), null); - } - } - - public static IEnumerator CoPerformCustomKill(this KillAnimation anim, PlayerControl source, PlayerControl target, - bool createDeadBody = true, bool teleportMurderer = true) - { - var cam = Camera.main.GetComponent(); - var isParticipant = PlayerControl.LocalPlayer == source || PlayerControl.LocalPlayer == target; - var sourcePhys = source.MyPhysics; - - if (teleportMurderer) - { - KillAnimation.SetMovement(source, false); - } - - KillAnimation.SetMovement(target, false); - - if (isParticipant) - { - PlayerControl.LocalPlayer.isKilling = true; - source.isKilling = true; - } - - DeadBody deadBody = null; - - if (createDeadBody) - { - deadBody = Object.Instantiate(GameManager.Instance.DeadBodyPrefab); - deadBody.enabled = false; - deadBody.ParentId = target.PlayerId; - deadBody.bodyRenderers.ToArray().ToList().ForEach(target.SetPlayerMaterialColors); - - target.SetPlayerMaterialColors(deadBody.bloodSplatter); - var vector = target.transform.position + anim.BodyOffset; - vector.z = vector.y / 1000f; - deadBody.transform.position = vector; - } - - if (isParticipant) - { - cam.Locked = true; - ConsoleJoystick.SetMode_Task(); - if (PlayerControl.LocalPlayer.AmOwner) - { - PlayerControl.LocalPlayer.MyPhysics.inputHandler.enabled = true; - } - } - - target.Die(DeathReason.Kill, true); - yield return source.MyPhysics.Animations.CoPlayCustomAnimation(anim.BlurAnim); - sourcePhys.Animations.PlayIdleAnimation(); - - if (teleportMurderer) - { - source.NetTransform.SnapTo(target.transform.position); - KillAnimation.SetMovement(source, true); - } - KillAnimation.SetMovement(target, true); - - if (deadBody) - { - deadBody.enabled = true; - } - - if (isParticipant) - { - cam.Locked = false; - PlayerControl.LocalPlayer.isKilling = false; - source.isKilling = false; - } - - yield break; - } -} \ No newline at end of file diff --git a/MiraAPI/Networking/MiraRpc.cs b/MiraAPI/Networking/MiraRpc.cs index 40861ce..5126dea 100644 --- a/MiraAPI/Networking/MiraRpc.cs +++ b/MiraAPI/Networking/MiraRpc.cs @@ -1,11 +1,37 @@ namespace MiraAPI.Networking; +/// +/// Mira RPCs. +/// public enum MiraRpc : uint { + /// + /// Syncs the game options. + /// SyncGameOptions, + + /// + /// Syncs the role options. + /// SyncRoleOptions, + + /// + /// Adds a modifier to a player. + /// AddModifier, + + /// + /// Removes a modifier from a player. + /// RemoveModifier, + + /// + /// Syncs all modifiers at once. + /// SyncModifiers, + + /// + /// Custom Murder RPC. + /// CustomMurder, -} \ No newline at end of file +} diff --git a/MiraAPI/Networking/NetData.cs b/MiraAPI/Networking/NetData.cs index 083a9d2..ce5de46 100644 --- a/MiraAPI/Networking/NetData.cs +++ b/MiraAPI/Networking/NetData.cs @@ -1,8 +1,25 @@ namespace MiraAPI.Networking; +/// +/// Used to network data and mark it with an ID. +/// +/// The ID of the data. +/// The byte[] data. public readonly struct NetData(uint id, byte[] data) { + /// + /// Gets the ID of the data. + /// public uint Id { get; } = id; + + /// + /// Gets the byte[] data. + /// public byte[] Data { get; } = data; + + /// + /// Gets the length of the data in bytes. + /// + /// An int representing the number of bytes this NetData takes up. public int GetLength() => 4 + Data.Length; -} \ No newline at end of file +} diff --git a/MiraAPI/Networking/SyncModifiersRpc.cs b/MiraAPI/Networking/SyncModifiersRpc.cs index 69154f9..288e2c1 100644 --- a/MiraAPI/Networking/SyncModifiersRpc.cs +++ b/MiraAPI/Networking/SyncModifiersRpc.cs @@ -6,12 +6,18 @@ using Reactor.Networking.Rpc; namespace MiraAPI.Networking; [RegisterCustomRpc((uint)MiraRpc.SyncModifiers)] -internal class SyncModifiersRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc(plugin, id) +internal sealed class SyncModifiersRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc(plugin, id) { public override RpcLocalHandling LocalHandling => RpcLocalHandling.None; - public override void Write(MessageWriter writer, NetData[] data) + public override void Write(MessageWriter writer, NetData[]? data) { + if (data == null) + { + writer.WritePacked(0U); + return; + } + writer.WritePacked((uint)data.Length); foreach (var netData in data) { @@ -26,21 +32,21 @@ internal class SyncModifiersRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc var data = new NetData[length]; for (var i = 0; i < length; i++) { - var id = reader.ReadPackedUInt32(); + var dataId = reader.ReadPackedUInt32(); var bytes = reader.ReadBytesAndSize(); - data[i] = new NetData(id, bytes); + data[i] = new NetData(dataId, bytes); } return data; } - public override void Handle(PlayerControl playerControl, NetData[] data) + public override void Handle(PlayerControl playerControl, NetData[]? data) { if (AmongUsClient.Instance.HostId != playerControl.OwnerId) { return; } - ModifierManager.HandleSyncModifiers(data); + ModifierManager.HandleSyncModifiers(data ?? []); } -} \ No newline at end of file +} diff --git a/MiraAPI/Networking/SyncOptionsRpc.cs b/MiraAPI/Networking/SyncOptionsRpc.cs index 98efc70..a80ace3 100644 --- a/MiraAPI/Networking/SyncOptionsRpc.cs +++ b/MiraAPI/Networking/SyncOptionsRpc.cs @@ -7,12 +7,18 @@ namespace MiraAPI.Networking; // METHOD RPC DOESNT WORK WITH THE ARRAYS AND STUFF SO THIS IS HOW WE WILL DO IT FOR NOW [RegisterCustomRpc((uint)MiraRpc.SyncGameOptions)] -internal class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc(plugin, id) +internal sealed class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc(plugin, id) { public override RpcLocalHandling LocalHandling => RpcLocalHandling.None; - - public override void Write(MessageWriter writer, NetData[] data) + + public override void Write(MessageWriter writer, NetData[]? data) { + if (data == null) + { + writer.WritePacked(0U); + return; + } + writer.WritePacked((uint)data.Length); foreach (var netData in data) { @@ -27,21 +33,21 @@ internal class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc(plugin, id) +internal sealed class SyncRoleOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc(plugin, id) { - public override RpcLocalHandling LocalHandling => RpcLocalHandling.None; - public override void Write(MessageWriter writer, NetData[] data) + public override void Write(MessageWriter writer, NetData[]? data) { + if (data == null) + { + writer.WritePacked(0U); + return; + } + writer.WritePacked((uint)data.Length); foreach (var netData in data) { @@ -27,21 +32,21 @@ internal class SyncRoleOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomR var data = new NetData[length]; for (var i = 0; i < length; i++) { - var id = reader.ReadPackedUInt32(); + var dataId = reader.ReadPackedUInt32(); var bytes = reader.ReadBytesAndSize(); - data[i] = new NetData(id, bytes); + data[i] = new NetData(dataId, bytes); } return data; } - public override void Handle(PlayerControl playerControl, NetData[] data) + public override void Handle(PlayerControl playerControl, NetData[]? data) { if (AmongUsClient.Instance.HostId != playerControl.OwnerId) { return; } - CustomRoleManager.HandleSyncRoleOptions(data); + CustomRoleManager.HandleSyncRoleOptions(data ?? []); } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/AmongUsClientSyncPatch.cs b/MiraAPI/Patches/AmongUsClientSyncPatch.cs index a25dc3f..3034b42 100644 --- a/MiraAPI/Patches/AmongUsClientSyncPatch.cs +++ b/MiraAPI/Patches/AmongUsClientSyncPatch.cs @@ -6,6 +6,9 @@ using MiraAPI.Roles; namespace MiraAPI.Patches; +/// +/// Sync all options, role settings, and modifiers to the player when they join the game. +/// [HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CreatePlayer))] public static class AmongUsClientSyncPatch { @@ -25,4 +28,4 @@ public static class AmongUsClientSyncPatch CustomRoleManager.SyncAllRoleSettings(clientData.Id); ModifierManager.SyncAllModifiers(clientData.Id); } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Colors/ScrollingColorsPatch.cs b/MiraAPI/Patches/Colors/ScrollingColorsPatch.cs index 9fdc6d4..e89de23 100644 --- a/MiraAPI/Patches/Colors/ScrollingColorsPatch.cs +++ b/MiraAPI/Patches/Colors/ScrollingColorsPatch.cs @@ -7,8 +7,8 @@ namespace MiraAPI.Patches.Colors; [HarmonyPatch(typeof(PlayerTab))] public static class ScrollingColorsPatch { - /// Collider - private static BoxCollider2D _collider; + // Collider + private static BoxCollider2D? _collider; /// /// Add scrolling to the colors tab @@ -20,7 +20,7 @@ public static class ScrollingColorsPatch { return; } - + var tab = PlayerCustomizationMenu.Instance.Tabs[1].Tab; if (__instance.scroller == null) @@ -31,7 +31,7 @@ public static class ScrollingColorsPatch var maskObj = new GameObject { layer = 5, - name = "SpriteMask" + name = "SpriteMask", }; maskObj.transform.SetParent(__instance.transform); maskObj.transform.localPosition = new Vector3(0, 0, 0); @@ -55,4 +55,4 @@ public static class ScrollingColorsPatch __instance.SetScrollerBounds(); } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/GameModes/ConsolePatches.cs b/MiraAPI/Patches/GameModes/ConsolePatches.cs index 50248e7..e2f24b2 100644 --- a/MiraAPI/Patches/GameModes/ConsolePatches.cs +++ b/MiraAPI/Patches/GameModes/ConsolePatches.cs @@ -1,5 +1,4 @@ - -/* +/* [HarmonyPatch] public static class ConsolePatches { @@ -69,4 +68,4 @@ public static class ConsolePatches canUse = couldUse = true; return true; } -}*/ \ No newline at end of file +}*/ diff --git a/MiraAPI/Patches/GameModes/DeadBodyPatch.cs b/MiraAPI/Patches/GameModes/DeadBodyPatch.cs index edd4e58..07a2899 100644 --- a/MiraAPI/Patches/GameModes/DeadBodyPatch.cs +++ b/MiraAPI/Patches/GameModes/DeadBodyPatch.cs @@ -1,6 +1,4 @@ - - -/* +/* [HarmonyPatch(typeof(DeadBody))] public static class DeadBodyPatch { @@ -10,4 +8,4 @@ public static class DeadBodyPatch { return CustomGameModeManager.ActiveMode == null || CustomGameModeManager.ActiveMode.CanReport(__instance); } -}*/ \ No newline at end of file +}*/ diff --git a/MiraAPI/Patches/GameModes/GameLogicPatches.cs b/MiraAPI/Patches/GameModes/GameLogicPatches.cs index 77eca63..a62be83 100644 --- a/MiraAPI/Patches/GameModes/GameLogicPatches.cs +++ b/MiraAPI/Patches/GameModes/GameLogicPatches.cs @@ -1,6 +1,4 @@ - - -/* +/* [HarmonyPatch] public static class GameLogicPatches { @@ -19,4 +17,4 @@ public static class GameLogicPatches CustomGameModeManager.ActiveMode?.AssignRoles(out runOriginal, __instance); return runOriginal; } -}*/ \ No newline at end of file +}*/ diff --git a/MiraAPI/Patches/GameModes/ImpostorTargetPatch.cs b/MiraAPI/Patches/GameModes/ImpostorTargetPatch.cs index 58f1187..ad0d9e0 100644 --- a/MiraAPI/Patches/GameModes/ImpostorTargetPatch.cs +++ b/MiraAPI/Patches/GameModes/ImpostorTargetPatch.cs @@ -1,6 +1,4 @@ - - -/* +/* /// /// Allow Impostors to kill each other if can kill is enabled in gamemode or friendly fire is toggled on /// @@ -16,11 +14,11 @@ public static class ImpostorTargetPatch { return true; } - + __result = target is { Disconnected: false, IsDead: false } && target.PlayerId != __instance.Player.PlayerId && !(target.Role == null) && !(target.Object == null) && !target.Object.inVent && !target.Object.inMovingPlat; return false; } -}*/ \ No newline at end of file +}*/ diff --git a/MiraAPI/Patches/GameModes/MapBehaviourPatch.cs b/MiraAPI/Patches/GameModes/MapBehaviourPatch.cs index 8f17b99..9a4bd8b 100644 --- a/MiraAPI/Patches/GameModes/MapBehaviourPatch.cs +++ b/MiraAPI/Patches/GameModes/MapBehaviourPatch.cs @@ -1,6 +1,4 @@ - - -/* +/* [HarmonyPatch(typeof(MapBehaviour))] public static class MapBehaviourPatch { @@ -17,4 +15,4 @@ public static class MapBehaviourPatch return true; } -}*/ \ No newline at end of file +}*/ diff --git a/MiraAPI/Patches/GameModes/OnDeathPatch.cs b/MiraAPI/Patches/GameModes/OnDeathPatch.cs index 275321d..99f9211 100644 --- a/MiraAPI/Patches/GameModes/OnDeathPatch.cs +++ b/MiraAPI/Patches/GameModes/OnDeathPatch.cs @@ -1,6 +1,4 @@ - - -/* +/* [HarmonyPatch(typeof(KillAnimation))] public static class OnDeathPatch { @@ -10,4 +8,4 @@ public static class OnDeathPatch { CustomGameModeManager.ActiveMode?.OnDeath(target); } -}*/ \ No newline at end of file +}*/ diff --git a/MiraAPI/Patches/GameModes/VentPatches.cs b/MiraAPI/Patches/GameModes/VentPatches.cs index a86aa46..597c40a 100644 --- a/MiraAPI/Patches/GameModes/VentPatches.cs +++ b/MiraAPI/Patches/GameModes/VentPatches.cs @@ -1,10 +1,7 @@ - - -/* +/* [HarmonyPatch(typeof(Vent))] public static class VentPatches { - [HarmonyPrefix] [HarmonyPatch(nameof(Vent.CanUse))] public static bool CanUseVentPatch(Vent __instance, ref float __result, [HarmonyArgument(0)] NetworkedPlayerInfo pc, [HarmonyArgument(1)] ref bool canUse, [HarmonyArgument(2)] ref bool couldUse) @@ -47,5 +44,4 @@ public static class VentPatches return false; } - -}*/ \ No newline at end of file +}*/ diff --git a/MiraAPI/Patches/MeetingHudPatches.cs b/MiraAPI/Patches/Hud/ButtonResetPatches.cs similarity index 56% rename from MiraAPI/Patches/MeetingHudPatches.cs rename to MiraAPI/Patches/Hud/ButtonResetPatches.cs index 6abe4c0..b9756ce 100644 --- a/MiraAPI/Patches/MeetingHudPatches.cs +++ b/MiraAPI/Patches/Hud/ButtonResetPatches.cs @@ -1,20 +1,20 @@ using HarmonyLib; using MiraAPI.Hud; -namespace MiraAPI.Patches; +namespace MiraAPI.Patches.Hud; /// -/// Harmony patches for the MeetingHud class. +/// Reset button patches /// -[HarmonyPatch(typeof(MeetingHud))] -public static class MeetingHudPatches +[HarmonyPatch] +public static class ButtonResetPatches { /// /// Resets the cooldown and effect of all custom buttons when the meeting starts. /// [HarmonyPostfix] - [HarmonyPatch(nameof(MeetingHud.Start))] - public static void StartPostfix() + [HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))] + public static void MeetingHudStartPostfix() { foreach (var customActionButton in CustomButtonManager.CustomButtons) { @@ -23,11 +23,11 @@ public static class MeetingHudPatches } /// - /// Resets the cooldown and effect of all custom buttons when the voting is complete. + /// Resets the cooldown and effect of all custom buttons after the exile screen is closed. /// [HarmonyPostfix] - [HarmonyPatch(nameof(MeetingHud.VotingComplete))] - public static void VotingCompletePostfix() + [HarmonyPatch(typeof(ExileController), nameof(ExileController.ReEnableGameplay))] + public static void ExileControllerReEnableGameplayPostfix() { foreach (var customActionButton in CustomButtonManager.CustomButtons) { diff --git a/MiraAPI/Patches/HudManagerPatches.cs b/MiraAPI/Patches/HudManagerPatches.cs index c3ee9e8..cdef7cc 100644 --- a/MiraAPI/Patches/HudManagerPatches.cs +++ b/MiraAPI/Patches/HudManagerPatches.cs @@ -1,7 +1,5 @@ using HarmonyLib; -using InnerNet; using MiraAPI.Hud; -using MiraAPI.Roles; using Reactor.Utilities.Extensions; using UnityEngine; using Object = UnityEngine.Object; @@ -17,62 +15,6 @@ public static class HudManagerPatches // Custom buttons parent. private static GameObject? _bottomLeft; - // Custom role tab. - private static TaskPanelBehaviour? _roleTab; - - /// - /// Update custom role tab and custom role hud elements. - /// - /// The HudManager instance. - [HarmonyPostfix] - [HarmonyPatch(nameof(HudManager.Update))] - public static void UpdatePostfix(HudManager __instance) - { - var local = PlayerControl.LocalPlayer; - - if (AmongUsClient.Instance.GameState != InnerNetClient.GameStates.Started && !ShipStatus.Instance) - { - return; - } - - // CustomGameModeManager.ActiveMode?.HudUpdate(__instance); - - switch (local?.Data?.Role) - { - case null: - return; - - case ICustomRole customRole: - { - customRole.HudUpdate(__instance); - - if (customRole.RoleHintType != RoleHintType.RoleTab) - { - _roleTab?.gameObject.Destroy(); - return; - } - - if (_roleTab == null) - { - _roleTab = CustomRoleManager.CreateRoleTab(customRole); - } - else - { - CustomRoleManager.UpdateRoleTab(_roleTab, customRole); - } - - break; - } - - default: - if (_roleTab != null) - { - _roleTab.gameObject.Destroy(); - } - break; - } - } - /* /// /// Trigger hudstart on current custom gamemode @@ -136,7 +78,7 @@ public static class HudManagerPatches } /// - /// Set the custom role tab and custom buttons active when the hud is active. + /// Set the custom buttons active when the hud is active. /// /// HudManager instance. /// The local PlayerControl. @@ -151,19 +93,9 @@ public static class HudManagerPatches return; } - if (_roleTab) - { - _roleTab?.gameObject.SetActive(isActive); - } - foreach (var button in CustomButtonManager.CustomButtons) { button.SetActive(isActive, role); } - - if (role is ICustomRole customRole) - { - __instance.ImpostorVentButton.gameObject.SetActive(isActive && customRole.CanUseVent); - } } } diff --git a/MiraAPI/Patches/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs index 01066c3..2250d3d 100644 --- a/MiraAPI/Patches/IntroCutscenePatches.cs +++ b/MiraAPI/Patches/IntroCutscenePatches.cs @@ -19,33 +19,38 @@ public static class IntroCutscenePatches __instance.TeamTitle.text = $"{mode.Name}\n{mode.Description}"; } }*/ - + [HarmonyPrefix] [HarmonyPatch(nameof(IntroCutscene.BeginCrewmate))] public static bool BeginCrewmatePatch(IntroCutscene __instance) { - if (PlayerControl.LocalPlayer.Data.Role is ICustomRole customRole) + if (PlayerControl.LocalPlayer.Data.Role is not ICustomRole customRole) { - if (customRole.Team is not ModdedRoleTeams.Neutral) - { - return true; - } - - var barTransform = __instance.BackgroundBar.transform; - var position = barTransform.position; - position.y -= 0.25f; - barTransform.position = position; - - __instance.BackgroundBar.material.SetColor(ShaderID.Color, Color.gray); - __instance.TeamTitle.text = "OUTCAST"; - __instance.impostorScale = 1f; - __instance.ImpostorText.text = "You are an Outcast. You do not have a team."; - __instance.TeamTitle.color = Color.gray; - - __instance.ourCrewmate = __instance.CreatePlayer(0, Mathf.CeilToInt(7.5f), PlayerControl.LocalPlayer.Data, false); - return false; + return true; } - return true; + + if (customRole.Team is not ModdedRoleTeams.Neutral) + { + return true; + } + + var barTransform = __instance.BackgroundBar.transform; + var position = barTransform.position; + position.y -= 0.25f; + barTransform.position = position; + + __instance.BackgroundBar.material.SetColor(ShaderID.Color, Color.gray); + __instance.TeamTitle.text = "NEUTRAL"; + __instance.impostorScale = 1f; + __instance.ImpostorText.text = "You are Neutral. You do not have a team."; + __instance.TeamTitle.color = Color.gray; + + __instance.ourCrewmate = __instance.CreatePlayer( + 0, + Mathf.CeilToInt(7.5f), + PlayerControl.LocalPlayer.Data, + false); + return false; } /* @@ -55,4 +60,4 @@ public static class IntroCutscenePatches { CustomGameModeManager.ActiveMode?.Initialize(); }*/ -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Modifiers/VentPatches.cs b/MiraAPI/Patches/Modifiers/VentPatches.cs index 6894848..8c6c26a 100644 --- a/MiraAPI/Patches/Modifiers/VentPatches.cs +++ b/MiraAPI/Patches/Modifiers/VentPatches.cs @@ -1,6 +1,5 @@ using HarmonyLib; using MiraAPI.Utilities; -using System.Linq; using UnityEngine; namespace MiraAPI.Patches.Modifiers; @@ -23,10 +22,10 @@ public static class VentPatches switch (role.CanVent) { - case true when modifiers.Any(x => !x.CanVent()): + case true when modifiers.Exists(x => !x.CanVent()): couldUse = canUse = false; return; - case false when modifiers.Any(x => x.CanVent()): + case false when modifiers.Exists(x => x.CanVent()): couldUse = true; break; } @@ -43,4 +42,4 @@ public static class VentPatches } __result = num; } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index 4c47865..517f7d0 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -30,12 +30,14 @@ public static class GameOptionsMenuPatch } var num = 2.1f; - var filteredGroups = GameSettingMenuPatches.SelectedMod.OptionGroups.Where(x => x.GroupVisible.Invoke() && x.AdvancedRole is null); + var filteredGroups = + GameSettingMenuPatches.SelectedMod?.OptionGroups.Where( + x => x.GroupVisible.Invoke() && x.AdvancedRole is null) ?? []; foreach (var group in filteredGroups) { var filteredOpts = group.Options.Where(x => x.Visible.Invoke()).ToList(); - if (filteredOpts.Count == 0) + if (filteredOpts.Count == 0 || group.Header is null) { continue; } @@ -50,7 +52,12 @@ public static class GameOptionsMenuPatch { var newOpt = opt.OptionBehaviour; - if (opt.Visible.Invoke() == false) + if (newOpt is null) + { + continue; + } + + if (!opt.Visible.Invoke()) { newOpt.gameObject.SetActive(false); continue; @@ -70,7 +77,6 @@ public static class GameOptionsMenuPatch } __instance.scrollBar.SetYBoundsMax(-num - 1.65f); - } [HarmonyPrefix] @@ -84,11 +90,15 @@ public static class GameOptionsMenuPatch __instance.MapPicker.gameObject.SetActive(false); - var filteredGroups = GameSettingMenuPatches.SelectedMod.OptionGroups.Where(x => x.AdvancedRole is null); + var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups.Where(x => x.AdvancedRole is null) ?? []; foreach (var group in filteredGroups) { - var categoryHeaderMasked = Object.Instantiate(__instance.categoryHeaderOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer); + var categoryHeaderMasked = Object.Instantiate( + __instance.categoryHeaderOrigin, + Vector3.zero, + Quaternion.identity, + __instance.settingsContainer); categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20); if (group.GroupColor != Color.clear) { @@ -96,17 +106,24 @@ public static class GameOptionsMenuPatch categoryHeaderMasked.Divider.color = group.GroupColor; categoryHeaderMasked.Title.color = group.GroupColor.GetAlternateColor(); } - categoryHeaderMasked.Background.size = new Vector2(categoryHeaderMasked.Background.size.x + 1.5f, categoryHeaderMasked.Background.size.y); + + categoryHeaderMasked.Background.size = new Vector2( + categoryHeaderMasked.Background.size.x + 1.5f, + categoryHeaderMasked.Background.size.y); categoryHeaderMasked.gameObject.SetActive(false); group.Header = categoryHeaderMasked; var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform); - newText.text = $"(Click to open)"; + newText.text = "(Click to close)"; newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f); newText.gameObject.GetComponent().Destroy(); - var options = group.Options.Select(opt => opt.CreateOption(__instance.checkboxOrigin, - __instance.numberOptionOrigin, __instance.stringOptionOrigin, __instance.settingsContainer)); + var options = group.Options.Select( + opt => opt.CreateOption( + __instance.checkboxOrigin, + __instance.numberOptionOrigin, + __instance.stringOptionOrigin, + __instance.settingsContainer)); foreach (var newOpt in options) { @@ -142,8 +159,10 @@ public static class GameOptionsMenuPatch if (newOpt is ToggleOption toggle) { toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset(); - toggle.CheckMark.color = group.GroupColor != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal; - var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite").GetComponent(); + toggle.CheckMark.color = + group.GroupColor != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal; + var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite") + .GetComponent(); rend.sprite = MiraAssets.CheckmarkBox.LoadAsset(); rend.color = group.GroupColor != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal; } @@ -162,11 +181,14 @@ public static class GameOptionsMenuPatch headerBtn.ClickSound = __instance.BackButton.GetComponent().ClickSound; headerBtn.OnMouseOver = new UnityEvent(); headerBtn.OnMouseOut = new UnityEvent(); - headerBtn.OnClick.AddListener((UnityAction)(() => - { - group.AllOptionsHidden = !group.AllOptionsHidden; - newText.text = group.AllOptionsHidden ? "(Click to open)" : "(Click to close)"; - })); + headerBtn.OnClick.AddListener( + (UnityAction)(() => + { + group.AllOptionsHidden = !group.AllOptionsHidden; + newText.text = group.AllOptionsHidden + ? "(Click to open)" + : "(Click to close)"; + })); headerBtn.SetButtonEnableState(true); } @@ -207,4 +229,4 @@ public static class GameOptionsMenuPatch return false; } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs index 13c9036..a44b35c 100644 --- a/MiraAPI/Patches/Options/GameSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/GameSettingMenuPatches.cs @@ -154,7 +154,6 @@ public static class GameSettingMenuPatches } roles.roleChances.Clear(); - roles.roleChances = null; roles.AdvancedRolesSettings.gameObject.SetActive(false); roles.RoleChancesSettings.gameObject.SetActive(true); roles.SetQuotaTab(); diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index a74588e..01c844b 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -16,10 +16,11 @@ namespace MiraAPI.Patches.Options; [HarmonyPatch(typeof(LobbyViewSettingsPane))] public static class LobbyViewPanePatches { - public static int CurrentSelectedMod { get; private set; } - - public static MiraPluginInfo? SelectedMod => CurrentSelectedMod == 0 ? null : MiraPluginManager.Instance.RegisteredPlugins()[CurrentSelectedMod-1]; + private static int CurrentSelectedMod { get; set; } + private static MiraPluginInfo? SelectedMod => CurrentSelectedMod == 0 + ? null + : MiraPluginManager.Instance.RegisteredPlugins()[CurrentSelectedMod - 1]; [HarmonyPostfix] [HarmonyPatch(nameof(LobbyViewSettingsPane.Awake))] @@ -33,92 +34,96 @@ public static class LobbyViewPanePatches nextButton.transform.localPosition = new Vector3(-5.4f, 2.4f, -2f); nextButton.transform.localScale = new Vector3(3, 3, 2); nextButton.name = "RightArrowButton"; - + var normal = nextButton.transform.FindChild("Normal").GetComponentInChildren(); normal.transform.localPosition = new Vector3(0, 0f, 0.3f); normal.sprite = MiraAssets.NextButton.LoadAsset(); - + var hover = nextButton.transform.FindChild("Hover").GetComponentInChildren(); hover.transform.localPosition = new Vector3(0, 0f, 0.3f); hover.sprite = MiraAssets.NextButtonActive.LoadAsset(); - + var passiveButton = nextButton.gameObject.GetComponent(); passiveButton.OnClick = new Button.ButtonClickedEvent(); - passiveButton.OnClick.AddListener((UnityAction)(() => - { - CurrentSelectedMod += 1; - if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins().Length) + passiveButton.OnClick.AddListener( + (UnityAction)(() => { - CurrentSelectedMod = 0; - } - __instance.RefreshTab(); - __instance.scrollBar.ScrollToTop(); - })); + CurrentSelectedMod += 1; + if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins().Length) + { + CurrentSelectedMod = 0; + } + + __instance.RefreshTab(); + __instance.scrollBar.ScrollToTop(); + })); // Create the back button var backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject; backButton.transform.localPosition = new Vector3(-6.3f, 2.4f, -2f); backButton.name = "LeftArrowButton"; backButton.transform.FindChild("Normal").gameObject.GetComponentInChildren().flipX - = backButton.transform.FindChild("Hover").gameObject.GetComponentInChildren().flipX + = backButton.transform.FindChild("Hover").gameObject.GetComponentInChildren().flipX = true; - + var passiveButton2 = backButton.gameObject.GetComponent(); passiveButton2.OnClick = new Button.ButtonClickedEvent(); - passiveButton2.OnClick.AddListener((UnityAction)(() => - { - CurrentSelectedMod -= 1; - if (CurrentSelectedMod < 0) + passiveButton2.OnClick.AddListener( + (UnityAction)(() => { - CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins().Length; - } - __instance.RefreshTab(); - __instance.scrollBar.ScrollToTop(); - })); + CurrentSelectedMod -= 1; + if (CurrentSelectedMod < 0) + { + CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins().Length; + } + + __instance.RefreshTab(); + __instance.scrollBar.ScrollToTop(); + })); } - + [HarmonyPostfix] [HarmonyPatch(nameof(LobbyViewSettingsPane.Update))] public static void UpdatePatch(LobbyViewSettingsPane __instance) { __instance.gameModeText.text = SelectedMod?.PluginInfo.Metadata.Name ?? "Default"; } - + [HarmonyPrefix] [HarmonyPatch(nameof(LobbyViewSettingsPane.DrawNormalTab))] public static bool DrawNormalTabPatch(LobbyViewSettingsPane __instance) { - if (CurrentSelectedMod == 0) - { - return true; - } - - DrawOptionsTab(__instance); - return false; + if (CurrentSelectedMod == 0) + { + return true; + } + + DrawOptionsTab(__instance); + return false; } - + [HarmonyPrefix] [HarmonyPatch(nameof(LobbyViewSettingsPane.DrawRolesTab))] public static bool DrawRolesTabPatch(LobbyViewSettingsPane __instance) { - if (CurrentSelectedMod == 0) - { - return true; - } - - DrawRolesTab(__instance); - return false; + if (CurrentSelectedMod == 0) + { + return true; + } + + DrawRolesTab(__instance); + return false; } - + private static void DrawOptionsTab(LobbyViewSettingsPane instance) { if (SelectedMod == null) { return; } - + var num = 1.44f; - + var filteredGroups = SelectedMod.OptionGroups.Where(x => x.GroupVisible.Invoke() && x.AdvancedRole is null); foreach (var group in filteredGroups) @@ -127,8 +132,11 @@ public static class LobbyViewPanePatches { continue; } - - var categoryHeaderMasked = Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true); + + var categoryHeaderMasked = Object.Instantiate( + instance.categoryHeaderOrigin, + instance.settingsContainer, + true); categoryHeaderMasked.SetHeader(StringNames.Name, 61); categoryHeaderMasked.Title.text = group.GroupName; categoryHeaderMasked.transform.localScale = Vector3.one; @@ -144,8 +152,11 @@ public static class LobbyViewPanePatches { continue; } - - var viewSettingsInfoPanel = Object.Instantiate(instance.infoPanelOrigin, instance.settingsContainer, true); + + var viewSettingsInfoPanel = Object.Instantiate( + instance.infoPanelOrigin, + instance.settingsContainer, + true); viewSettingsInfoPanel.transform.localScale = Vector3.one; float num2; if (i % 2 == 0) @@ -160,25 +171,35 @@ public static class LobbyViewPanePatches { num2 = -3f; } + viewSettingsInfoPanel.transform.localPosition = new Vector3(num2, num, -2f); var data = option.Data; - + + if (data == null) + { + continue; + } + if (data.Type == OptionTypes.Checkbox) { - viewSettingsInfoPanel.SetInfoCheckbox(data.Title, 61, Mathf.Approximately(option.GetFloatData(), 1)); + viewSettingsInfoPanel.SetInfoCheckbox( + data.Title, + 61, + Mathf.Approximately(option.GetFloatData(), 1)); } else { viewSettingsInfoPanel.SetInfo(data.Title, data.GetValueString(option.GetFloatData()), 61); } - + instance.settingsInfo.Add(viewSettingsInfoPanel.gameObject); i++; } - + num -= 0.59f; } + instance.scrollBar.CalculateAndSetYBounds(instance.settingsInfo.Count + 10, 2f, 6f, 0.59f); } @@ -188,7 +209,7 @@ public static class LobbyViewPanePatches { return; } - + var num = 0.95f; var num2 = -6.53f; var categoryHeaderMasked = @@ -197,7 +218,7 @@ public static class LobbyViewPanePatches categoryHeaderMasked.transform.localScale = Vector3.one; categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f); instance.settingsInfo.Add(categoryHeaderMasked.gameObject); - + var list = new List(); foreach (var team in Enum.GetValues()) @@ -205,12 +226,12 @@ public static class LobbyViewPanePatches var filteredRoles = SelectedMod.CustomRoles.Values .OfType() .Where(x => !x.HideSettings && x.Team == team).ToList(); - + if (filteredRoles.Count == 0) { continue; } - + var categoryHeaderRoleVariant = Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true); @@ -229,7 +250,7 @@ public static class LobbyViewPanePatches categoryHeaderRoleVariant.Title.color = Color.white; break; } - + categoryHeaderRoleVariant.transform.localScale = Vector3.one; categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f); instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject); @@ -242,42 +263,48 @@ public static class LobbyViewPanePatches { continue; } - + var chancePerGame = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleBehaviour.Role); var numPerGame = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role); - + var flag = numPerGame == 0; - + var viewSettingsInfoPanelRoleVariant = Object.Instantiate( - instance.infoPanelRoleOrigin, instance.settingsContainer, true); + instance.infoPanelRoleOrigin, + instance.settingsContainer, + true); viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one; viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f); - + var advancedRoleOptions = SelectedMod.Options .Where(x => x.AdvancedRole == customRole.GetType()) .ToList(); - + if (!flag && advancedRoleOptions.Count > 0) { list.Add(customRole.GetType()); } - viewSettingsInfoPanelRoleVariant.SetInfo(roleBehaviour.NiceName, numPerGame, chancePerGame, - 61, customRole.RoleColor, customRole.Icon.LoadAsset(), true); - - viewSettingsInfoPanelRoleVariant.titleText.color = - viewSettingsInfoPanelRoleVariant.chanceTitle.color = - viewSettingsInfoPanelRoleVariant.chanceBackground.color = - viewSettingsInfoPanelRoleVariant.background.color = + viewSettingsInfoPanelRoleVariant.SetInfo( + roleBehaviour.NiceName, + numPerGame, + chancePerGame, + 61, + customRole.RoleColor, + customRole.Icon.LoadAsset(), + true); + + viewSettingsInfoPanelRoleVariant.titleText.color = + viewSettingsInfoPanelRoleVariant.chanceTitle.color = + viewSettingsInfoPanelRoleVariant.chanceBackground.color = + viewSettingsInfoPanelRoleVariant.background.color = customRole.RoleColor.GetAlternateColor(); instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject); num -= 0.664f; } - - } if (list.Count > 0) @@ -312,7 +339,7 @@ public static class LobbyViewPanePatches advancedRoleViewPanel.transform.localScale = Vector3.one; advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f); var num5 = SetUpAdvancedRoleViewPanel(advancedRoleViewPanel, list[k], 0.59f, 61); - + if (num5 > num3) { num3 = num5; @@ -325,59 +352,74 @@ public static class LobbyViewPanePatches instance.scrollBar.SetYBoundsMax(-num); } - private static float SetUpAdvancedRoleViewPanel(AdvancedRoleViewPanel viewPanel, Type roleType, float spacingY, int maskLayer) + private static float SetUpAdvancedRoleViewPanel( + AdvancedRoleViewPanel viewPanel, + Type roleType, + float spacingY, + int maskLayer) { - if (SelectedMod == null) - { - return 0; - } - - var role = SelectedMod.CustomRoles.Values.FirstOrDefault(x => x.GetType() == roleType); + if (SelectedMod == null) + { + return 0; + } - if (role == null) - { - return 0; - } + var role = SelectedMod.CustomRoles.Values.FirstOrDefault(x => x.GetType() == roleType); - if (role is not ICustomRole customRole) - { - return 0; - } - - viewPanel.header.SetHeader(role.StringName, maskLayer, role.TeamType == RoleTeamTypes.Crewmate, customRole.Icon.LoadAsset()); - viewPanel.divider.material.SetInt(PlayerMaterial.MaskLayer, maskLayer); - - var num = viewPanel.yPosStart; - var num2 = 1.08f; - - var filteredOptions = SelectedMod.Options - .Where(x => x.AdvancedRole == roleType) - .ToList(); - - for (var i = 0; i < filteredOptions.Count; i++) - { - var option = filteredOptions[i]; - var baseGameSetting = option.Data; - var viewSettingsInfoPanel = Object.Instantiate(viewPanel.infoPanelOrigin, viewPanel.transform, true); - viewSettingsInfoPanel.transform.localScale = Vector3.one; - viewSettingsInfoPanel.transform.localPosition = new Vector3(viewPanel.xPosStart, num, -2f); - - var value = option.GetFloatData(); - - if (baseGameSetting.Type == OptionTypes.Checkbox) - { - viewSettingsInfoPanel.SetInfoCheckbox(baseGameSetting.Title, maskLayer, value > 0f); - } - else - { - viewSettingsInfoPanel.SetInfo(baseGameSetting.Title, baseGameSetting.GetValueString(value), maskLayer); - } - num -= spacingY; - if (i > 0) - { - num2 += 0.8f; - } - } - return num2; + if (role == null) + { + return 0; + } + + if (role is not ICustomRole customRole) + { + return 0; + } + + viewPanel.header.SetHeader( + role.StringName, + maskLayer, + role.TeamType == RoleTeamTypes.Crewmate, + customRole.Icon.LoadAsset()); + viewPanel.divider.material.SetInt(PlayerMaterial.MaskLayer, maskLayer); + + var num = viewPanel.yPosStart; + var num2 = 1.08f; + + var filteredOptions = SelectedMod.Options + .Where(x => x.AdvancedRole == roleType) + .ToList(); + + for (var i = 0; i < filteredOptions.Count; i++) + { + var option = filteredOptions[i]; + var baseGameSetting = option.Data; + var viewSettingsInfoPanel = Object.Instantiate(viewPanel.infoPanelOrigin, viewPanel.transform, true); + viewSettingsInfoPanel.transform.localScale = Vector3.one; + viewSettingsInfoPanel.transform.localPosition = new Vector3(viewPanel.xPosStart, num, -2f); + + var value = option.GetFloatData(); + + if (baseGameSetting == null) + { + continue; + } + + if (baseGameSetting.Type == OptionTypes.Checkbox) + { + viewSettingsInfoPanel.SetInfoCheckbox(baseGameSetting.Title, maskLayer, value > 0f); + } + else + { + viewSettingsInfoPanel.SetInfo(baseGameSetting.Title, baseGameSetting.GetValueString(value), maskLayer); + } + + num -= spacingY; + if (i > 0) + { + num2 += 0.8f; + } + } + + return num2; } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Options/NotificationPopperPatch.cs b/MiraAPI/Patches/Options/NotificationPopperPatch.cs index 0a8fc57..d13b854 100644 --- a/MiraAPI/Patches/Options/NotificationPopperPatch.cs +++ b/MiraAPI/Patches/Options/NotificationPopperPatch.cs @@ -6,34 +6,38 @@ using System.Linq; namespace MiraAPI.Patches.Options; [HarmonyPatch(typeof(NotificationPopper))] -public class NotificationPopperPatch +public static class NotificationPopperPatch { [HarmonyPrefix] [HarmonyPatch(nameof(NotificationPopper.AddRoleSettingsChangeMessage))] - public static bool RoleChangeMsgPatch(NotificationPopper __instance, - [HarmonyArgument(0)] StringNames key, [HarmonyArgument(1)] int roleCount, [HarmonyArgument(2)] int roleChance, [HarmonyArgument(3)] RoleTeamTypes teamType, + public static bool RoleChangeMsgPatch( + NotificationPopper __instance, + [HarmonyArgument(0)] StringNames key, + [HarmonyArgument(1)] int roleCount, + [HarmonyArgument(2)] int roleChance, + [HarmonyArgument(3)] RoleTeamTypes teamType, [HarmonyArgument(4)] bool playSound) { - if (CustomRoleManager.CustomRoles.Values.Any(role => role.StringName == key)) + if (CustomRoleManager.CustomRoles.Values.All(role => role.StringName != key)) { - var item = string.Empty; - var text = (teamType == RoleTeamTypes.Crewmate) ? Palette.CrewmateSettingChangeText.ToTextColor() : Palette.ImpostorRed.ToTextColor(); - item = DestroyableSingleton.Instance.GetString(StringNames.LobbyChangeSettingNotificationRole, new Object[] - { - string.Concat( - [ - "", - text, - DestroyableSingleton.Instance.GetString(key, Array.Empty()), - "" - ]), - "" + roleCount.ToString() + "", - "" + roleChance.ToString() + "%" - }); - __instance.SettingsChangeMessageLogic(key, item, playSound); - return false; + return true; } - return true; + var text = teamType == RoleTeamTypes.Crewmate + ? Palette.CrewmateSettingChangeText.ToTextColor() + : Palette.ImpostorRed.ToTextColor(); + + var item = DestroyableSingleton.Instance.GetString( + StringNames.LobbyChangeSettingNotificationRole, + "", + text, + DestroyableSingleton.Instance.GetString(key, Array.Empty()), + "", + "" + roleCount + "", + "" + roleChance + "%" + ); + + __instance.SettingsChangeMessageLogic(key, item, playSound); + return false; } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Options/OptionsPatches.cs b/MiraAPI/Patches/Options/OptionsPatches.cs index de7da75..a3be128 100644 --- a/MiraAPI/Patches/Options/OptionsPatches.cs +++ b/MiraAPI/Patches/Options/OptionsPatches.cs @@ -16,7 +16,8 @@ public static class OptionsPatches return true; } - __instance.TitleText.text = DestroyableSingleton.Instance.GetString(__instance.Title, Array.Empty()); + __instance.TitleText.text = + DestroyableSingleton.Instance.GetString(__instance.Title, Array.Empty()); return false; } @@ -37,7 +38,8 @@ public static class OptionsPatches return true; } - __instance.TitleText.text = DestroyableSingleton.Instance.GetString(__instance.Title, Array.Empty()); + __instance.TitleText.text = + DestroyableSingleton.Instance.GetString(__instance.Title, Array.Empty()); return false; } @@ -58,8 +60,11 @@ public static class OptionsPatches return true; } - __instance.TitleText.text = DestroyableSingleton.Instance.GetString(__instance.Title, Array.Empty()); - __instance.ValueText.text = DestroyableSingleton.Instance.GetString(__instance.Values[__instance.Value], Array.Empty()); + __instance.TitleText.text = + DestroyableSingleton.Instance.GetString(__instance.Title, Array.Empty()); + __instance.ValueText.text = DestroyableSingleton.Instance.GetString( + __instance.Values[__instance.Value], + Array.Empty()); return false; } @@ -70,4 +75,4 @@ public static class OptionsPatches { return !__instance.IsCustom(); } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 420fff8..d3eb9f7 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -54,58 +54,72 @@ public static class RoleSettingMenuPatches var num3 = 0; - var crewRoles = GameSettingMenuPatches.SelectedMod.CustomRoles.Values + var crewRoles = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values .OfType() - .Where(role => role.Team == ModdedRoleTeams.Crewmate && !role.HideSettings) - .ToList(); + .Where(role => role is { Team: ModdedRoleTeams.Crewmate, HideSettings: false }) + .ToList() ?? []; - if (crewRoles.Count > 0) + if (crewRoles is { Count: > 0 }) { - - var categoryHeaderEditRole = Object.Instantiate(__instance.categoryHeaderEditRoleOrigin, Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform); + var categoryHeaderEditRole = Object.Instantiate( + __instance.categoryHeaderEditRoleOrigin, + Vector3.zero, + Quaternion.identity, + __instance.RoleChancesSettings.transform); categoryHeaderEditRole.SetHeader(StringNames.CrewmateRolesHeader, 20); categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, num, -2f); num -= 0.522f; foreach (var role in crewRoles) { - CreateQuotaOption(__instance, role as RoleBehaviour, ref num, num3); + if (role is RoleBehaviour roleBehaviour) + { + CreateQuotaOption(__instance, roleBehaviour, ref num, num3); + } num3++; } } - var impRoles = GameSettingMenuPatches.SelectedMod.CustomRoles.Values + var impRoles = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values .OfType() - .Where(role => role.Team == ModdedRoleTeams.Impostor && !role.HideSettings) - .ToList(); + .Where(role => role is { Team: ModdedRoleTeams.Impostor, HideSettings: false }) + .ToList() ?? []; - if (impRoles.Count > 0) + if (impRoles is { Count: > 0 }) { num -= 0.4f; - var categoryHeaderEditRole2 = Object.Instantiate(__instance.categoryHeaderEditRoleOrigin, - Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform); + var categoryHeaderEditRole2 = Object.Instantiate( + __instance.categoryHeaderEditRoleOrigin, + Vector3.zero, + Quaternion.identity, + __instance.RoleChancesSettings.transform); categoryHeaderEditRole2.SetHeader(StringNames.ImpostorRolesHeader, 20); categoryHeaderEditRole2.transform.localPosition = new Vector3(4.986f, num, -2f); num -= 0.522f; - foreach (var role in impRoles) { - CreateQuotaOption(__instance, role as RoleBehaviour, ref num, num3); + if (role is RoleBehaviour roleBehaviour) + { + CreateQuotaOption(__instance, roleBehaviour, ref num, num3); + } num3++; } } - var neutRoles = GameSettingMenuPatches.SelectedMod.CustomRoles.Values + var neutRoles = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values .OfType() - .Where(role => role.Team == ModdedRoleTeams.Neutral && !role.HideSettings) - .ToList(); + .Where(role => role is { Team: ModdedRoleTeams.Neutral, HideSettings: false }) + .ToList() ?? []; - if (neutRoles.Count > 0) + if (neutRoles is { Count: > 0 }) { num -= 0.4f; - var categoryHeaderEditRole3 = Object.Instantiate(__instance.categoryHeaderEditRoleOrigin, - Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform); + var categoryHeaderEditRole3 = Object.Instantiate( + __instance.categoryHeaderEditRoleOrigin, + Vector3.zero, + Quaternion.identity, + __instance.RoleChancesSettings.transform); categoryHeaderEditRole3.SetHeader(StringNames.None, 20); categoryHeaderEditRole3.Title.text = "Neutral Roles"; categoryHeaderEditRole3.Background.color = Color.gray; @@ -115,7 +129,10 @@ public static class RoleSettingMenuPatches foreach (var role in neutRoles) { - CreateQuotaOption(__instance, role as RoleBehaviour, ref num, num3); + if (role is RoleBehaviour roleBehaviour) + { + CreateQuotaOption(__instance, roleBehaviour, ref num, num3); + } num3++; } } @@ -161,12 +178,18 @@ public static class RoleSettingMenuPatches } roleSetting.UpdateValuesAndText(GameOptionsManager.Instance.CurrentGameOptions.RoleOptions); - DestroyableSingleton.Instance.Notifier.AddRoleSettingsChangeMessage(roleSetting.Role.StringName, roleSetting.RoleMaxCount, roleSetting.RoleChance, roleSetting.Role.TeamType, false); + DestroyableSingleton.Instance.Notifier.AddRoleSettingsChangeMessage( + roleSetting.Role.StringName, + roleSetting.RoleMaxCount, + roleSetting.RoleChance, + roleSetting.Role.TeamType, + false); if (AmongUsClient.Instance.AmHost) { Rpc.Instance.Send(PlayerControl.LocalPlayer, [role.GetNetData()], true); } + GameOptionsManager.Instance.GameHostOptions = GameOptionsManager.Instance.CurrentGameOptions; } @@ -181,7 +204,7 @@ public static class RoleSettingMenuPatches var num = -0.872f; - var filteredOptions = GameSettingMenuPatches.SelectedMod.Options.Where(x => x.AdvancedRole == role.GetType()); + var filteredOptions = GameSettingMenuPatches.SelectedMod?.Options.Where(x => x.AdvancedRole == role.GetType()) ?? []; foreach (var option in filteredOptions) { @@ -190,7 +213,12 @@ public static class RoleSettingMenuPatches continue; } - var newOpt = option.CreateOption(__instance.checkboxOrigin, __instance.numberOptionOrigin, __instance.stringOptionOrigin, __instance.AdvancedRolesSettings.transform); + var newOpt = option.CreateOption( + __instance.checkboxOrigin, + __instance.numberOptionOrigin, + __instance.stringOptionOrigin, + __instance.AdvancedRolesSettings.transform); + newOpt.transform.localPosition = new Vector3(2.17f, num, -2f); newOpt.SetClickMask(__instance.ButtonClickMask); @@ -199,10 +227,11 @@ public static class RoleSettingMenuPatches { renderer.material.SetInt(PlayerMaterial.MaskLayer, 20); } - foreach (var textMeshPro in newOpt.GetComponentsInChildren(true)) + + foreach (var fontMat in newOpt.GetComponentsInChildren(true).Select(x=>x.fontMaterial)) { - textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f); - textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20); + fontMat.SetFloat(ShaderID.StencilComp, 3f); + fontMat.SetFloat(ShaderID.Stencil, 20); } newOpt.LabelBackground.enabled = false; @@ -215,12 +244,24 @@ public static class RoleSettingMenuPatches __instance.scrollBar.CalculateAndSetYBounds(__instance.advancedSettingChildren.Count + 3, 1f, 6f, 0.45f); __instance.scrollBar.ScrollToTop(); } + private static void ChangeTab(RoleBehaviour role, RolesSettingsMenu __instance) { - var customRole = role as ICustomRole; + if (role is not ICustomRole customRole) + { + Logger.Error($"Role {role.NiceName} is not a custom role."); + return; + } + __instance.roleDescriptionText.text = customRole.RoleLongDescription; - __instance.roleTitleText.text = DestroyableSingleton.Instance.GetString(role.StringName, new Il2CppReferenceArray(0)); - __instance.roleScreenshot.sprite = Sprite.Create(customRole.OptionsScreenshot.LoadAsset().texture, new Rect(0, 0, 370, 230), Vector2.one / 2, 100); + __instance.roleTitleText.text = DestroyableSingleton.Instance.GetString( + role.StringName, + new Il2CppReferenceArray(0)); + __instance.roleScreenshot.sprite = Sprite.Create( + customRole.OptionsScreenshot.LoadAsset().texture, + new Rect(0, 0, 370, 230), + Vector2.one / 2, + 100); __instance.roleScreenshot.drawMode = SpriteDrawMode.Sliced; __instance.roleHeaderSprite.color = customRole.RoleColor; __instance.roleHeaderText.color = customRole.RoleColor.GetAlternateColor(); @@ -244,15 +285,25 @@ public static class RoleSettingMenuPatches optionBehaviour.SetAsPlayer(); } } + __instance.RoleChancesSettings.SetActive(false); __instance.AdvancedRolesSettings.SetActive(true); __instance.RefreshChildren(); } - public static void CreateQuotaOption(RolesSettingsMenu __instance, RoleBehaviour role, ref float yPos, int index) + private static void CreateQuotaOption(RolesSettingsMenu __instance, RoleBehaviour role, ref float yPos, int index) { - var customRole = role as ICustomRole; - var roleOptionSetting = Object.Instantiate(__instance.roleOptionSettingOrigin, Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform); + if (role is not ICustomRole customRole) + { + Logger.Error($"Role {role.NiceName} is not a custom role."); + return; + } + + var roleOptionSetting = Object.Instantiate( + __instance.roleOptionSettingOrigin, + Vector3.zero, + Quaternion.identity, + __instance.RoleChancesSettings.transform); roleOptionSetting.transform.localPosition = new Vector3(-0.15f, yPos, -2f); roleOptionSetting.SetRole(GameOptionsManager.Instance.CurrentGameOptions.RoleOptions, role, 20); roleOptionSetting.labelSprite.color = customRole.RoleColor; @@ -265,7 +316,8 @@ public static class RoleSettingMenuPatches roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left; if (GameSettingMenuPatches.SelectedMod is null || - GameSettingMenuPatches.SelectedMod.Options.Any(x => x.AdvancedRole != null && x.AdvancedRole.IsInstanceOfType(role))) + GameSettingMenuPatches.SelectedMod.Options.Exists( + x => x.AdvancedRole != null && x.AdvancedRole.IsInstanceOfType(role))) { var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform); newButton.name = "ConfigButton"; @@ -289,4 +341,4 @@ public static class RoleSettingMenuPatches yPos += -0.43f; } } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Options/SettingPatches.cs b/MiraAPI/Patches/Options/SettingPatches.cs index 1b228a3..3c20bfe 100644 --- a/MiraAPI/Patches/Options/SettingPatches.cs +++ b/MiraAPI/Patches/Options/SettingPatches.cs @@ -9,7 +9,7 @@ namespace MiraAPI.Patches.Options; /// Patches for the various game settings. /// [HarmonyPatch] -public class SettingPatches +public static class SettingPatches { /// /// Prefix for the method. Adds support for custom number suffixes. @@ -25,33 +25,24 @@ public class SettingPatches ref string __result, [HarmonyArgument(0)] float value) { - var result = string.Empty; + string result; var suffix = (MiraNumberSuffixes)__instance.SuffixType; if (__instance.ZeroIsInfinity && Mathf.Abs(value) < 0.0001f) { result = "∞"; } - else if (suffix == MiraNumberSuffixes.None) - { - result = value.ToString(__instance.FormatString); - } - else if (suffix == MiraNumberSuffixes.Multiplier) - { - result = value.ToString(__instance.FormatString) + "x"; - } - else if (suffix == MiraNumberSuffixes.Percent) - { - result = value.ToString(__instance.FormatString) + "%"; - } else { - result = DestroyableSingleton.Instance.GetString( - StringNames.GameSecondsAbbrev, - (Il2CppSystem.Object[]) - [ - value.ToString(__instance.FormatString, CultureInfo.InvariantCulture) - ]); + result = suffix switch + { + MiraNumberSuffixes.None => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo), + MiraNumberSuffixes.Multiplier => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "x", + MiraNumberSuffixes.Percent => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "%", + _ => DestroyableSingleton.Instance.GetString( + StringNames.GameSecondsAbbrev, + (Il2CppSystem.Object[]) [value.ToString(__instance.FormatString, CultureInfo.InvariantCulture)]), + }; } __result = result; diff --git a/MiraAPI/Patches/PlayerControlPatches.cs b/MiraAPI/Patches/PlayerControlPatches.cs index 7d4d97a..b9bc0f2 100644 --- a/MiraAPI/Patches/PlayerControlPatches.cs +++ b/MiraAPI/Patches/PlayerControlPatches.cs @@ -32,13 +32,13 @@ public static class PlayerControlPatches /// [HarmonyPostfix] [HarmonyPatch(nameof(PlayerControl.Die))] - public static void PlayerControlDiePostfix(PlayerControl __instance) + public static void PlayerControlDiePostfix(PlayerControl __instance, DeathReason reason) { var modifiersComponent = __instance.GetComponent(); if (modifiersComponent) { - modifiersComponent.ActiveModifiers.ForEach(x=>x.OnDeath()); + modifiersComponent.ActiveModifiers.ForEach(x=>x.OnDeath(reason)); } } diff --git a/MiraAPI/Patches/Roles/EjectionPatches.cs b/MiraAPI/Patches/Roles/EjectionPatches.cs index bbea1e8..9f85872 100644 --- a/MiraAPI/Patches/Roles/EjectionPatches.cs +++ b/MiraAPI/Patches/Roles/EjectionPatches.cs @@ -3,15 +3,18 @@ using MiraAPI.Roles; namespace MiraAPI.Patches.Roles; +/// +/// Patches for custom ejection messages. +/// [HarmonyPatch(typeof(ExileController))] public static class EjectionPatches { - [HarmonyPostfix] [HarmonyPatch(nameof(ExileController.Begin))] - public static void Begin(ExileController __instance) + public static void BeginPostfix(ExileController __instance) { - if (!__instance.initData.networkedPlayer || !__instance.initData.networkedPlayer.Role || __instance.initData.networkedPlayer.Role is not ICustomRole role) + if (!__instance.initData.networkedPlayer || !__instance.initData.networkedPlayer.Role || + __instance.initData.networkedPlayer.Role is not ICustomRole role) { return; } @@ -23,4 +26,4 @@ public static class EjectionPatches __instance.completeString = role.GetCustomEjectionMessage(__instance.initData.networkedPlayer); } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Roles/HudManagerPatches.cs b/MiraAPI/Patches/Roles/HudManagerPatches.cs new file mode 100644 index 0000000..34f0bb9 --- /dev/null +++ b/MiraAPI/Patches/Roles/HudManagerPatches.cs @@ -0,0 +1,74 @@ +using HarmonyLib; +using InnerNet; +using MiraAPI.Roles; +using Reactor.Utilities.Extensions; + +namespace MiraAPI.Patches.Roles; + +/// +/// HudManager patches for roles. +/// +[HarmonyPatch(typeof(HudManager))] +public static class HudManagerPatches +{ + // Custom role tab. + private static TaskPanelBehaviour? _roleTab; + + /// + /// Fixes Kill Button not showing for Neutral killing role. + /// + [HarmonyPostfix] + [HarmonyPatch(nameof(HudManager.SetHudActive), typeof(PlayerControl), typeof(RoleBehaviour), typeof(bool))] + public static void SetHudActivePostfix( + HudManager __instance, + PlayerControl localPlayer, + RoleBehaviour role, + bool isActive) + { + var flag = localPlayer.Data != null && localPlayer.Data.IsDead; + + if (role is ICustomRole) + { + __instance.KillButton.ToggleVisible(isActive && role.CanUseKillButton && !flag); + __instance.ImpostorVentButton.ToggleVisible(isActive && role.CanVent && !flag); + } + + if (_roleTab) + { + _roleTab?.gameObject.SetActive(isActive); + } + } + + /// + /// Update custom role tab and custom role hud elements. + /// + [HarmonyPostfix] + [HarmonyPatch(nameof(HudManager.Update))] + public static void UpdatePostfix(HudManager __instance) + { + var local = PlayerControl.LocalPlayer; + + if (AmongUsClient.Instance.GameState != InnerNetClient.GameStates.Started && !ShipStatus.Instance) + { + return; + } + + var role = local?.Data?.Role; + + if (role is ICustomRole { RoleHintType: RoleHintType.RoleTab } customRole) + { + customRole.HudUpdate(__instance); + + if (_roleTab == null) + { + _roleTab = CustomRoleManager.CreateRoleTab(customRole); + } + + CustomRoleManager.UpdateRoleTab(_roleTab, customRole); + } + else if (_roleTab) + { + _roleTab?.gameObject.Destroy(); + } + } +} diff --git a/MiraAPI/Patches/Roles/KillButtonPatches.cs b/MiraAPI/Patches/Roles/KillButtonPatches.cs new file mode 100644 index 0000000..7b7010a --- /dev/null +++ b/MiraAPI/Patches/Roles/KillButtonPatches.cs @@ -0,0 +1,71 @@ +using HarmonyLib; +using Il2CppSystem; +using MiraAPI.Networking; +using MiraAPI.Roles; +using UnityEngine; + +namespace MiraAPI.Patches.Roles; + +/// +/// Fix kill button issues for neutral killers. +/// +[HarmonyPatch(typeof(KillButton))] +public static class KillButtonPatches +{ + /// + /// SetTarget for custom roles. + /// + [HarmonyPrefix] + [HarmonyPatch(nameof(KillButton.SetTarget))] + public static bool SetTargetPrefix(KillButton __instance, PlayerControl target) + { + if (!PlayerControl.LocalPlayer || PlayerControl.LocalPlayer.Data == null || !PlayerControl.LocalPlayer.Data.Role) + { + return false; + } + + if (PlayerControl.LocalPlayer.Data.Role is not ICustomRole customRole) + { + return true; + } + + if (__instance.currentTarget && __instance.currentTarget != target) + { + __instance.currentTarget.cosmetics.SetOutline(false, new Nullable(Color.clear)); + } + __instance.currentTarget = target; + if (__instance.currentTarget) + { + __instance.currentTarget.cosmetics.SetOutline(true, new Nullable(customRole.KillButtonOutlineColor)); + __instance.SetEnabled(); + return false; + } + __instance.SetDisabled(); + + return false; + } + + /// + /// Use Custom Murder if player is custom role. + /// + [HarmonyPrefix] + [HarmonyPatch(nameof(KillButton.DoClick))] + public static bool DoClickPrefix(KillButton __instance) + { + if (PlayerControl.LocalPlayer.Data.Role is not ICustomRole) + { + return true; + } + + if (!__instance.isActiveAndEnabled || !__instance.currentTarget || __instance.isCoolingDown || + PlayerControl.LocalPlayer.Data.IsDead || !PlayerControl.LocalPlayer.CanMove) + { + return false; + } + + PlayerControl.LocalPlayer.RpcCustomMurder(__instance.currentTarget); + __instance.SetTarget(null); + + return false; + } +} diff --git a/MiraAPI/Patches/Roles/NameTagPatch.cs b/MiraAPI/Patches/Roles/NameTagPatch.cs index b427112..821db1c 100644 --- a/MiraAPI/Patches/Roles/NameTagPatch.cs +++ b/MiraAPI/Patches/Roles/NameTagPatch.cs @@ -32,4 +32,4 @@ public static class NameTagPatch return false; } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Roles/RoleBehaviourPatches.cs b/MiraAPI/Patches/Roles/RoleBehaviourPatches.cs index 5879df8..98cb5e6 100644 --- a/MiraAPI/Patches/Roles/RoleBehaviourPatches.cs +++ b/MiraAPI/Patches/Roles/RoleBehaviourPatches.cs @@ -22,4 +22,4 @@ public static class RoleBehaviourPatches __result = behaviour.RoleColor; return false; } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Roles/RoleManagerPatches.cs b/MiraAPI/Patches/Roles/RoleManagerPatches.cs index 350767f..4f75a16 100644 --- a/MiraAPI/Patches/Roles/RoleManagerPatches.cs +++ b/MiraAPI/Patches/Roles/RoleManagerPatches.cs @@ -18,7 +18,9 @@ public static class RoleManagerPatches return; } - ModifierManager.AssignModifiers(PlayerControl.AllPlayerControls.ToArray().Where(plr => !plr.Data.IsDead && !plr.Data.Disconnected).ToList()); + ModifierManager.AssignModifiers( + PlayerControl.AllPlayerControls.ToArray().Where(plr => !plr.Data.IsDead && !plr.Data.Disconnected) + .ToList()); } [HarmonyPrefix] @@ -39,9 +41,8 @@ public static class RoleManagerPatches { return true; } - + plr.RpcSetRole(role.GhostRole); return false; - } -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs b/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs index c146b8a..ed4f7b7 100644 --- a/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs +++ b/MiraAPI/Patches/Roles/RoleOptionsCollectionPatch.cs @@ -14,13 +14,14 @@ public static class RoleOptionsCollectionPatch [HarmonyPatch(nameof(RoleOptionsCollectionV08.GetChancePerGame))] public static bool GetChancePrefix([HarmonyArgument(0)] RoleTypes roleType, ref int __result) { - if (!CustomRoleManager.GetCustomRoleBehaviour(roleType, out var customRole)) + if (!CustomRoleManager.GetCustomRoleBehaviour(roleType, out var customRole) || customRole == null) { return true; } if (customRole.HideSettings) { + __result = 0; return false; } @@ -36,18 +37,19 @@ public static class RoleOptionsCollectionPatch [HarmonyPatch(nameof(RoleOptionsCollectionV08.GetNumPerGame))] public static bool GetNumPrefix([HarmonyArgument(0)] RoleTypes roleType, ref int __result) { - if (!CustomRoleManager.GetCustomRoleBehaviour(roleType, out var customRole)) + if (!CustomRoleManager.GetCustomRoleBehaviour(roleType, out var customRole) || customRole == null) { return true; } + if (customRole.HideSettings) { + __result = 0; return false; } customRole.ParentMod.PluginConfig.TryGetEntry(customRole.NumConfigDefinition, out var entry); __result = entry.Value; return false; - } } diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs index 9db0f92..0f3037f 100644 --- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs +++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs @@ -15,9 +15,8 @@ namespace MiraAPI.Patches.Roles; [HarmonyPatch] public static class TaskAdderPatch { - private static TaskFolder _rolesFolder; - private static readonly System.Collections.Generic.Dictionary ModsFolders = new(); - private static Scroller _scroller; + private static readonly System.Collections.Generic.Dictionary ModsFolders = []; + private static Scroller? _scroller; [HarmonyPostfix] [HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.Begin))] @@ -56,18 +55,22 @@ public static class TaskAdderPatch __instance.TaskParent = inner.transform; - _rolesFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner); - _rolesFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner); - _rolesFolder.gameObject.SetActive(false); - _rolesFolder.FolderName = "Roles"; - _rolesFolder.name = "RolesFolder"; + var rolesFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner); + rolesFolder.gameObject.SetActive(false); + rolesFolder.FolderName = "Roles"; + rolesFolder.name = "RolesFolder"; foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins()) { + if (plugin.CustomRoles.Count == 0) + { + continue; + } + var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner); newFolder.FolderName = newFolder.name = plugin.PluginInfo.Metadata.Name; newFolder.gameObject.SetActive(false); - _rolesFolder.SubFolders.Add(newFolder); + rolesFolder.SubFolders.Add(newFolder); if (!ModsFolders.ContainsKey(plugin.PluginInfo.Metadata.Name)) { @@ -75,7 +78,7 @@ public static class TaskAdderPatch } } - __instance.Root.SubFolders.Add(_rolesFolder); + __instance.Root.SubFolders.Add(rolesFolder); __instance.GoToRoot(); } @@ -92,7 +95,12 @@ public static class TaskAdderPatch __instance.RolloverHandler.OutColor = __instance.FileImage.color; } - private static void AddFileAsChildCustom(this TaskAdderGame instance, TaskFolder taskFolder, TaskAddButton item, ref float xCursor, ref float yCursor, ref float maxHeight) + private static void AddFileAsChildCustom( + this TaskAdderGame instance, + TaskAddButton item, + ref float xCursor, + ref float yCursor, + ref float maxHeight) { item.transform.SetParent(instance.TaskParent); item.transform.localPosition = new Vector3(xCursor, yCursor, 0f); @@ -105,9 +113,9 @@ public static class TaskAdderPatch yCursor -= maxHeight; maxHeight = 0f; } + instance.ActiveItems.Add(item.transform); } - // yes it might be crazy patching the entire method, but i tried so many other methods and only this works :cry: [HarmonyPrefix] @@ -121,11 +129,13 @@ public static class TaskAdderPatch stringBuilder.Append(__instance.Hierarchy.ToArray()[i].FolderName); stringBuilder.Append("\\"); } + __instance.PathText.text = stringBuilder.ToString(); for (var j = 0; j < __instance.ActiveItems.Count; j++) { Object.Destroy(__instance.ActiveItems.ToArray()[j].gameObject); } + __instance.ActiveItems.Clear(); var num = 0f; var num2 = 0f; @@ -148,6 +158,7 @@ public static class TaskAdderPatch num2 -= num3; num3 = 0f; } + __instance.ActiveItems.Add(folderTransform); if (!taskFolder2 || !taskFolder2.Button) { @@ -155,7 +166,8 @@ public static class TaskAdderPatch } ControllerManager.Instance.AddSelectableUiElement(taskFolder2.Button); - if (!string.IsNullOrEmpty(__instance.restorePreviousSelectionByFolderName) && taskFolder2.FolderName.Equals(__instance.restorePreviousSelectionByFolderName)) + if (!string.IsNullOrEmpty(__instance.restorePreviousSelectionByFolderName) && + taskFolder2.FolderName.Equals(__instance.restorePreviousSelectionByFolderName, System.StringComparison.Ordinal)) { __instance.restorePreviousSelectionFound = taskFolder2.Button; } @@ -173,63 +185,90 @@ public static class TaskAdderPatch { var taskAddButton = Object.Instantiate(__instance.TaskPrefab); taskAddButton.MyTask = list.ToArray()[l]; - if (taskAddButton.MyTask.TaskType == TaskTypes.DivertPower) + switch (taskAddButton.MyTask.TaskType) { - var targetSystem = taskAddButton.MyTask.Cast().TargetSystem; - taskAddButton.Text.text = DestroyableSingleton.Instance.GetString(StringNames.DivertPowerTo, DestroyableSingleton.Instance.GetString(targetSystem)); - } - else if (taskAddButton.MyTask.TaskType == TaskTypes.FixWeatherNode) - { - var nodeId = ((WeatherNodeTask)taskAddButton.MyTask).NodeId; - taskAddButton.Text.text = DestroyableSingleton.Instance.GetString(StringNames.FixWeatherNode, Array.Empty()) + " " + DestroyableSingleton.Instance.GetString(WeatherSwitchGame.ControlNames[nodeId], - Array.Empty()); - } - else - { - taskAddButton.Text.text = DestroyableSingleton.Instance.GetString(taskAddButton.MyTask.TaskType); - } - __instance.AddFileAsChildCustom(taskFolder, taskAddButton, ref num, ref num2, ref num3); - if (taskAddButton != null && taskAddButton.Button != null) - { - ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button); - if (__instance.Hierarchy.Count != 1 && !flag) + case TaskTypes.DivertPower: { - var component = ControllerManager.Instance.CurrentUiState.CurrentSelection.GetComponent(); - if (component != null) - { - __instance.restorePreviousSelectionByFolderName = component.FolderName; - } - ControllerManager.Instance.SetDefaultSelection(taskAddButton.Button); - flag = true; + var targetSystem = taskAddButton.MyTask.Cast().TargetSystem; + taskAddButton.Text.text = DestroyableSingleton.Instance.GetString( + StringNames.DivertPowerTo, + DestroyableSingleton.Instance.GetString(targetSystem)); + break; } + case TaskTypes.FixWeatherNode: + { + var nodeId = ((WeatherNodeTask)taskAddButton.MyTask).NodeId; + taskAddButton.Text.text = + DestroyableSingleton.Instance.GetString( + StringNames.FixWeatherNode, + Array.Empty()) + " " + + DestroyableSingleton.Instance.GetString( + WeatherSwitchGame.ControlNames[nodeId], + Array.Empty()); + break; + } + default: + taskAddButton.Text.text = + DestroyableSingleton.Instance.GetString(taskAddButton.MyTask.TaskType); + break; } + + __instance.AddFileAsChildCustom(taskAddButton, ref num, ref num2, ref num3); + if (taskAddButton.Button == null) + { + continue; + } + + ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button); + if (__instance.Hierarchy.Count == 1 || flag) + { + continue; + } + + var component = + ControllerManager.Instance.CurrentUiState.CurrentSelection.GetComponent(); + if (component != null) + { + __instance.restorePreviousSelectionByFolderName = component.FolderName; + } + + ControllerManager.Instance.SetDefaultSelection(taskAddButton.Button); + flag = true; } + if (taskFolder.FolderName == "Roles") { for (var m = 0; m < DestroyableSingleton.Instance.AllRoles.Length; m++) { var roleBehaviour = DestroyableSingleton.Instance.AllRoles[m]; - if (roleBehaviour.Role != RoleTypes.ImpostorGhost && roleBehaviour.Role != RoleTypes.CrewmateGhost && !CustomRoleManager.CustomRoles.ContainsKey((ushort)roleBehaviour.Role)) + if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost || + CustomRoleManager.CustomRoles.ContainsKey((ushort)roleBehaviour.Role)) { - var taskAddButton2 = Object.Instantiate(__instance.RoleButton); - taskAddButton2.SafePositionWorld = __instance.SafePositionWorld; - taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe"; - __instance.AddFileAsChildCustom(_rolesFolder, taskAddButton2, ref num, ref num2, ref num3); - taskAddButton2.Role = roleBehaviour; - if (taskAddButton2 != null && taskAddButton2.Button != null) - { - ControllerManager.Instance.AddSelectableUiElement(taskAddButton2.Button); - if (m == 0 && __instance.restorePreviousSelectionFound != null) - { - ControllerManager.Instance.SetDefaultSelection(__instance.restorePreviousSelectionFound); - __instance.restorePreviousSelectionByFolderName = string.Empty; - __instance.restorePreviousSelectionFound = null; - } - else if (m == 0) - { - ControllerManager.Instance.SetDefaultSelection(taskAddButton2.Button); - } - } + continue; + } + + var taskAddButton2 = Object.Instantiate(__instance.RoleButton); + taskAddButton2.SafePositionWorld = __instance.SafePositionWorld; + taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe"; + __instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3); + taskAddButton2.Role = roleBehaviour; + + if (taskAddButton2.Button == null) + { + continue; + } + + ControllerManager.Instance.AddSelectableUiElement(taskAddButton2.Button); + switch (m) + { + case 0 when __instance.restorePreviousSelectionFound != null: + ControllerManager.Instance.SetDefaultSelection(__instance.restorePreviousSelectionFound); + __instance.restorePreviousSelectionByFolderName = string.Empty; + __instance.restorePreviousSelectionFound = null; + break; + case 0: + ControllerManager.Instance.SetDefaultSelection(taskAddButton2.Button); + break; } } } @@ -240,27 +279,33 @@ public static class TaskAdderPatch for (var m = 0; m < plugin.CustomRoles.Count; m++) { var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value; - if (roleBehaviour.Role != RoleTypes.ImpostorGhost && roleBehaviour.Role != RoleTypes.CrewmateGhost && !roleBehaviour.IsDead) + if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost || + roleBehaviour.IsDead) { - var taskAddButton2 = Object.Instantiate(__instance.RoleButton); - taskAddButton2.SafePositionWorld = __instance.SafePositionWorld; - taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe"; - __instance.AddFileAsChildCustom(_rolesFolder, taskAddButton2, ref num, ref num2, ref num3); - taskAddButton2.Role = roleBehaviour; - if (taskAddButton2 != null && taskAddButton2.Button != null) - { - ControllerManager.Instance.AddSelectableUiElement(taskAddButton2.Button); - if (m == 0 && __instance.restorePreviousSelectionFound != null) - { - ControllerManager.Instance.SetDefaultSelection(__instance.restorePreviousSelectionFound); - __instance.restorePreviousSelectionByFolderName = string.Empty; - __instance.restorePreviousSelectionFound = null; - } - else if (m == 0) - { - ControllerManager.Instance.SetDefaultSelection(taskAddButton2.Button); - } - } + continue; + } + + var taskAddButton2 = Object.Instantiate(__instance.RoleButton); + taskAddButton2.SafePositionWorld = __instance.SafePositionWorld; + taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe"; + __instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3); + taskAddButton2.Role = roleBehaviour; + if (taskAddButton2.Button == null) + { + continue; + } + + ControllerManager.Instance.AddSelectableUiElement(taskAddButton2.Button); + switch (m) + { + case 0 when __instance.restorePreviousSelectionFound != null: + ControllerManager.Instance.SetDefaultSelection(__instance.restorePreviousSelectionFound); + __instance.restorePreviousSelectionByFolderName = string.Empty; + __instance.restorePreviousSelectionFound = null; + break; + case 0: + ControllerManager.Instance.SetDefaultSelection(taskAddButton2.Button); + break; } } } @@ -271,17 +316,19 @@ public static class TaskAdderPatch chip.GetComponent().maskInteraction = SpriteMaskInteraction.VisibleInsideMask; } - if (_scroller) + if (_scroller && _scroller != null) { _scroller.CalculateAndSetYBounds(__instance.ActiveItems.Count, 6, 3, 1f); _scroller.SetYBoundsMin(0.0f); _scroller.ScrollToTop(); } + if (__instance.Hierarchy.Count == 1) { ControllerManager.Instance.SetBackButton(__instance.BackButton); return false; } + ControllerManager.Instance.SetBackButton(__instance.FolderBackButton); return false; } diff --git a/MiraAPI/Patches/Roles/TaskPanelPatch.cs b/MiraAPI/Patches/Roles/TaskPanelPatch.cs index e93ab10..431d47b 100644 --- a/MiraAPI/Patches/Roles/TaskPanelPatch.cs +++ b/MiraAPI/Patches/Roles/TaskPanelPatch.cs @@ -7,9 +7,9 @@ namespace MiraAPI.Patches.Roles; public static class TaskPanelPatch { /// - /// This patch is to override the automatic updating of the y position on the tab (which is in base game), + /// This patch is to override the automatic updating of the y position on the tab (which is in base game) /// because I can't change the custom tab y pos if it's being overriden every frame. - /// Im sure there is an easier/better way, but this is the fix that worked for me + /// Im sure there is an easier/better way, but this is the fix that worked for me. /// [HarmonyPrefix] [HarmonyPatch(nameof(TaskPanelBehaviour.Update))] @@ -24,7 +24,12 @@ public static class TaskPanelPatch var vector = __instance.background.sprite.bounds.extents; var vector2 = __instance.tab.sprite.bounds.extents; - transform.localScale = __instance.taskText.textBounds.size.x > 0f ? new Vector3(__instance.taskText.textBounds.size.x + 0.4f, __instance.taskText.textBounds.size.y + 0.3f, 1f) : Vector3.zero; + transform.localScale = __instance.taskText.textBounds.size.x > 0f + ? new Vector3( + __instance.taskText.textBounds.size.x + 0.4f, + __instance.taskText.textBounds.size.y + 0.3f, + 1f) + : Vector3.zero; vector.y = -vector.y; vector = vector.Mul(transform.localScale); @@ -39,20 +44,24 @@ public static class TaskPanelPatch { return false; } - var closePosition = new Vector3(-__instance.background.sprite.bounds.size.x * __instance.background.transform.localScale.x, __instance.closedPosition.y, __instance.closedPosition.z); + + var closePosition = new Vector3( + -__instance.background.sprite.bounds.size.x * __instance.background.transform.localScale.x, + __instance.closedPosition.y, + __instance.closedPosition.z); __instance.closedPosition = closePosition; - if (__instance.open) - { - __instance.timer = Mathf.Min(1f, __instance.timer + Time.deltaTime / __instance.animationTimeSeconds); - } - else - { - __instance.timer = Mathf.Max(0f, __instance.timer - Time.deltaTime / __instance.animationTimeSeconds); - } - Vector3 relativePos; - relativePos = new Vector3(Mathf.SmoothStep(__instance.closedPosition.x, __instance.openPosition.x, __instance.timer), Mathf.SmoothStep(__instance.closedPosition.y, __instance.openPosition.y, __instance.timer), __instance.openPosition.z); - __instance.transform.localPosition = AspectPosition.ComputePosition(AspectPosition.EdgeAlignments.LeftTop, relativePos); + + __instance.timer = __instance.open + ? Mathf.Min(1f, __instance.timer + Time.deltaTime / __instance.animationTimeSeconds) + : Mathf.Max(0f, __instance.timer - Time.deltaTime / __instance.animationTimeSeconds); + + var relativePos = new Vector3( + Mathf.SmoothStep(__instance.closedPosition.x, __instance.openPosition.x, __instance.timer), + Mathf.SmoothStep(__instance.closedPosition.y, __instance.openPosition.y, __instance.timer), + __instance.openPosition.z); + __instance.transform.localPosition = AspectPosition.ComputePosition( + AspectPosition.EdgeAlignments.LeftTop, + relativePos); return false; } - -} \ No newline at end of file +} diff --git a/MiraAPI/Patches/Roles/VentOutlinePatch.cs b/MiraAPI/Patches/Roles/VentOutlinePatch.cs new file mode 100644 index 0000000..5e50e96 --- /dev/null +++ b/MiraAPI/Patches/Roles/VentOutlinePatch.cs @@ -0,0 +1,27 @@ +using HarmonyLib; +using MiraAPI.Roles; +using MiraAPI.Utilities; +using UnityEngine; + +namespace MiraAPI.Patches.Roles; + +/// +/// Set vent outline color based on the player's role. +/// +[HarmonyPatch(typeof(Vent), nameof(Vent.SetOutline))] +public static class VentOutlinePatch +{ + public static void Postfix(Vent __instance, bool on, bool mainTarget) + { + if (PlayerControl.LocalPlayer.Data.Role is not ICustomRole customRole) + { + return; + } + + var color = customRole.RoleColor; + + __instance.myRend.material.SetFloat(ShaderID.Outline, on ? 1 : 0); + __instance.myRend.material.SetColor(ShaderID.OutlineColor, color); + __instance.myRend.material.SetColor(ShaderID.AddColor, mainTarget ? color : Color.clear); + } +} diff --git a/MiraAPI/PluginLoading/IMiraPlugin.cs b/MiraAPI/PluginLoading/IMiraPlugin.cs index 9a71645..07928da 100644 --- a/MiraAPI/PluginLoading/IMiraPlugin.cs +++ b/MiraAPI/PluginLoading/IMiraPlugin.cs @@ -6,4 +6,4 @@ public interface IMiraPlugin { string OptionsTitleText { get; } public ConfigFile GetConfigFile(); -} \ No newline at end of file +} diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 794d74e..2a735b8 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -15,7 +15,7 @@ using Reactor.Utilities; namespace MiraAPI.PluginLoading; -internal class MiraPluginManager +internal sealed class MiraPluginManager { private readonly Dictionary _registeredPlugins = []; @@ -83,6 +83,8 @@ internal class MiraPluginManager ModdedOptionsManager.RegisterAttributeOption(type, attribute, property, pluginInfo); } } + + pluginInfo.OptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority)); } private static void RegisterRoleAttribute(Assembly assembly, MiraPluginInfo pluginInfo) diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index ca34213..f63a5c8 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -35,6 +35,11 @@ public static class CustomRoleManager internal static void RegisterInRoleManager() { RoleManager.Instance.AllRoles = RoleManager.Instance.AllRoles.Concat(CustomRoles.Values).ToArray(); + + foreach (var role in CustomRoles.Values.Where(x=>x.IsDead)) + { + RoleManager.GhostRoles.Add(role.Role); + } } internal static void RegisterRoleTypes(List roles, MiraPluginInfo pluginInfo) @@ -79,10 +84,10 @@ public static class CustomRoleManager roleBehaviour.StringName = CustomStringName.CreateAndRegister(customRole.RoleName); roleBehaviour.BlurbName = CustomStringName.CreateAndRegister(customRole.RoleDescription); roleBehaviour.BlurbNameLong = CustomStringName.CreateAndRegister(customRole.RoleLongDescription); - roleBehaviour.AffectedByLightAffectors = customRole.AffectedByLight; + roleBehaviour.AffectedByLightAffectors = customRole.AffectedByLightOnAirship; roleBehaviour.CanBeKilled = customRole.CanGetKilled; - roleBehaviour.CanUseKillButton = customRole.CanKill; - roleBehaviour.TasksCountTowardProgress = customRole.TasksCount; + roleBehaviour.CanUseKillButton = customRole.UseVanillaKillButton; + roleBehaviour.TasksCountTowardProgress = customRole.TasksCountForProgress; roleBehaviour.CanVent = customRole.CanUseVent; roleBehaviour.DefaultGhostRole = customRole.GhostRole; roleBehaviour.MaxCount = customRole.MaxPlayers; diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index ffe2327..3087dbe 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -41,7 +41,7 @@ public interface ICustomRole ModdedRoleTeams Team { get; } /// - /// Gets the **maximum amount** of players that can have this role at a time. This is not the same as the **number** of players that can have this role in a game. + /// Gets the hard limit of players that can have this role. This property is used to set a limit in the Role Options menu. /// int MaxPlayers => 15; @@ -58,29 +58,19 @@ public interface ICustomRole LoadableAsset Icon => MiraAssets.Empty; /// - /// Gets the BepInEx ConfigDefinition for the amount of players that can have this role. + /// Gets a value indicating whether the role is affected by light affectors on Airship. /// - ConfigDefinition NumConfigDefinition => new("Roles", $"Num {GetType().FullName}"); + bool AffectedByLightOnAirship => Team == ModdedRoleTeams.Crewmate; /// - /// Gets the BepInEx ConfigDefinition for the chance of this role being selected. - /// - ConfigDefinition ChanceConfigDefinition => new("Roles", $"Chance {GetType().FullName}"); - - /// - /// Gets a value indicating whether the role is affected by light sabotages. - /// - bool AffectedByLight => Team == ModdedRoleTeams.Crewmate; - - /// - /// Gets a value indicating whether the role can be killed by others. + /// Gets a value indicating whether the role can be killed by vanilla murder system. /// bool CanGetKilled => Team == ModdedRoleTeams.Crewmate; /// - /// Gets a value indicating whether the role can kill others. + /// Gets a value indicating whether the role should use the vanilla kill button. /// - bool CanKill => Team == ModdedRoleTeams.Impostor; + bool UseVanillaKillButton => Team == ModdedRoleTeams.Impostor; /// /// Gets a value indicating whether the role can use vents. @@ -90,7 +80,7 @@ public interface ICustomRole /// /// Gets a value indicating whether the role's tasks count towards task progress. /// - bool TasksCount => Team == ModdedRoleTeams.Crewmate; + bool TasksCountForProgress => Team == ModdedRoleTeams.Crewmate; /// /// Gets a value indicating whether the role is a Ghost. @@ -102,6 +92,16 @@ public interface ICustomRole /// bool HideSettings => IsGhostRole; + /// + /// Gets the outline color for the KillButton if is true. + /// + Color KillButtonOutlineColor => Team switch + { + ModdedRoleTeams.Impostor => Palette.ImpostorRed, + ModdedRoleTeams.Crewmate => Palette.CrewmateBlue, + _ => RoleColor, + }; + /// /// Gets the role hint style. See enum for all options. /// @@ -112,6 +112,16 @@ public interface ICustomRole /// RoleTypes GhostRole => Team == ModdedRoleTeams.Impostor ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost; + /// + /// Gets the BepInEx ConfigDefinition for the amount of players that can have this role. + /// + ConfigDefinition NumConfigDefinition => new("Roles", $"Num {GetType().FullName}"); + + /// + /// Gets the BepInEx ConfigDefinition for the chance of this role being selected. + /// + ConfigDefinition ChanceConfigDefinition => new("Roles", $"Chance {GetType().FullName}"); + /// /// Gets the parent mod of this role. /// diff --git a/MiraAPI/Roles/ModdedRoleTeams.cs b/MiraAPI/Roles/ModdedRoleTeams.cs index ca7adad..e599cd7 100644 --- a/MiraAPI/Roles/ModdedRoleTeams.cs +++ b/MiraAPI/Roles/ModdedRoleTeams.cs @@ -4,5 +4,5 @@ public enum ModdedRoleTeams { Crewmate, Impostor, - Neutral -} \ No newline at end of file + Neutral, +} diff --git a/MiraAPI/Roles/RegisterCustomRoleAttribute.cs b/MiraAPI/Roles/RegisterCustomRoleAttribute.cs index db9a0e7..a6352f1 100644 --- a/MiraAPI/Roles/RegisterCustomRoleAttribute.cs +++ b/MiraAPI/Roles/RegisterCustomRoleAttribute.cs @@ -3,4 +3,4 @@ namespace MiraAPI.Roles; [AttributeUsage(AttributeTargets.Class)] -public class RegisterCustomRoleAttribute : Attribute; \ No newline at end of file +public class RegisterCustomRoleAttribute : Attribute; diff --git a/MiraAPI/Roles/RoleHintType.cs b/MiraAPI/Roles/RoleHintType.cs index 8994016..f3d5cc6 100644 --- a/MiraAPI/Roles/RoleHintType.cs +++ b/MiraAPI/Roles/RoleHintType.cs @@ -1,6 +1,4 @@ -// Copyright (c) PlaceholderCompany. All rights reserved. - -namespace MiraAPI.Roles; +namespace MiraAPI.Roles; /// /// The type of hint style for a role to use. diff --git a/MiraAPI/Roles/RoleId.cs b/MiraAPI/Roles/RoleId.cs index 9970d62..05f9cd0 100644 --- a/MiraAPI/Roles/RoleId.cs +++ b/MiraAPI/Roles/RoleId.cs @@ -13,7 +13,7 @@ public static class RoleId return roleId; } - + public static ushort Get(Type type) { if (!CustomRoleManager.RoleIds.TryGetValue(type, out var roleId)) @@ -23,4 +23,4 @@ public static class RoleId return roleId; } -} \ No newline at end of file +} diff --git a/MiraAPI/Utilities/Assets/LoadableAsset.cs b/MiraAPI/Utilities/Assets/LoadableAsset.cs index 464da65..798dfc9 100644 --- a/MiraAPI/Utilities/Assets/LoadableAsset.cs +++ b/MiraAPI/Utilities/Assets/LoadableAsset.cs @@ -5,17 +5,17 @@ /// Mira uses the pattern in various locations. /// You can create your own implementation of this class to load assets in different ways. /// -/// The type of the asset to be loaded +/// The type of the asset to be loaded. public abstract class LoadableAsset where T : UnityEngine.Object { /// - /// A reference to the loaded asset. Intended to be used for caching purposes. + /// Gets or sets reference to the loaded asset. Intended to be used for caching purposes. /// - protected T LoadedAsset; - + protected T? LoadedAsset { get; set; } + /// - /// The method that loads the asset. + /// Loads the asset from the source. /// - /// The loaded asset + /// The loaded asset. public abstract T LoadAsset(); -} \ No newline at end of file +} diff --git a/MiraAPI/Utilities/Assets/LoadableBundleAsset.cs b/MiraAPI/Utilities/Assets/LoadableBundleAsset.cs index 8ce1b07..6f46eef 100644 --- a/MiraAPI/Utilities/Assets/LoadableBundleAsset.cs +++ b/MiraAPI/Utilities/Assets/LoadableBundleAsset.cs @@ -7,30 +7,30 @@ namespace MiraAPI.Utilities.Assets; /// /// A utility class for loading assets from an asset bundle. /// -/// The name of the asset -/// The AssetBundle that contains the asset -/// The type of the asset to be loaded +/// The name of the asset. +/// The AssetBundle that contains the asset. +/// The type of the asset to be loaded. public class LoadableBundleAsset(string name, AssetBundle bundle) : LoadableAsset where T : UnityEngine.Object { /// /// Loads the asset from the asset bundle. /// - /// The asset + /// The asset. /// The asset did not load properly. public override T LoadAsset() { - if (LoadedAsset) + if (LoadedAsset != null) { return LoadedAsset; } LoadedAsset = bundle.LoadAsset(name); - - if (!LoadedAsset) + + if (LoadedAsset == null) { - throw new Exception($"INVALID ASSET: {name}"); + throw new InvalidOperationException($"INVALID ASSET: {name}"); } - + return LoadedAsset; } -} \ No newline at end of file +} diff --git a/MiraAPI/Utilities/Assets/LoadableResourceAsset.cs b/MiraAPI/Utilities/Assets/LoadableResourceAsset.cs index 29ce7ad..1dfda4a 100644 --- a/MiraAPI/Utilities/Assets/LoadableResourceAsset.cs +++ b/MiraAPI/Utilities/Assets/LoadableResourceAsset.cs @@ -1,28 +1,24 @@ using System.Reflection; -using Reactor.Utilities; using UnityEngine; namespace MiraAPI.Utilities.Assets; +/// +/// A utility class for loading assets from embedded resources. +/// +/// The path to the embedded resource. public class LoadableResourceAsset(string path) : LoadableAsset { private readonly Assembly _assembly = Assembly.GetCallingAssembly(); + /// public override Sprite LoadAsset() { - if (LoadedAsset) + if (LoadedAsset != null) { return LoadedAsset; } - try - { - return LoadedAsset = SpriteTools.LoadSpriteFromPath(path, _assembly); - } - catch - { - Logger.Error($"Not loading, invalid asset: {path}"); - return null; - } + return LoadedAsset = SpriteTools.LoadSpriteFromPath(path, _assembly); } -} \ No newline at end of file +} diff --git a/MiraAPI/Utilities/Assets/MiraAssets.cs b/MiraAPI/Utilities/Assets/MiraAssets.cs index 527fadb..514bd25 100644 --- a/MiraAPI/Utilities/Assets/MiraAssets.cs +++ b/MiraAPI/Utilities/Assets/MiraAssets.cs @@ -11,7 +11,5 @@ public static class MiraAssets public static readonly LoadableResourceAsset Cog = new("MiraAPI.Resources.Cog.png"); public static readonly LoadableResourceAsset Checkmark = new("MiraAPI.Resources.Checkmark.png"); public static readonly LoadableResourceAsset CheckmarkBox = new("MiraAPI.Resources.CheckMarkBox.png"); - - - public static readonly Color32 AcceptedTeal = new Color32(43, 233, 198, 255); -} \ No newline at end of file + public static readonly Color32 AcceptedTeal = new(43, 233, 198, 255); +} diff --git a/MiraAPI/Utilities/Assets/SpriteTools.cs b/MiraAPI/Utilities/Assets/SpriteTools.cs index d086a9e..bf79182 100644 --- a/MiraAPI/Utilities/Assets/SpriteTools.cs +++ b/MiraAPI/Utilities/Assets/SpriteTools.cs @@ -1,9 +1,9 @@ -using Il2CppInterop.Runtime; -using Il2CppInterop.Runtime.InteropTypes.Arrays; -using Reactor.Utilities.Extensions; -using System; +using System; using System.Reflection; +using Il2CppInterop.Runtime; +using Il2CppInterop.Runtime.InteropTypes.Arrays; using Reactor.Utilities; +using Reactor.Utilities.Extensions; using UnityEngine; namespace MiraAPI.Utilities.Assets; @@ -13,15 +13,9 @@ namespace MiraAPI.Utilities.Assets; /// public static class SpriteTools { - /// - /// Delegate for LoadImage. - /// - public delegate bool DLoadImage(IntPtr tex, IntPtr data, bool markNonReadable); + private delegate bool DLoadImage(IntPtr tex, IntPtr data, bool markNonReadable); - /// - /// The ICall for LoadImage. - /// - public static DLoadImage? ICallLoadImage; + private static DLoadImage? _iCallLoadImage; /// /// Load an image into a texture. @@ -32,18 +26,18 @@ public static class SpriteTools /// True if succeeded. public static bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable) { - ICallLoadImage ??= IL2CPP.ResolveICall("UnityEngine.ImageConversion::LoadImage"); + _iCallLoadImage ??= IL2CPP.ResolveICall("UnityEngine.ImageConversion::LoadImage"); var il2CPPArray = (Il2CppStructArray)data; - return ICallLoadImage.Invoke(tex.Pointer, il2CPPArray.Pointer, markNonReadable); + return _iCallLoadImage.Invoke(tex.Pointer, il2CPPArray.Pointer, markNonReadable); } /// /// Load a sprite from a resource path. /// /// The path to the resource. - /// A sprite made from the resource + /// A sprite made from the resource. /// The resource cannot be found. public static Sprite LoadSpriteFromPath(string resourcePath, Assembly assembly) { diff --git a/MiraAPI/Utilities/MiraNumberSuffixes.cs b/MiraAPI/Utilities/MiraNumberSuffixes.cs index 3682e2b..20ed21c 100644 --- a/MiraAPI/Utilities/MiraNumberSuffixes.cs +++ b/MiraAPI/Utilities/MiraNumberSuffixes.cs @@ -4,5 +4,5 @@ public enum MiraNumberSuffixes None, Multiplier, Seconds, - Percent -} \ No newline at end of file + Percent, +} diff --git a/MiraAPI/Utilities/ShaderID.cs b/MiraAPI/Utilities/ShaderID.cs index 08775bf..d250336 100644 --- a/MiraAPI/Utilities/ShaderID.cs +++ b/MiraAPI/Utilities/ShaderID.cs @@ -5,8 +5,8 @@ namespace MiraAPI.Utilities; public static class ShaderID { - private static readonly Dictionary Cache = new(); - + private static readonly Dictionary Cache = []; + public static int Get(string name) { if (Cache.TryGetValue(name, out var id)) @@ -18,74 +18,74 @@ public static class ShaderID Cache[name] = id; return id; } - + // For player shader public static readonly int BodyColor = Shader.PropertyToID("_BodyColor"); public static readonly int BackColor = Shader.PropertyToID("_BackColor"); public static readonly int VisorColor = Shader.PropertyToID("_VisorColor"); - + // Main texture, very obviously used in any shader with a texture public static readonly int MainTex = Shader.PropertyToID("_MainTex"); - + // Any masking stuff, like MeetingHud bubbles public static readonly int Mask = Shader.PropertyToID("_Mask"); public static readonly int MaskComp = Shader.PropertyToID("_MaskComp"); public static readonly int MaskLayer = Shader.PropertyToID("_MaskLayer"); public static readonly int Stencil = Shader.PropertyToID("_Stencil"); public static readonly int StencilComp = Shader.PropertyToID("_StencilComp"); - + // Used in many tasks public static readonly int Color = Shader.PropertyToID("_Color"); - + // Has 2 uses in the game, provided for convenience public static readonly int Opacity = Shader.PropertyToID("_Opacity"); - + // Used once in CooldownHelpers, once in PowerBarMining public static readonly int NormalizedUvs = Shader.PropertyToID("_NormalizedUvs"); - + // Used in many consoles public static readonly int Outline = Shader.PropertyToID("_Outline"); public static readonly int OutlineColor = Shader.PropertyToID("_OutlineColor"); - + // Used in some consoles public static readonly int AddColor = Shader.PropertyToID("_AddColor"); - + // Has some uses in various locations public static readonly int Percent = Shader.PropertyToID("_Percent"); public static readonly int PercentY = Shader.PropertyToID("_PercentY"); public static readonly int Desat = Shader.PropertyToID("_Desat"); - + // Used in LightSource public static readonly int PlayerRadius = Shader.PropertyToID("_PlayerRadius"); public static readonly int LightRadius = Shader.PropertyToID("_LightRadius"); public static readonly int LightOffset = Shader.PropertyToID("_LightOffset"); public static readonly int FlashlightSize = Shader.PropertyToID("_FlashlightSize"); public static readonly int FlashlightAngle = Shader.PropertyToID("_FlashlightAngle"); - + // Used once for LightSourceGpuRenderer public static readonly int DepthCompressionValue = Shader.PropertyToID("_DepthCompressionValue"); - + // Used in ProgressTracker public static readonly int Buckets = Shader.PropertyToID("_Buckets"); public static readonly int FullBuckets = Shader.PropertyToID("_FullBuckets"); - + // Used once in IntroCutscene and EndGameManager public static readonly int Rad = Shader.PropertyToID("_Rad"); - + // Used only once for Quick Chat public static readonly int FaceColor = Shader.PropertyToID("_FaceColor"); - + // Used for NavigationMinigame public static readonly int CrossHair = Shader.PropertyToID("_CrossHair"); public static readonly int CrossColor = Shader.PropertyToID("_CrossColor"); - + // Used for both SurveillanceMinigame (Planet and Normal) public static readonly int Center = Shader.PropertyToID("_Center"); public static readonly int Color2 = Shader.PropertyToID("_Color2"); - + // Used in ReactorShipRoom public static readonly int Speed = Shader.PropertyToID("_Speed"); - + // Used only once in CourseMinigame public static readonly int AltTex = Shader.PropertyToID("_AltTex"); public static readonly int Perc = Shader.PropertyToID("_Perc"); @@ -93,8 +93,7 @@ public static class ShaderID // Used once in TextMarquee public static readonly int VertexOffsetX = Shader.PropertyToID("_VertexOffsetX"); public static readonly int VertexOffsetY = Shader.PropertyToID("_VertexOffsetY"); - + // Used in VertLineBehaviour public static readonly int Fade = Shader.PropertyToID("_Fade"); - -} \ No newline at end of file +}