From a0a2e9a6ab191db8cc304e71287e5e2a2978ef7f Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Thu, 22 Aug 2024 18:48:38 -0400 Subject: [PATCH 1/9] sequential role IDs --- MiraAPI/PluginLoading/MiraPluginInfo.cs | 4 ++-- MiraAPI/PluginLoading/MiraPluginManager.cs | 16 +++++++++++----- MiraAPI/Roles/CustomRoleManager.cs | 20 ++++++++------------ MiraAPI/Roles/RegisterCustomRoleAttribute.cs | 14 +------------- MiraAPI/Roles/RoleId.cs | 17 +++++++++++++++++ 5 files changed, 39 insertions(+), 32 deletions(-) create mode 100644 MiraAPI/Roles/RoleId.cs diff --git a/MiraAPI/PluginLoading/MiraPluginInfo.cs b/MiraAPI/PluginLoading/MiraPluginInfo.cs index 8214316..1947666 100644 --- a/MiraAPI/PluginLoading/MiraPluginInfo.cs +++ b/MiraAPI/PluginLoading/MiraPluginInfo.cs @@ -6,9 +6,9 @@ using System.Collections.Generic; namespace MiraAPI.PluginLoading; -public class MiraPluginInfo(string id, IMiraPlugin miraPlugin, PluginInfo info) +public class MiraPluginInfo(IMiraPlugin miraPlugin, PluginInfo info) { - public string PluginId { get; set; } = id; + public string PluginId { get; set; } = info.Metadata.GUID; public IMiraPlugin MiraPlugin { get; set; } = miraPlugin; public PluginInfo PluginInfo { get; set; } = info; public ConfigFile PluginConfig { get; set; } = miraPlugin.GetConfigFile(); diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 45dccb2..a23e928 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using MiraAPI.Hud; +using Reactor.Networking; namespace MiraAPI.PluginLoading; @@ -27,15 +28,14 @@ internal class MiraPluginManager internal void Initialize() { Instance = this; - IL2CPPChainloader.Instance.PluginLoad += (_, assembly, plugin) => + IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) => { if (!plugin.GetType().GetInterfaces().Contains(typeof(IMiraPlugin))) { return; } - var id = MetadataHelper.GetMetadata(plugin.GetType()).GUID; - var info = new MiraPluginInfo(id, plugin as IMiraPlugin, IL2CPPChainloader.Instance.Plugins[id]); + var info = new MiraPluginInfo(plugin as IMiraPlugin, pluginInfo); RegisterAllOptions(assembly, info); @@ -44,7 +44,7 @@ internal class MiraPluginManager RegisteredPlugins.Add(assembly, info); - Logger.Info($"Registering mod {id} with Mira API."); + Logger.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API."); }; } @@ -92,10 +92,16 @@ internal class MiraPluginManager { continue; } + + if (!ModList.GetById(pluginInfo.PluginId).IsRequiredOnAllClients) + { + Logger.Error("Custom roles are only supported on all clients."); + return; + } ClassInjector.RegisterTypeInIl2Cpp(type); - var role = CustomRoleManager.RegisterRole(type, attribute.RoleId, pluginInfo); + var role = CustomRoleManager.RegisterRole(type, pluginInfo); try { diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index 329b3c6..0c3ff2f 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -20,23 +20,19 @@ public static class CustomRoleManager { public static readonly Dictionary CustomRoles = new(); - private static int GetNextRoleId(ushort requestedId) + private static ushort _roleId = 100; + + private static ushort GetNextRoleId() { - while (CustomRoles.ContainsKey(requestedId) || requestedId <= Enum.GetNames().Length) - { - Logger.Error(requestedId + ", " + (requestedId+1)); - requestedId++; - } - - return requestedId; + return _roleId++; } - public static void RegisterInRoleManager() + internal static void RegisterInRoleManager() { RoleManager.Instance.AllRoles = RoleManager.Instance.AllRoles.Concat(CustomRoles.Values).ToArray(); } - internal static RoleBehaviour RegisterRole(Type roleType, ushort roleId, MiraPluginInfo parentMod) + internal static RoleBehaviour RegisterRole(Type roleType, MiraPluginInfo parentMod) { if (!(typeof(RoleBehaviour).IsAssignableFrom(roleType) && typeof(ICustomRole).IsAssignableFrom(roleType))) { @@ -52,7 +48,7 @@ public static class CustomRoleManager return null; } - roleBehaviour.Role = (RoleTypes)GetNextRoleId(roleId); + roleBehaviour.Role = (RoleTypes)GetNextRoleId(); roleBehaviour.TeamType = customRole.Team == ModdedRoleTeams.Neutral ? RoleTeamTypes.Crewmate : (RoleTeamTypes)customRole.Team; roleBehaviour.NameColor = customRole.RoleColor; roleBehaviour.StringName = CustomStringName.CreateAndRegister(customRole.RoleName); @@ -72,7 +68,7 @@ public static class CustomRoleManager RoleManager.GhostRoles.Add(roleBehaviour.Role); } - CustomRoles.Add(roleId, roleBehaviour); + CustomRoles.Add(_roleId, roleBehaviour); if (customRole.HideSettings) { diff --git a/MiraAPI/Roles/RegisterCustomRoleAttribute.cs b/MiraAPI/Roles/RegisterCustomRoleAttribute.cs index f32cdd7..db9a0e7 100644 --- a/MiraAPI/Roles/RegisterCustomRoleAttribute.cs +++ b/MiraAPI/Roles/RegisterCustomRoleAttribute.cs @@ -3,16 +3,4 @@ namespace MiraAPI.Roles; [AttributeUsage(AttributeTargets.Class)] -public class RegisterCustomRoleAttribute : Attribute -{ - public ushort RoleId { get; } - - public RegisterCustomRoleAttribute() - { - RoleId = (ushort)(20 + CustomRoleManager.CustomRoles.Count); - } - public RegisterCustomRoleAttribute(ushort roleId) - { - RoleId = roleId; - } -} \ No newline at end of file +public class RegisterCustomRoleAttribute : Attribute; \ No newline at end of file diff --git a/MiraAPI/Roles/RoleId.cs b/MiraAPI/Roles/RoleId.cs new file mode 100644 index 0000000..bb7b714 --- /dev/null +++ b/MiraAPI/Roles/RoleId.cs @@ -0,0 +1,17 @@ +using System; +using System.Linq; + +namespace MiraAPI.Roles; + +public class RoleId +{ + public static ushort Get() where T : RoleBehaviour + { + if (!CustomRoleManager.CustomRoles.Values.OfType().Any()) + { + throw new InvalidOperationException($"Role {typeof(T)} is not registered"); + } + + return (ushort)CustomRoleManager.CustomRoles.Values.OfType().Single().Role; + } +} \ No newline at end of file From a2b1e9ddbfbdfe8e3e1130008ff5570c8acdae56 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:44:28 -0400 Subject: [PATCH 2/9] increase contrast on AlternateColors --- MiraAPI/Utilities/Extensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 31e729e..47f516d 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -72,7 +72,7 @@ public static class Extensions public static Color DarkenColor(this Color color) { - return new Color(color.r - 0.3f, color.g - 0.3f, color.b - 0.3f); + return new Color(color.r - 0.45f, color.g - 0.45f, color.b - 0.45f); } public static Color GetAlternateColor(this Color color) { @@ -81,7 +81,7 @@ public static class Extensions public static Color LightenColor(this Color color) { - return new Color(color.r + 0.3f, color.g + 0.3f, color.b + 0.3f); + return new Color(color.r + 0.45f, color.g + 0.45f, color.b + 0.45f); } public static bool IsColorDark(this Color color) From d96b146e591c567200e3584a6fa7528a919fd809 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:44:41 -0400 Subject: [PATCH 3/9] set neutral header color for uniformity --- MiraAPI/Patches/Options/RoleSettingMenuPatches.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 6e24a53..b7b46cd 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -107,6 +107,8 @@ public static class RoleSettingMenuPatches Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform); categoryHeaderEditRole3.SetHeader(StringNames.None, 20); categoryHeaderEditRole3.Title.text = "Neutral Roles"; + categoryHeaderEditRole3.Background.color = Color.gray; + categoryHeaderEditRole3.Title.color = Color.white; categoryHeaderEditRole3.transform.localPosition = new Vector3(4.986f, num, -2f); num -= 0.522f; From 5e9c592aabe225dc7d6a8a7ad3235314ad8fd30a Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:44:53 -0400 Subject: [PATCH 4/9] prep for view pane --- MiraAPI/GameOptions/IModdedOption.cs | 1 + MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs | 7 ++++++- MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs | 5 +++++ MiraAPI/GameOptions/OptionTypes/ModdedOption.cs | 2 ++ MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs | 5 +++++ MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs | 5 +++++ 6 files changed, 24 insertions(+), 1 deletion(-) diff --git a/MiraAPI/GameOptions/IModdedOption.cs b/MiraAPI/GameOptions/IModdedOption.cs index bd8a6f3..c4b88c1 100644 --- a/MiraAPI/GameOptions/IModdedOption.cs +++ b/MiraAPI/GameOptions/IModdedOption.cs @@ -19,6 +19,7 @@ public interface IModdedOption public ConfigDefinition ConfigDefinition { get; } public void ValueChanged(OptionBehaviour optionBehaviour); public OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container); + public abstract float GetFloatData(); public NetData GetNetData(); public void HandleNetData(byte[] data); } \ No newline at end of file diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs index 30de265..1e43a2b 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs @@ -39,7 +39,12 @@ public class ModdedEnumOption : ModdedOption return stringOption; } - + + public override float GetFloatData() + { + return Value; + } + public override NetData GetNetData() { return new NetData(Id, BitConverter.GetBytes(Value)); diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs index 51e350e..b10926c 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedNumberOption.cs @@ -65,6 +65,11 @@ public class ModdedNumberOption : ModdedOption return numberOption; } + public override float GetFloatData() + { + return Value; + } + public override NetData GetNetData() { return new NetData(Id, BitConverter.GetBytes(Value)); diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs index e06ca88..33a066c 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs @@ -77,6 +77,8 @@ public abstract class ModdedOption : IModdedOption OnValueChanged(newValue); } + public abstract float GetFloatData(); + public abstract NetData GetNetData(); public abstract void HandleNetData(byte[] data); diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs index 484126f..6b878b9 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedStringOption.cs @@ -40,6 +40,11 @@ public class ModdedStringOption : ModdedOption return stringOption; } + public override float GetFloatData() + { + return Value; + } + public override NetData GetNetData() { return new NetData(Id, BitConverter.GetBytes(Value)); diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs index 15b920e..f90f83f 100644 --- a/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs +++ b/MiraAPI/GameOptions/OptionTypes/ModdedToggleOption.cs @@ -32,6 +32,11 @@ public class ModdedToggleOption : ModdedOption return toggleOption; } + public override float GetFloatData() + { + return Value ? 1 : 0; + } + public override NetData GetNetData() { return new NetData(Id, BitConverter.GetBytes(Value)); From 564f524dd6359b52238f0d18862f786fb3e1bad2 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:45:22 -0400 Subject: [PATCH 5/9] lobby view pane almost done --- .../Patches/Options/LobbyViewPanePatches.cs | 275 +++++++++++++++++- 1 file changed, 270 insertions(+), 5 deletions(-) diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index 0f2fc49..f2cf6d5 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -1,14 +1,28 @@ -using HarmonyLib; +#nullable enable +using System; +using System.Collections.Generic; +using System.Linq; +using HarmonyLib; +using MiraAPI.PluginLoading; +using MiraAPI.Roles; +using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using UnityEngine; using UnityEngine.Events; using UnityEngine.UI; +using Object = UnityEngine.Object; 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.ElementAt(CurrentSelectedMod-1).Value; + + [HarmonyPostfix] [HarmonyPatch(nameof(LobbyViewSettingsPane.Awake))] public static void AwakePatch(LobbyViewSettingsPane __instance) @@ -16,7 +30,7 @@ public static class LobbyViewPanePatches __instance.gameModeText.transform.localPosition = new Vector3(-2.3f, 2.4f, -2); // Create the next button - GameObject nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject; + var nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject; nextButton.GetComponent().size = new Vector2(0.2f, 0.3f); nextButton.transform.localPosition = new Vector3(-5.4f, 2.4f, -2f); nextButton.transform.localScale = new Vector3(3, 3, 2); @@ -30,24 +44,275 @@ public static class LobbyViewPanePatches hover.transform.localPosition = new Vector3(0, 0f, 0.3f); hover.sprite = MiraAssets.NextButtonActive.LoadAsset(); - PassiveButton passiveButton = nextButton.gameObject.GetComponent(); + var passiveButton = nextButton.gameObject.GetComponent(); passiveButton.OnClick = new Button.ButtonClickedEvent(); passiveButton.OnClick.AddListener((UnityAction)(() => { + CurrentSelectedMod += 1; + if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins.Count) + { + CurrentSelectedMod = 0; + } + __instance.RefreshTab(); })); // Create the back button - GameObject backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject; + 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 = true; - PassiveButton passiveButton2 = backButton.gameObject.GetComponent(); + var passiveButton2 = backButton.gameObject.GetComponent(); passiveButton2.OnClick = new Button.ButtonClickedEvent(); passiveButton2.OnClick.AddListener((UnityAction)(() => { + CurrentSelectedMod -= 1; + if (CurrentSelectedMod < 0) + { + CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins.Count; + } + __instance.RefreshTab(); })); } + + [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; + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(LobbyViewSettingsPane.DrawRolesTab))] + public static bool DrawRolesTabPatch(LobbyViewSettingsPane __instance) + { + 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) + { + if (group.Options.Count == 0) + { + continue; + } + + var categoryHeaderMasked = Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true); + categoryHeaderMasked.SetHeader(StringNames.Name, 61); + categoryHeaderMasked.Title.text = group.GroupName; + categoryHeaderMasked.transform.localScale = Vector3.one; + categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, num, -2f); + instance.settingsInfo.Add(categoryHeaderMasked.gameObject); + num -= 0.85f; + + var i = 0; + + foreach (var option in group.Options) + { + if (!option.Visible.Invoke()) + { + continue; + } + + var viewSettingsInfoPanel = Object.Instantiate(instance.infoPanelOrigin, instance.settingsContainer, true); + viewSettingsInfoPanel.transform.localScale = Vector3.one; + float num2; + if (i % 2 == 0) + { + num2 = -8.95f; + if (i > 0) + { + num -= 0.59f; + } + } + else + { + num2 = -3f; + } + viewSettingsInfoPanel.transform.localPosition = new Vector3(num2, num, -2f); + + var data = option.Data; + + if (data.Type == OptionTypes.Checkbox) + { + 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); + } + + private static void DrawRolesTab(LobbyViewSettingsPane instance) + { + if (SelectedMod == null) + { + return; + } + + var num = 0.95f; + var num2 = -6.53f; + var categoryHeaderMasked = + Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true); + categoryHeaderMasked.SetHeader(StringNames.RoleQuotaLabel, 61); + 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()) + { + var categoryHeaderRoleVariant = + Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true); + + switch (team) + { + case ModdedRoleTeams.Crewmate: + categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61); + break; + case ModdedRoleTeams.Impostor: + categoryHeaderRoleVariant.SetHeader(StringNames.ImpostorRolesHeader, 61); + break; + case ModdedRoleTeams.Neutral: + default: + categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61); + categoryHeaderRoleVariant.Title.text = team + " Roles"; + categoryHeaderRoleVariant.Background.color = Color.gray; + 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); + num -= 0.696f; + + var filteredRoles = SelectedMod.CustomRoles.Values + .OfType() + .Where(x => !x.HideSettings && x.Team == team); + + foreach (var role in filteredRoles) + { + var roleBehaviour = role as RoleBehaviour; + if (roleBehaviour == null) + { + 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); + viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one; + viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f); + + if (!flag) + { + //list.Add(role.GetType()); + } + + viewSettingsInfoPanelRoleVariant.SetInfo(roleBehaviour.NiceName, numPerGame, chancePerGame, + 61, role.RoleColor, role.Icon.LoadAsset(), true); + + viewSettingsInfoPanelRoleVariant.titleText.color = + viewSettingsInfoPanelRoleVariant.chanceTitle.color = + viewSettingsInfoPanelRoleVariant.chanceBackground.color = + viewSettingsInfoPanelRoleVariant.background.color = + role.RoleColor.GetAlternateColor(); + instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject); + num -= 0.664f; + } + + + } + + if (list.Count > 0) + { + var categoryHeaderMasked2 = + Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true); + categoryHeaderMasked2.SetHeader(StringNames.RoleSettingsLabel, 61); + categoryHeaderMasked2.transform.localScale = Vector3.one; + categoryHeaderMasked2.transform.localPosition = new Vector3(-9.77f, num, -2f); + instance.settingsInfo.Add(categoryHeaderMasked2.gameObject); + num -= 1.7f; + var num3 = 0f; + for (var k = 0; k < list.Count; k++) + { + float num4; + if (k % 2 == 0) + { + num4 = -5.8f; + if (k > 0) + { + num -= num3 + 0.59f; + num3 = 0f; + } + } + else + { + num4 = 0.14999962f; + } + + var advancedRoleViewPanel = + Object.Instantiate(instance.advancedRolePanelOrigin, instance.settingsContainer, true); + advancedRoleViewPanel.transform.localScale = Vector3.one; + advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f); + var num5 = advancedRoleViewPanel.SetUp(list[k], 0.59f, 61); + if (num5 > num3) + { + num3 = num5; + } + + instance.settingsInfo.Add(advancedRoleViewPanel.gameObject); + } + } + + instance.scrollBar.SetYBoundsMax(-num); + } } \ No newline at end of file From cd1529b4bf1e997ca2cf69c3c8d10e6cd913f645 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:51:00 -0400 Subject: [PATCH 6/9] QOL in view tab --- MiraAPI/Patches/Options/LobbyViewPanePatches.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index f2cf6d5..44b02bb 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -54,6 +54,7 @@ public static class LobbyViewPanePatches CurrentSelectedMod = 0; } __instance.RefreshTab(); + __instance.scrollBar.ScrollToTop(); })); // Create the back button @@ -74,6 +75,7 @@ public static class LobbyViewPanePatches CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins.Count; } __instance.RefreshTab(); + __instance.scrollBar.ScrollToTop(); })); } @@ -202,6 +204,15 @@ public static class LobbyViewPanePatches foreach (var team in Enum.GetValues()) { + 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); @@ -227,10 +238,6 @@ public static class LobbyViewPanePatches instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject); num -= 0.696f; - var filteredRoles = SelectedMod.CustomRoles.Values - .OfType() - .Where(x => !x.HideSettings && x.Team == team); - foreach (var role in filteredRoles) { var roleBehaviour = role as RoleBehaviour; From 4cd8aa0bd7f0effa7846031b33cc158247f10b6b Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:18:38 -0400 Subject: [PATCH 7/9] advanced role settings in view tab --- .../Patches/Options/LobbyViewPanePatches.cs | 74 +++++++++++++++++-- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index 44b02bb..187265c 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -200,7 +200,7 @@ public static class LobbyViewPanePatches categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f); instance.settingsInfo.Add(categoryHeaderMasked.gameObject); - var list = new List(); + var list = new List(); foreach (var team in Enum.GetValues()) { @@ -238,9 +238,9 @@ public static class LobbyViewPanePatches instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject); num -= 0.696f; - foreach (var role in filteredRoles) + foreach (var customRole in filteredRoles) { - var roleBehaviour = role as RoleBehaviour; + var roleBehaviour = customRole as RoleBehaviour; if (roleBehaviour == null) { continue; @@ -259,19 +259,23 @@ public static class LobbyViewPanePatches viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one; viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f); - if (!flag) + var advancedRoleOptions = SelectedMod.Options + .Where(x => x.AdvancedRole == customRole.GetType()) + .ToList(); + + if (!flag && advancedRoleOptions.Count > 0) { - //list.Add(role.GetType()); + list.Add(customRole.GetType()); } viewSettingsInfoPanelRoleVariant.SetInfo(roleBehaviour.NiceName, numPerGame, chancePerGame, - 61, role.RoleColor, role.Icon.LoadAsset(), true); + 61, customRole.RoleColor, customRole.Icon.LoadAsset(), true); viewSettingsInfoPanelRoleVariant.titleText.color = viewSettingsInfoPanelRoleVariant.chanceTitle.color = viewSettingsInfoPanelRoleVariant.chanceBackground.color = viewSettingsInfoPanelRoleVariant.background.color = - role.RoleColor.GetAlternateColor(); + customRole.RoleColor.GetAlternateColor(); instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject); num -= 0.664f; } @@ -310,7 +314,8 @@ public static class LobbyViewPanePatches Object.Instantiate(instance.advancedRolePanelOrigin, instance.settingsContainer, true); advancedRoleViewPanel.transform.localScale = Vector3.one; advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f); - var num5 = advancedRoleViewPanel.SetUp(list[k], 0.59f, 61); + var num5 = SetUpAdvancedRoleViewPanel(advancedRoleViewPanel, list[k], 0.59f, 61); + if (num5 > num3) { num3 = num5; @@ -322,4 +327,57 @@ public static class LobbyViewPanePatches instance.scrollBar.SetYBoundsMax(-num); } + + 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 (role == null) + { + return 0; + } + + var customRole = role as ICustomRole; + + 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]; + BaseGameSetting 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; + } } \ No newline at end of file From f39580e8588578ddf3f97816e0abee82055393d4 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:26:32 -0400 Subject: [PATCH 8/9] fixes --- MiraAPI/Patches/Options/LobbyViewPanePatches.cs | 5 ++++- MiraAPI/PluginLoading/MiraPluginManager.cs | 4 +--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index 187265c..74c0fd0 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -342,7 +342,10 @@ public static class LobbyViewPanePatches return 0; } - var customRole = role as ICustomRole; + 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); diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index c65a7c9..8a48e38 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -1,5 +1,4 @@ -using BepInEx; -using BepInEx.Unity.IL2CPP; +using BepInEx.Unity.IL2CPP; using Il2CppInterop.Runtime.Injection; using MiraAPI.GameOptions; using MiraAPI.GameOptions.Attributes; @@ -12,7 +11,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using MiraAPI.Utilities; -using MiraAPI.Hud; using Reactor.Networking; namespace MiraAPI.PluginLoading; From 37aee8712ec52dc77c3e786ccded50bf7543fcc8 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:54:40 -0400 Subject: [PATCH 9/9] readme update --- README.md | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 504c2cb..e7ce157 100644 --- a/README.md +++ b/README.md @@ -5,14 +5,16 @@ A thorough, but simple, Among Us modding API and utility library that covers: - Roles - Options -- Modifiers -- Game Modes +- Buttons - Assets -- HUD Elements - Compatibility +- ~~Modifiers~~ (coming soon) +- ~~Game Modes~~ (coming soon) Mira API strives to be simple and easy to use, while also using as many base game elements as possible. The result is a less intrusive, better modding API that covers general use cases. +**Join the [Discord](https://discord.gg/all-of-us-launchpad-794950428756410429) for support and to stay updated on the latest releases** + # Usage To start using Mira API, you need to: @@ -22,16 +24,46 @@ To start using Mira API, you need to: For a full example, see [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/ExamplePlugin.cs). +## Recommended Project Structure +It is highly recommended to follow this project structure when using Mira API in order to keep your code clean and organized. You can also view the Example Mod in this repository for some guidance. +``` +MyMiraMod/ +├── Buttons/ +│ └── MyCoolButton.cs +├── Options/ +│ ├── Roles/ +│ │ └── CoolCustomRoleOptions.cs +│ └── MainOptionGroup.cs +├── Patches/ +│ ├── Roles/ +│ │ └── CoolCustomRole/ +│ │ ├── PlayerControlPatches.cs +│ │ └── ExileControllerPatches.cs +│ └── General/ +│ └── HudManagerPatches.cs +├── Resources/ +│ ├── CoolButton.png +│ └── myAssets-win-x86.bundle +├── Roles/ +│ └── CoolCustomRole.cs +├── MyMiraModPlugin.cs +└── MyModAssets.cs +``` + ## Roles Roles are very simple in Mira API. There are 3 things you need to do to create a custom role: -1. Create a class that inherits from a base game role (like `RoleBehaviour`, `CrewmateRole`, `ImpostorRole`, etc). +1. Create a class that inherits from a base game role (like `CrewmateRole`, `ImpostorRole`, etc) 2. Implement the `ICustomRole` interface from Mira API. 3. Add the `[RegisterCustomRole]` attribute to the class. +Note: For step 1, if you are making neutral roles, choose either `CrewmateRole` or `ImpostorRole` as the base depending on if it can kill or not! + +Mira API handles everything else, from adding the proper options to the settings menu, to managing the role assignment at the start of the game. There are no extra steps on the developer's part. + See [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Roles/CustomRole.cs) for a code example. ## Options -Options are also very simple in Mira API. Options are split up into Groups and Options. Every Option needs to be in a Group. +Options are also very simple in Mira API. Mira API handles all the hard work behind the scenes, so developers only have to follow a few steps to create their custom options. The Options API is split up into Groups and Options. Every Option needs to be in a Group. To create a group, you need to create a class that inherits from the `AbstractOptionGroup` abstract class. Groups contain 4 properties, `GroupName`, `GroupColor`, `GroupVisible`, and `AdvancedRole`. Only the `GroupName` is required. @@ -57,6 +89,8 @@ Once you have an options group, there are two ways to make the actual options: - Use an Option Attribute with a property. - Create a ModdedOption property. +### Option Attributes + This is an example of using an Option Attribute on a property: ```csharp // The first parameter is always the name of the option. The rest are dependent on the type of option. @@ -64,6 +98,26 @@ This is an example of using an Option Attribute on a property: public float SussyLevel { get; set; } = 4f; // You can set a default value here. ``` +Here are the available Option Attributes and their signatures: +```csharp +ModdedEnumOption(string name, Type enumType, Type roleType = null) + +ModdedNumberOption( + string name, + float min, + float max, + float increment=1 + NumberSuffixes suffixType = NumberSuffixes.None, + bool zeroInfinity = false, + Type roleType = null) + +ModdedStringOption(string title, string[] values, Type roleType = null) + +ModdedToggleOption(string name, Type roleType = null) +``` + +### ModdedOption Properties + And this is an example of a ModdedOption property: ```csharp public ModdedToggleOption YeezusAbility { get; } = new ModdedToggleOption("Yeezus Ability", false); @@ -114,6 +168,8 @@ Mira API provides a simple interface for adding ability buttons to the game. The 1. Create a class that inherits from the `CustomActionButton` class and implement the properties and methods. 2. Add the `[RegisterCustomButton]` attribute to the class. +All other tasks and logic required to add the button to the game are handled by Mira API. + The button API is simple, but provides a lot of flexibility. There are various methods you can override to customize the behaviour of your button. See [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI/Hud/CustomActionButton.cs) for a full list of methods you can override. An example button can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Buttons/ExampleButton.cs).