From 645d1dad1cf600d89385c55498936cb8970a2805 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Wed, 12 Mar 2025 18:01:58 -0400 Subject: [PATCH 1/5] Broken thing --- MiraAPI/MiraApiPlugin.cs | 9 ++- MiraAPI/Patches/MainMenuManagerPatch.cs | 26 +++++++ .../Patches/Options/LobbyViewPanePatches.cs | 8 +-- .../Options/NotificationPopperPatch.cs | 36 +++++++++- MiraAPI/Roles/CustomRoleConfiguration.cs | 5 +- MiraAPI/Roles/CustomRoleManager.cs | 16 +++++ MiraAPI/Utilities/Helpers.cs | 67 +++++++++++++++++++ 7 files changed, 157 insertions(+), 10 deletions(-) create mode 100644 MiraAPI/Patches/MainMenuManagerPatch.cs diff --git a/MiraAPI/MiraApiPlugin.cs b/MiraAPI/MiraApiPlugin.cs index 3380820..fa601ce 100644 --- a/MiraAPI/MiraApiPlugin.cs +++ b/MiraAPI/MiraApiPlugin.cs @@ -1,12 +1,19 @@ -using BepInEx; +using System; +using BepInEx; using BepInEx.Unity.IL2CPP; using HarmonyLib; +using Il2CppInterop.Runtime; using MiraAPI.PluginLoading; +using MiraAPI.Roles; +using MonoMod.Utils; using Reactor; using Reactor.Networking; using Reactor.Networking.Attributes; using Reactor.Utilities; +using TMPro; using UnityEngine; +using UnityEngine.ProBuilder; +using Delegate = Il2CppSystem.Delegate; namespace MiraAPI; diff --git a/MiraAPI/Patches/MainMenuManagerPatch.cs b/MiraAPI/Patches/MainMenuManagerPatch.cs new file mode 100644 index 0000000..a54efe0 --- /dev/null +++ b/MiraAPI/Patches/MainMenuManagerPatch.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq; +using HarmonyLib; +using Il2CppInterop.Runtime; +using MiraAPI.Roles; +using MiraAPI.Utilities; +using TMPro; + +namespace MiraAPI.Patches; + +[HarmonyPatch(typeof(MainMenuManager))] + +public class MainMenuManagerPatch +{ + [HarmonyPostfix] + [HarmonyPatch(nameof(MainMenuManager.Start))] + public static void StartPatch() + { + TMP_Settings.defaultSpriteAsset.fallbackSpriteAssets.Add(CustomRoleManager.SpriteAsset); + /*TMP_Text.OnSpriteAssetRequest += DelegateSupport.ConvertDelegate>(new Func((index, str) => + { + var asset = Helpers.RegisteredSpriteAssets.First(x => x.name.Contains(str)); + return asset != null ? asset : null; + }));*/ + } +} diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index 89a6a24..c8e782a 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -330,8 +330,8 @@ public static class LobbyViewPanePatches numPerGame, chancePerGame, 61, - customRole.OptionsMenuColor, - customRole.Configuration.Icon.LoadAsset(), + customRole.RoleColor, + customRole.Configuration.Icon != null ? customRole.Configuration.Icon.LoadAsset() : MiraAssets.Empty.LoadAsset(), true); viewSettingsInfoPanelRoleVariant.iconSprite.transform.localScale = new Vector3(0.365f, 0.365f, 1f); viewSettingsInfoPanelRoleVariant.iconSprite.transform.localPosition = new Vector3(0.7144f, -0.028f, -2); @@ -340,7 +340,7 @@ public static class LobbyViewPanePatches viewSettingsInfoPanelRoleVariant.chanceTitle.color = viewSettingsInfoPanelRoleVariant.chanceBackground.color = viewSettingsInfoPanelRoleVariant.background.color = - customRole.OptionsMenuColor.FindAlternateColor(); + customRole.RoleColor.GetAlternateColor(); instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject); num -= 0.664f; } @@ -418,7 +418,7 @@ public static class LobbyViewPanePatches role.StringName, maskLayer, role.TeamType == RoleTeamTypes.Crewmate, - customRole.Configuration.Icon.LoadAsset()); + customRole.Configuration.Icon != null ? customRole.Configuration.Icon.LoadAsset() : MiraAssets.Empty.LoadAsset()); viewPanel.header.icon.transform.localScale = new Vector3(0.465f, 0.465f, 1f); viewPanel.divider.material.SetInt(PlayerMaterial.MaskLayer, maskLayer); diff --git a/MiraAPI/Patches/Options/NotificationPopperPatch.cs b/MiraAPI/Patches/Options/NotificationPopperPatch.cs index 02efe2b..522bc2b 100644 --- a/MiraAPI/Patches/Options/NotificationPopperPatch.cs +++ b/MiraAPI/Patches/Options/NotificationPopperPatch.cs @@ -2,6 +2,10 @@ using HarmonyLib; using Il2CppSystem; using MiraAPI.Roles; +using Reactor.Utilities; +using UnityEngine; +using Action = System.Action; +using Object = Il2CppSystem.Object; namespace MiraAPI.Patches.Options; @@ -18,11 +22,14 @@ public static class NotificationPopperPatch [HarmonyArgument(3)] RoleTeamTypes teamType, [HarmonyArgument(4)] bool playSound) { - if (CustomRoleManager.CustomRoles.Values.FirstOrDefault(x=>x.StringName==key) is not ICustomRole customRole) + var role = CustomRoleManager.CustomRoles.Values.FirstOrDefault(x => x.StringName == key); + if (!role || role is not ICustomRole) { return true; } + Logger.Error("UPDATING ROLE TEXT"); + var text = teamType == RoleTeamTypes.Crewmate ? Palette.CrewmateSettingChangeText.ToTextColor() : Palette.ImpostorRed.ToTextColor(); @@ -30,6 +37,7 @@ public static class NotificationPopperPatch var item = TranslationController.Instance.GetString( StringNames.LobbyChangeSettingNotificationRole, string.Concat( + $"", "", text, TranslationController.Instance.GetString(key, Array.Empty()), @@ -39,7 +47,31 @@ public static class NotificationPopperPatch "" + roleChance + "%" ); - __instance.SettingsChangeMessageLogic(key, item, playSound); + CustomMessageLogic(__instance, role, item, playSound); return false; } + private static void CustomMessageLogic(NotificationPopper __instance, RoleBehaviour role, string item, bool playSound) + { + if (__instance.lastMessageKey == (int)role.StringName && __instance.activeMessages.Count > 0) + { + __instance.activeMessages.ToArray()[__instance.activeMessages.Count - 1].UpdateMessage(item); + } + else + { + __instance.lastMessageKey = (int)role.StringName; + var newMessage = UnityEngine.Object.Instantiate(__instance.notificationMessageOrigin, Vector3.zero, Quaternion.identity, __instance.transform); + newMessage.transform.localPosition = new Vector3(0f, 0f, -2f); + newMessage.SetUp(item, __instance.settingsChangeSprite, __instance.settingsChangeColor, new Action(delegate + { + __instance.OnMessageDestroy(newMessage); + })); + __instance.ShiftMessages(); + __instance.AddMessageToQueue(newMessage); + } + if (playSound) + { + SoundManager.Instance.PlaySoundImmediate(__instance.settingsChangeSound, false, 1f, 1f, null); + } + } } + diff --git a/MiraAPI/Roles/CustomRoleConfiguration.cs b/MiraAPI/Roles/CustomRoleConfiguration.cs index efaf444..85094a9 100644 --- a/MiraAPI/Roles/CustomRoleConfiguration.cs +++ b/MiraAPI/Roles/CustomRoleConfiguration.cs @@ -32,7 +32,6 @@ public record struct CustomRoleConfiguration TasksCountForProgress = role.Team is ModdedRoleTeams.Crewmate; HideSettings = roleBehaviour?.IsDead == true; ShowInFreeplay = roleBehaviour?.IsDead == false; - OptionsScreenshot = null; } /// @@ -58,12 +57,12 @@ public record struct CustomRoleConfiguration /// /// Gets or sets the Sprite used for the Role Options menu screenshot. /// - public LoadableAsset? OptionsScreenshot { get; set; } + public LoadableAsset? OptionsScreenshot { get; set; } = null; /// /// Gets or sets the Sprite used for the Role Icon. /// - public LoadableAsset Icon { get; set; } = MiraAssets.Empty; + public LoadableAsset? Icon { get; set; } = null; /// /// Gets or sets a value indicating whether the role is affected by light affectors on Airship. diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index a778a09..1484e26 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -13,6 +13,7 @@ using Reactor.Utilities; using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; +using UnityEngine.TextCore; using Object = UnityEngine.Object; namespace MiraAPI.Roles; @@ -24,7 +25,9 @@ public static class CustomRoleManager { internal static readonly Dictionary CustomRoles = []; internal static readonly Dictionary RoleIds = []; + internal static TMP_SpriteAsset SpriteAsset; + private static Dictionary _roleIcons = []; private static ushort _roleId = 100; private static ushort GetNextRoleId() @@ -58,6 +61,8 @@ public static class CustomRoleManager pluginInfo.CustomRoles.Add((ushort)role.Role, role); } + + SpriteAsset = Helpers.CreateSpriteAsset(_roleIcons, "RoleIcons"); } private static RoleBehaviour? RegisterRole(Type roleType, MiraPluginInfo parentMod) @@ -93,6 +98,17 @@ public static class CustomRoleManager roleBehaviour.MaxCount = customRole.Configuration.MaxRoleCount; roleBehaviour.RoleScreenshot = customRole.Configuration.OptionsScreenshot?.LoadAsset(); + if (customRole.Configuration.Icon != null) + { + var asset = customRole.Configuration.Icon.LoadAsset(); + if (asset != null) + { + roleBehaviour.RoleIconSolid = asset; + roleBehaviour.RoleIconWhite = asset; + _roleIcons.Add(roleBehaviour.NiceName, asset); + } + } + if (roleBehaviour.IsDead) { RoleManager.GhostRoles.Add(roleBehaviour.Role); diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 6b3b79f..7e5e812 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -1,11 +1,15 @@ using System.Collections; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Text; +using HarmonyLib; using MiraAPI.Roles; +using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; +using UnityEngine.TextCore; using Object = UnityEngine.Object; namespace MiraAPI.Utilities; @@ -24,6 +28,69 @@ public static class Helpers return PlayerControl.AllPlayerControls.ToArray().Where(x => !x.Data.IsDead).ToList(); } + internal static readonly List RegisteredSpriteAssets = []; + + /// + /// Allows you to create a custom sprite asset. Normally used for TextMeshPro. + /// Ensure you set the TMP object's sprite asset to this one and use the sprite name tag. + /// https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/RichTextSprite.html + /// + /// The dictionary of sprites you want to add to the asset. String is the name of the sprite you'd like to use for rich text. + /// The name of the sprite asset. + /// The created sprite asset. + public static TMP_SpriteAsset CreateSpriteAsset(Dictionary sprites, string spriteAssetName) + { + var textures = sprites.Values.Select(x => x.texture).ToArray(); + var asset = ScriptableObject.CreateInstance(); + var image = new Texture2D(2048, 2048) { name = spriteAssetName }; + var rects = image.PackTextures(textures, 2); + + for (var i = 0; i < rects.Length; i++) + { + var rect = rects[i]; + var pair = sprites.ElementAt(i); + + var glyph = new TMP_SpriteGlyph + { + glyphRect = new GlyphRect + { + x = (int)(rect.x * image.width), + y = (int)(rect.y * image.height), + width = (int)(rect.width * image.width), + height = (int)(rect.height * image.height), + }, + metrics = new GlyphMetrics + { + width = (int)(rect.width * image.width), + height = (int)(rect.height * image.height), + horizontalBearingY = (int)(rect.height * image.height) * 0.75f, + horizontalBearingX = 0, + horizontalAdvance = (int)(rect.width * image.width), + }, + index = (uint)i, + sprite = pair.Value, + }; + + var character = new TMP_SpriteCharacter(0, asset, glyph) + { + name = pair.Key, + glyphIndex = (uint)i, + }; + + asset.spriteGlyphTable.Add(glyph); + asset.spriteCharacterTable.Add(character); + } + + asset.name = spriteAssetName; + asset.hashCode = TMP_TextUtilities.GetSimpleHashCode(asset.name); + asset.material = new Material(Shader.Find("TextMeshPro/Sprite")); + AccessTools.Property(asset.GetType(), "version").SetValue(asset, "1.1.0"); + asset.material.mainTexture = asset.spriteSheet = image; + asset.UpdateLookupTables(); + RegisteredSpriteAssets.Add(asset); + return asset.DontDestroyOnLoad().DontUnload(); + } + /// /// Returns an empty coroutine. /// From ab2f03ee4dee948b62a6ff12669c0ee9eed120b2 Mon Sep 17 00:00:00 2001 From: XtraCube <72575280+XtraCube@users.noreply.github.com> Date: Wed, 12 Mar 2025 20:13:20 -0400 Subject: [PATCH 2/5] still broken --- MiraAPI/Patches/MainMenuManagerPatch.cs | 8 +--- .../Patches/Options/LobbyViewPanePatches.cs | 2 +- .../Options/NotificationPopperPatch.cs | 4 +- MiraAPI/Roles/CustomRoleManager.cs | 4 +- MiraAPI/Utilities/Assets/SpriteTools.cs | 5 ++- MiraAPI/Utilities/Helpers.cs | 40 +++++++++++++++---- 6 files changed, 43 insertions(+), 20 deletions(-) diff --git a/MiraAPI/Patches/MainMenuManagerPatch.cs b/MiraAPI/Patches/MainMenuManagerPatch.cs index a54efe0..126e1a0 100644 --- a/MiraAPI/Patches/MainMenuManagerPatch.cs +++ b/MiraAPI/Patches/MainMenuManagerPatch.cs @@ -1,9 +1,5 @@ -using System; -using System.Linq; -using HarmonyLib; -using Il2CppInterop.Runtime; +using HarmonyLib; using MiraAPI.Roles; -using MiraAPI.Utilities; using TMPro; namespace MiraAPI.Patches; @@ -16,7 +12,7 @@ public class MainMenuManagerPatch [HarmonyPatch(nameof(MainMenuManager.Start))] public static void StartPatch() { - TMP_Settings.defaultSpriteAsset.fallbackSpriteAssets.Add(CustomRoleManager.SpriteAsset); + TMP_Settings.instance.m_defaultSpriteAsset = CustomRoleManager.SpriteAsset; /*TMP_Text.OnSpriteAssetRequest += DelegateSupport.ConvertDelegate>(new Func((index, str) => { var asset = Helpers.RegisteredSpriteAssets.First(x => x.name.Contains(str)); diff --git a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs index c8e782a..43c5719 100644 --- a/MiraAPI/Patches/Options/LobbyViewPanePatches.cs +++ b/MiraAPI/Patches/Options/LobbyViewPanePatches.cs @@ -331,7 +331,7 @@ public static class LobbyViewPanePatches chancePerGame, 61, customRole.RoleColor, - customRole.Configuration.Icon != null ? customRole.Configuration.Icon.LoadAsset() : MiraAssets.Empty.LoadAsset(), + customRole.Configuration.Icon?.LoadAsset() ?? MiraAssets.Empty.LoadAsset(), true); viewSettingsInfoPanelRoleVariant.iconSprite.transform.localScale = new Vector3(0.365f, 0.365f, 1f); viewSettingsInfoPanelRoleVariant.iconSprite.transform.localPosition = new Vector3(0.7144f, -0.028f, -2); diff --git a/MiraAPI/Patches/Options/NotificationPopperPatch.cs b/MiraAPI/Patches/Options/NotificationPopperPatch.cs index 522bc2b..78a8d38 100644 --- a/MiraAPI/Patches/Options/NotificationPopperPatch.cs +++ b/MiraAPI/Patches/Options/NotificationPopperPatch.cs @@ -23,7 +23,7 @@ public static class NotificationPopperPatch [HarmonyArgument(4)] bool playSound) { var role = CustomRoleManager.CustomRoles.Values.FirstOrDefault(x => x.StringName == key); - if (!role || role is not ICustomRole) + if (!role || role is not ICustomRole customRole) { return true; } @@ -37,7 +37,7 @@ public static class NotificationPopperPatch var item = TranslationController.Instance.GetString( StringNames.LobbyChangeSettingNotificationRole, string.Concat( - $"", + $"", "", text, TranslationController.Instance.GetString(key, Array.Empty()), diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index 1484e26..fa49d65 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -62,7 +62,7 @@ public static class CustomRoleManager pluginInfo.CustomRoles.Add((ushort)role.Role, role); } - SpriteAsset = Helpers.CreateSpriteAsset(_roleIcons, "RoleIcons"); + SpriteAsset = Helpers.CreateSpriteAsset(_roleIcons, "CustomRoleIcons"); } private static RoleBehaviour? RegisterRole(Type roleType, MiraPluginInfo parentMod) @@ -105,7 +105,7 @@ public static class CustomRoleManager { roleBehaviour.RoleIconSolid = asset; roleBehaviour.RoleIconWhite = asset; - _roleIcons.Add(roleBehaviour.NiceName, asset); + _roleIcons.Add(customRole.RoleName, asset); } } diff --git a/MiraAPI/Utilities/Assets/SpriteTools.cs b/MiraAPI/Utilities/Assets/SpriteTools.cs index fa49f12..5d438de 100644 --- a/MiraAPI/Utilities/Assets/SpriteTools.cs +++ b/MiraAPI/Utilities/Assets/SpriteTools.cs @@ -30,6 +30,9 @@ public static class SpriteTools throw new ArgumentException($"Resource not found: {resourcePath}"); } - return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), pixelsPerUnit); + tex.name = resourcePath; + var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), pixelsPerUnit); + sprite.name = resourcePath; + return sprite; } } diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 7e5e812..0c65c90 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Text; using HarmonyLib; using MiraAPI.Roles; +using Reactor.Utilities; using Reactor.Utilities.Extensions; using TMPro; using UnityEngine; @@ -30,6 +31,23 @@ public static class Helpers internal static readonly List RegisteredSpriteAssets = []; + public static void Dump(this Texture2D texture, string path) => File.WriteAllBytes(path, texture.Decompress().EncodeToPNG()); + + public static Texture2D Decompress(this Texture2D source) + { + var renderTex = RenderTexture.GetTemporary(source.width, source.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear); + Graphics.Blit(source, renderTex); + var previous = RenderTexture.active; + RenderTexture.active = renderTex; + var readableText = new Texture2D(source.width, source.height); + readableText.ReadPixels(new(0, 0, renderTex.width, renderTex.height), 0, 0); + readableText.Apply(); + RenderTexture.active = previous; + RenderTexture.ReleaseTemporary(renderTex); + readableText.name = source.name; + return readableText; + } + /// /// Allows you to create a custom sprite asset. Normally used for TextMeshPro. /// Ensure you set the TMP object's sprite asset to this one and use the sprite name tag. @@ -44,11 +62,15 @@ public static class Helpers var asset = ScriptableObject.CreateInstance(); var image = new Texture2D(2048, 2048) { name = spriteAssetName }; var rects = image.PackTextures(textures, 2); + Dump(image, $"{spriteAssetName}.png"); for (var i = 0; i < rects.Length; i++) { var rect = rects[i]; var pair = sprites.ElementAt(i); + var sprite = pair.Value; + var spriteRect = sprite.rect; + var pivot = sprite.pivot; var glyph = new TMP_SpriteGlyph { @@ -61,17 +83,19 @@ public static class Helpers }, metrics = new GlyphMetrics { - width = (int)(rect.width * image.width), - height = (int)(rect.height * image.height), - horizontalBearingY = (int)(rect.height * image.height) * 0.75f, - horizontalBearingX = 0, - horizontalAdvance = (int)(rect.width * image.width), + width = spriteRect.width, + height = spriteRect.height, + horizontalBearingX = pivot.x, + horizontalBearingY = spriteRect.height - pivot.y, // Top of the sprite relative to baseline + horizontalAdvance = spriteRect.width, }, index = (uint)i, - sprite = pair.Value, + sprite = sprite, }; - var character = new TMP_SpriteCharacter(0, asset, glyph) + // Assign unique Unicode from the Private Use Area + var unicode = 0xE000 + (uint)i; + var character = new TMP_SpriteCharacter(unicode, asset, glyph) { name = pair.Key, glyphIndex = (uint)i, @@ -84,7 +108,7 @@ public static class Helpers asset.name = spriteAssetName; asset.hashCode = TMP_TextUtilities.GetSimpleHashCode(asset.name); asset.material = new Material(Shader.Find("TextMeshPro/Sprite")); - AccessTools.Property(asset.GetType(), "version").SetValue(asset, "1.1.0"); + asset.version = "1.1.0"; asset.material.mainTexture = asset.spriteSheet = image; asset.UpdateLookupTables(); RegisteredSpriteAssets.Add(asset); From bc7b835dc8f1277fe8c7ecd46a4a959b4a32521e Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sat, 15 Mar 2025 15:04:57 -0400 Subject: [PATCH 3/5] Admit defeat to custom sprite assets --- MiraAPI/Patches/MainMenuManagerPatch.cs | 22 ----- .../Options/NotificationPopperPatch.cs | 36 +------- MiraAPI/Roles/CustomRoleManager.cs | 5 -- MiraAPI/Utilities/Helpers.cs | 86 ------------------- 4 files changed, 2 insertions(+), 147 deletions(-) delete mode 100644 MiraAPI/Patches/MainMenuManagerPatch.cs diff --git a/MiraAPI/Patches/MainMenuManagerPatch.cs b/MiraAPI/Patches/MainMenuManagerPatch.cs deleted file mode 100644 index 126e1a0..0000000 --- a/MiraAPI/Patches/MainMenuManagerPatch.cs +++ /dev/null @@ -1,22 +0,0 @@ -using HarmonyLib; -using MiraAPI.Roles; -using TMPro; - -namespace MiraAPI.Patches; - -[HarmonyPatch(typeof(MainMenuManager))] - -public class MainMenuManagerPatch -{ - [HarmonyPostfix] - [HarmonyPatch(nameof(MainMenuManager.Start))] - public static void StartPatch() - { - TMP_Settings.instance.m_defaultSpriteAsset = CustomRoleManager.SpriteAsset; - /*TMP_Text.OnSpriteAssetRequest += DelegateSupport.ConvertDelegate>(new Func((index, str) => - { - var asset = Helpers.RegisteredSpriteAssets.First(x => x.name.Contains(str)); - return asset != null ? asset : null; - }));*/ - } -} diff --git a/MiraAPI/Patches/Options/NotificationPopperPatch.cs b/MiraAPI/Patches/Options/NotificationPopperPatch.cs index 78a8d38..02efe2b 100644 --- a/MiraAPI/Patches/Options/NotificationPopperPatch.cs +++ b/MiraAPI/Patches/Options/NotificationPopperPatch.cs @@ -2,10 +2,6 @@ using HarmonyLib; using Il2CppSystem; using MiraAPI.Roles; -using Reactor.Utilities; -using UnityEngine; -using Action = System.Action; -using Object = Il2CppSystem.Object; namespace MiraAPI.Patches.Options; @@ -22,14 +18,11 @@ public static class NotificationPopperPatch [HarmonyArgument(3)] RoleTeamTypes teamType, [HarmonyArgument(4)] bool playSound) { - var role = CustomRoleManager.CustomRoles.Values.FirstOrDefault(x => x.StringName == key); - if (!role || role is not ICustomRole customRole) + if (CustomRoleManager.CustomRoles.Values.FirstOrDefault(x=>x.StringName==key) is not ICustomRole customRole) { return true; } - Logger.Error("UPDATING ROLE TEXT"); - var text = teamType == RoleTeamTypes.Crewmate ? Palette.CrewmateSettingChangeText.ToTextColor() : Palette.ImpostorRed.ToTextColor(); @@ -37,7 +30,6 @@ public static class NotificationPopperPatch var item = TranslationController.Instance.GetString( StringNames.LobbyChangeSettingNotificationRole, string.Concat( - $"", "", text, TranslationController.Instance.GetString(key, Array.Empty()), @@ -47,31 +39,7 @@ public static class NotificationPopperPatch "" + roleChance + "%" ); - CustomMessageLogic(__instance, role, item, playSound); + __instance.SettingsChangeMessageLogic(key, item, playSound); return false; } - private static void CustomMessageLogic(NotificationPopper __instance, RoleBehaviour role, string item, bool playSound) - { - if (__instance.lastMessageKey == (int)role.StringName && __instance.activeMessages.Count > 0) - { - __instance.activeMessages.ToArray()[__instance.activeMessages.Count - 1].UpdateMessage(item); - } - else - { - __instance.lastMessageKey = (int)role.StringName; - var newMessage = UnityEngine.Object.Instantiate(__instance.notificationMessageOrigin, Vector3.zero, Quaternion.identity, __instance.transform); - newMessage.transform.localPosition = new Vector3(0f, 0f, -2f); - newMessage.SetUp(item, __instance.settingsChangeSprite, __instance.settingsChangeColor, new Action(delegate - { - __instance.OnMessageDestroy(newMessage); - })); - __instance.ShiftMessages(); - __instance.AddMessageToQueue(newMessage); - } - if (playSound) - { - SoundManager.Instance.PlaySoundImmediate(__instance.settingsChangeSound, false, 1f, 1f, null); - } - } } - diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index fa49d65..5eba0de 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -25,9 +25,7 @@ public static class CustomRoleManager { internal static readonly Dictionary CustomRoles = []; internal static readonly Dictionary RoleIds = []; - internal static TMP_SpriteAsset SpriteAsset; - private static Dictionary _roleIcons = []; private static ushort _roleId = 100; private static ushort GetNextRoleId() @@ -61,8 +59,6 @@ public static class CustomRoleManager pluginInfo.CustomRoles.Add((ushort)role.Role, role); } - - SpriteAsset = Helpers.CreateSpriteAsset(_roleIcons, "CustomRoleIcons"); } private static RoleBehaviour? RegisterRole(Type roleType, MiraPluginInfo parentMod) @@ -105,7 +101,6 @@ public static class CustomRoleManager { roleBehaviour.RoleIconSolid = asset; roleBehaviour.RoleIconWhite = asset; - _roleIcons.Add(customRole.RoleName, asset); } } diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index 0c65c90..ab12bb8 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -29,92 +29,6 @@ public static class Helpers return PlayerControl.AllPlayerControls.ToArray().Where(x => !x.Data.IsDead).ToList(); } - internal static readonly List RegisteredSpriteAssets = []; - - public static void Dump(this Texture2D texture, string path) => File.WriteAllBytes(path, texture.Decompress().EncodeToPNG()); - - public static Texture2D Decompress(this Texture2D source) - { - var renderTex = RenderTexture.GetTemporary(source.width, source.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear); - Graphics.Blit(source, renderTex); - var previous = RenderTexture.active; - RenderTexture.active = renderTex; - var readableText = new Texture2D(source.width, source.height); - readableText.ReadPixels(new(0, 0, renderTex.width, renderTex.height), 0, 0); - readableText.Apply(); - RenderTexture.active = previous; - RenderTexture.ReleaseTemporary(renderTex); - readableText.name = source.name; - return readableText; - } - - /// - /// Allows you to create a custom sprite asset. Normally used for TextMeshPro. - /// Ensure you set the TMP object's sprite asset to this one and use the sprite name tag. - /// https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/RichTextSprite.html - /// - /// The dictionary of sprites you want to add to the asset. String is the name of the sprite you'd like to use for rich text. - /// The name of the sprite asset. - /// The created sprite asset. - public static TMP_SpriteAsset CreateSpriteAsset(Dictionary sprites, string spriteAssetName) - { - var textures = sprites.Values.Select(x => x.texture).ToArray(); - var asset = ScriptableObject.CreateInstance(); - var image = new Texture2D(2048, 2048) { name = spriteAssetName }; - var rects = image.PackTextures(textures, 2); - Dump(image, $"{spriteAssetName}.png"); - - for (var i = 0; i < rects.Length; i++) - { - var rect = rects[i]; - var pair = sprites.ElementAt(i); - var sprite = pair.Value; - var spriteRect = sprite.rect; - var pivot = sprite.pivot; - - var glyph = new TMP_SpriteGlyph - { - glyphRect = new GlyphRect - { - x = (int)(rect.x * image.width), - y = (int)(rect.y * image.height), - width = (int)(rect.width * image.width), - height = (int)(rect.height * image.height), - }, - metrics = new GlyphMetrics - { - width = spriteRect.width, - height = spriteRect.height, - horizontalBearingX = pivot.x, - horizontalBearingY = spriteRect.height - pivot.y, // Top of the sprite relative to baseline - horizontalAdvance = spriteRect.width, - }, - index = (uint)i, - sprite = sprite, - }; - - // Assign unique Unicode from the Private Use Area - var unicode = 0xE000 + (uint)i; - var character = new TMP_SpriteCharacter(unicode, asset, glyph) - { - name = pair.Key, - glyphIndex = (uint)i, - }; - - asset.spriteGlyphTable.Add(glyph); - asset.spriteCharacterTable.Add(character); - } - - asset.name = spriteAssetName; - asset.hashCode = TMP_TextUtilities.GetSimpleHashCode(asset.name); - asset.material = new Material(Shader.Find("TextMeshPro/Sprite")); - asset.version = "1.1.0"; - asset.material.mainTexture = asset.spriteSheet = image; - asset.UpdateLookupTables(); - RegisteredSpriteAssets.Add(asset); - return asset.DontDestroyOnLoad().DontUnload(); - } - /// /// Returns an empty coroutine. /// From ce9864ae8b3ef189ebba07b3616e8a839e5082b0 Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sat, 15 Mar 2025 17:21:26 -0400 Subject: [PATCH 4/5] Lots of new Util methods, and fix opt header colors --- MiraAPI/GameOptions/AbstractOptionGroup.cs | 2 +- MiraAPI/Hud/CustomActionButton.cs | 5 +- MiraAPI/MiraApiPlugin.cs | 1 + MiraAPI/Modifiers/ModifierExtensions.cs | 24 +++ MiraAPI/Modifiers/ModifierUtils.cs | 33 ++++ .../Patches/Options/GameOptionsMenuPatch.cs | 25 ++- .../Patches/Options/RoleSettingMenuPatches.cs | 38 ++--- MiraAPI/Patches/Stubs/MinigameStubs.cs | 11 ++ MiraAPI/Resources/CategoryHeader.png | Bin 0 -> 1221 bytes MiraAPI/Roles/CustomRoleConfiguration.cs | 7 + MiraAPI/Roles/CustomRoleManager.cs | 5 + MiraAPI/Roles/CustomRoleUtils.cs | 49 ++++++ MiraAPI/Roles/ICustomRole.cs | 2 +- MiraAPI/Utilities/Assets/MiraAssets.cs | 1 + MiraAPI/Utilities/Assets/SpriteTools.cs | 17 +++ MiraAPI/Utilities/Extensions.cs | 36 +++++ MiraAPI/Utilities/Helpers.cs | 144 ++++++++++++++++-- 17 files changed, 355 insertions(+), 45 deletions(-) create mode 100644 MiraAPI/Modifiers/ModifierUtils.cs create mode 100644 MiraAPI/Resources/CategoryHeader.png create mode 100644 MiraAPI/Roles/CustomRoleUtils.cs diff --git a/MiraAPI/GameOptions/AbstractOptionGroup.cs b/MiraAPI/GameOptions/AbstractOptionGroup.cs index 45f9bd4..db2b93c 100644 --- a/MiraAPI/GameOptions/AbstractOptionGroup.cs +++ b/MiraAPI/GameOptions/AbstractOptionGroup.cs @@ -35,7 +35,7 @@ public abstract class AbstractOptionGroup /// /// Gets the group color. This is used to color the group in the options menu. /// - public virtual Color GroupColor => Color.clear; + public virtual Color GroupColor => MiraApiPlugin.DefaultHeaderColor; /// /// Gets the group priority. This is used to determine the order in which groups are displayed in the options menu. diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs index 8e5815c..6a721da 100644 --- a/MiraAPI/Hud/CustomActionButton.cs +++ b/MiraAPI/Hud/CustomActionButton.cs @@ -168,7 +168,7 @@ public abstract class CustomActionButton /// Allows you to change the button's location. /// /// The new location. - /// Whether the button's position should change ingame. + /// Whether the button's position should change in-game. public virtual void SetButtonLocation(ButtonLocation location, bool moveButton = true) { if (!HudManager.InstanceExists || Button == null) @@ -221,7 +221,6 @@ public abstract class CustomActionButton /// A utility function to change the outline color of the button's text. /// /// The new color. - /// The thickness of the outline. Set to 0 if you want to remove it. public virtual void SetTextOutline(Color color) { Button?.buttonLabelText.SetOutlineColor(color); @@ -364,7 +363,7 @@ public abstract class CustomActionButton /// A value that represents whether the button should light up or not. public virtual bool CanUse() { - return !LimitedUses || UsesLeft > 0; + return PlayerControl.LocalPlayer.moveable && (!LimitedUses || UsesLeft > 0); } /// diff --git a/MiraAPI/MiraApiPlugin.cs b/MiraAPI/MiraApiPlugin.cs index fa601ce..46f0a16 100644 --- a/MiraAPI/MiraApiPlugin.cs +++ b/MiraAPI/MiraApiPlugin.cs @@ -27,6 +27,7 @@ namespace MiraAPI; public partial class MiraApiPlugin : BasePlugin { public static Color MiraColor { get; } = new Color32(238, 154, 112, 255); + public static Color DefaultHeaderColor = new Color32(77, 77, 77, 255); private static MiraPluginManager? PluginManager { get; set; } internal Harmony Harmony { get; } = new(Id); diff --git a/MiraAPI/Modifiers/ModifierExtensions.cs b/MiraAPI/Modifiers/ModifierExtensions.cs index 06d9f06..ca49808 100644 --- a/MiraAPI/Modifiers/ModifierExtensions.cs +++ b/MiraAPI/Modifiers/ModifierExtensions.cs @@ -180,6 +180,30 @@ public static class ModifierExtensions return player.GetModifierComponent().HasModifier(typeId, predicate); } + /// + /// Clears all modifiers from a player. + /// + /// The player you want to clear modifiers for. + public static void ClearModifiers(this PlayerControl plr) + { + foreach (var mod in plr.GetModifierComponent().ActiveModifiers) + { + plr.RemoveModifier(mod); + } + } + + /// + /// Clears all modifiers from a player. + /// + /// The player you want to clear modifiers for. + public static void RpcClearModifiers(this PlayerControl plr) + { + foreach (var mod in plr.GetModifierComponent().ActiveModifiers) + { + plr.RpcRemoveModifier(mod.UniqueId); + } + } + /// /// Checks if the player has a specific modifier by its GUID. /// diff --git a/MiraAPI/Modifiers/ModifierUtils.cs b/MiraAPI/Modifiers/ModifierUtils.cs new file mode 100644 index 0000000..b69e7bc --- /dev/null +++ b/MiraAPI/Modifiers/ModifierUtils.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MiraAPI.Modifiers; + +/// +/// Utilities to make handling modifiers in-game easier. +/// +public static class ModifierUtils +{ + /// + /// Gets a list of all active in-game modifiers. + /// + /// Select if modifier is valid to be added to list. + /// The modifier type. + /// A list of modifiers. + public static IEnumerable GetActiveModifiers(Func? predicate = null) where T : BaseModifier + { + return PlayerControl.AllPlayerControls.ToArray().SelectMany(x => x.GetModifiers(predicate)).OfType(); + } + + /// + /// Gets all players with a certain modifier. + /// + /// Select if modifier is valid. + /// The modifier type. + /// A list of players with that modifier. + public static IEnumerable GetPlayersWithModifier(Func? predicate = null) where T : BaseModifier + { + return PlayerControl.AllPlayerControls.ToArray().Where(x => x.HasModifier(predicate)); + } +} diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs index b792767..05de7f6 100644 --- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs +++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs @@ -42,7 +42,7 @@ public static class GameOptionsMenuPatch { var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups - .Where(x => x.OptionableType == null) ?? []; + .Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? []; foreach (var group in filteredGroups) { @@ -144,7 +144,7 @@ public static class GameOptionsMenuPatch } var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups - .Where(x => x.OptionableType == null) ?? []; + .Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? []; foreach (var group in filteredGroups) { @@ -198,16 +198,16 @@ public static class GameOptionsMenuPatch menu.settingsContainer); categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20); - if (group.GroupColor != Color.clear) - { - categoryHeaderMasked.Background.color = group.GroupColor; - categoryHeaderMasked.Divider.color = group.GroupColor; - categoryHeaderMasked.Title.color = group.GroupColor.FindAlternateColor(); - } + categoryHeaderMasked.Background.color = group.GroupColor; + categoryHeaderMasked.Divider.color = group.GroupColor; + categoryHeaderMasked.Title.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) ? Color.white : group.GroupColor.FindAlternateColor(); + categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset(); + categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.55f, -0.1833f, 0); categoryHeaderMasked.Background.size = new Vector2( categoryHeaderMasked.Background.size.x + 1.5f, categoryHeaderMasked.Background.size.y); + categoryHeaderMasked.gameObject.SetActive(false); group.Header = categoryHeaderMasked; @@ -230,7 +230,7 @@ public static class GameOptionsMenuPatch SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren(true); foreach (var renderer in componentsInChildren) { - if (group.GroupColor != Color.clear) + if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) { renderer.color = group.GroupColor.FindAlternateColor(); if (renderer.transform.parent.TryGetComponent(out var btn)) @@ -245,7 +245,7 @@ public static class GameOptionsMenuPatch foreach (var textMeshPro in newOpt.GetComponentsInChildren(true)) { - if (group.GroupColor != Color.clear) + if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor) { textMeshPro.color = group.GroupColor; } @@ -257,12 +257,11 @@ 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; + toggle.CheckMark.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor ? 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; + rend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor ? group.GroupColor : MiraAssets.AcceptedTeal; } menu.Children.Add(newOpt); diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 011857d..e4984fd 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -127,32 +127,34 @@ public static class RoleSettingMenuPatches chanceLabel.Destroy(); countLabel.Destroy(); - if (name is StringNames.CrewmateRolesHeader) - { - categoryHeaderMasked.Title.color = Palette.CrewmateRoleHeaderTextBlue; - categoryHeaderMasked.Background.color = Palette.CrewmateRoleHeaderBlue; - } - else if (name is StringNames.ImpostorRolesHeader) - { - categoryHeaderMasked.Title.color = Palette.ImpostorRoleHeaderTextRed; - categoryHeaderMasked.Background.color = Palette.ImpostorRoleHeaderRed; - } - else - { - categoryHeaderMasked.Title.color = group.Color; - categoryHeaderMasked.Divider.color = group.Color; - categoryHeaderMasked.Background.color = group.Color; - } + categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset(); + categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.55f, -0.1833f, 0); - categoryHeaderMasked.Title.color = categoryHeaderMasked.Title.color.IsColorDark() ? - categoryHeaderMasked.Title.color.LightenColor(0.3f) : categoryHeaderMasked.Title.color.LightenColor(0.1f); + switch (name) + { + case StringNames.CrewmateRolesHeader: + categoryHeaderMasked.Title.color = Palette.CrewmateRoleHeaderTextBlue; + categoryHeaderMasked.Background.color = Palette.CrewmateRoleHeaderBlue; + break; + case StringNames.ImpostorRolesHeader: + categoryHeaderMasked.Title.color = Palette.ImpostorRoleHeaderTextRed; + categoryHeaderMasked.Background.color = Palette.ImpostorRoleHeaderRed; + break; + default: + categoryHeaderMasked.Title.color = group.Color.Equals(MiraApiPlugin.DefaultHeaderColor) ? Color.white : group.Color.FindAlternateColor(); + categoryHeaderMasked.Divider.color = group.Color; + categoryHeaderMasked.Background.color = group.Color; + break; + } categoryHeaderMasked.Title.fontStyle = __instance.categoryHeaderEditRoleOrigin.Title.fontStyle; categoryHeaderMasked.Title.font = __instance.categoryHeaderEditRoleOrigin.Title.font; categoryHeaderMasked.Title.fontMaterial = __instance.categoryHeaderEditRoleOrigin.Title.fontMaterial; categoryHeaderMasked.Divider.color = categoryHeaderMasked.Background.color; + categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.55f, -0.1833f, 0); categoryHeaderMasked.Background.size = new Vector2(categoryHeaderMasked.Background.size.x + 1.5f, categoryHeaderMasked.Background.size.y); + categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f; categoryHeaderMasked.transform.localPosition = new Vector3(-0.44f, ScrollerNum, -2f); categoryHeaderMasked.gameObject.SetActive(true); diff --git a/MiraAPI/Patches/Stubs/MinigameStubs.cs b/MiraAPI/Patches/Stubs/MinigameStubs.cs index 3818d09..11e085e 100644 --- a/MiraAPI/Patches/Stubs/MinigameStubs.cs +++ b/MiraAPI/Patches/Stubs/MinigameStubs.cs @@ -33,4 +33,15 @@ public static class MinigameStubs { // nothing needed } + + /// + /// Fixes the error when creating custom minigames. Credit to Submerged for discovering this fix. + /// + /// The Minigame instance. + [HarmonyPrefix] + [HarmonyPatch(typeof(Minigame), nameof(Minigame.Begin))] + public static void MinigameErrorFixPatch(Minigame __instance) + { + __instance.logger ??= new Logger("Minigame", Logger.Level.Info, Logger.Category.Gameplay); + } } diff --git a/MiraAPI/Resources/CategoryHeader.png b/MiraAPI/Resources/CategoryHeader.png new file mode 100644 index 0000000000000000000000000000000000000000..3d2a4bfaf12c74008a375e85c21720685b4bfaae GIT binary patch literal 1221 zcmeAS@N?(olHy`uVBq!ia0y~yV2S{;BRSZBylDJ>JydH zPiC^)b32sCcJq;cKPCscIv zkG9**|6^{uJDoT``Do=|);g=_d#|rOm|(!cz_2OOlbwM<$1Q=8Aw_Ki1A|fa&4Ww~ zCl=p+J1;ISZk{EGv*h~gzkBxY|G)77kn`l)+Gu^HM=>A`mtL0mDLtAA683=`APn@o ze2~(F^78MVUn5a8aBH3Zbo~|QApxM~?&#Rq z+8A3$2_|lagu>OUSO5L>AH-z*Tem-YzZ?(Hpbay+ySv}NzTOP7L2UEx-M>xda_TTN zoM91Ta7Y6w_DhYFTgv?oB>!yt)~#O+Vhi};E&w_9OyZq?p&URxKYMQP=YmVEF_qw9 zdnv%s@NwO`b^kK9-+nLdT3Az2F{AD+3((lG>C>m%_d+xn?B2R{>*bO#b)X$-TlVh# zYcuyMsw3DY-+K@z2=tQij4y28yHLVODr-}{DA1(~cB=gj2L}`=;AVmhV4L+Wz7?oA zW7$iH6aT=y)wM}yzUf`Cdktb&U;VWUs^X=Yxq1AO&7N?DO_BHNLqS#x@2UIyt3C4D zdMBW#eXapx?9xpWpuGylKtCHCvw>KCZ1&Ud?62XvTVB6@J^gbGe;d#h5^L|jx1aaC z$^a;PAmj6%c?*?*9v1p+XBz=Bzq7ZuSO585aI}2bwn?WyEG%rF*YnL8yll;mRxAt= zN%KWqKPt#BTzep}tIr1a?W_idNg=K&)+;iXU4Y6;F3K*paE=lWcsuvbyfBbFjvbPbw z${QFOmImzS%Ml6_1*JL(JJ}ygZf*7|U*qN)Iu{t;9UY*+|6BLJaQS7+mHHcw7aY%u z3IV08gv_OZq5IilN|tAZ0b}sT{mQSeuFikHfBye%-_G6Ax3;!E-}bn9Z_@jUTQ8@8 zV(#IaH*db2`#1Nu;K#eb&~VdR)Bic1{o4KK@f+?g4nBJkSoAP>y85}Sb4q9e0AN<~ AcK`qY literal 0 HcmV?d00001 diff --git a/MiraAPI/Roles/CustomRoleConfiguration.cs b/MiraAPI/Roles/CustomRoleConfiguration.cs index 85094a9..cf714fd 100644 --- a/MiraAPI/Roles/CustomRoleConfiguration.cs +++ b/MiraAPI/Roles/CustomRoleConfiguration.cs @@ -1,6 +1,8 @@ using AmongUs.GameOptions; +using Il2CppSystem.Web.Util; using MiraAPI.Utilities.Assets; using UnityEngine; +using Helpers = MiraAPI.Utilities.Helpers; namespace MiraAPI.Roles; @@ -64,6 +66,11 @@ public record struct CustomRoleConfiguration /// public LoadableAsset? Icon { get; set; } = null; + /// + /// Gets or sets the Intro sound for the Role. + /// + public LoadableAsset? IntroSound { get; set; } = null; + /// /// Gets or sets a value indicating whether the role is affected by light affectors on Airship. /// diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index 5eba0de..0c2c4ad 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -104,6 +104,11 @@ public static class CustomRoleManager } } + if (customRole.Configuration.IntroSound != null) + { + roleBehaviour.IntroSound = customRole.Configuration.IntroSound.LoadAsset(); + } + if (roleBehaviour.IsDead) { RoleManager.GhostRoles.Add(roleBehaviour.Role); diff --git a/MiraAPI/Roles/CustomRoleUtils.cs b/MiraAPI/Roles/CustomRoleUtils.cs new file mode 100644 index 0000000..8f2432d --- /dev/null +++ b/MiraAPI/Roles/CustomRoleUtils.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using AmongUs.GameOptions; +using UnityEngine; + +namespace MiraAPI.Roles; + +/// +/// Utilities to make handling roles in-game easier. +/// +public static class CustomRoleUtils +{ + /// + /// Gets all active in-game roles. + /// + /// A list of roles. + // ReSharper disable once MemberCanBePrivate.Global + public static IEnumerable GetActiveRoles() => PlayerControl.AllPlayerControls.ToArray().Select(x => x.Data.Role); + + /// + /// Gets all active in-game roles in a certain team. + /// + /// The team you would like to check for. + /// A list of roles with the team. + public static IEnumerable GetActiveRolesOfTeam(ModdedRoleTeams team) => GetActiveRoles().Where(x => x is ICustomRole customRole && customRole.Team == team); + + /// + /// Gets all active in-game roles of a certain type. + /// + /// The role Type you would like to check for. Must be a RoleBehaviour. + /// A list of roles with that specific type. + public static IEnumerable GetActiveRolesOfType() where T : RoleBehaviour => GetActiveRoles().OfType(); + + /// + /// Creates a string builder for the Role Tab. + /// + /// The ICustomRole object. + /// A StringBuilder. + public static StringBuilder CreateForRole(ICustomRole role) + { + var taskStringBuilder = new StringBuilder(); + taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}You are a {role.RoleName}."); + taskStringBuilder.Append(""); + taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription}"); + return taskStringBuilder; + } +} diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs index a1c0e5d..1784702 100644 --- a/MiraAPI/Roles/ICustomRole.cs +++ b/MiraAPI/Roles/ICustomRole.cs @@ -206,7 +206,7 @@ public interface ICustomRole : IOptionable /// Get the custom Role Tab text for this role. /// /// A StringBuilder with the role tab text. - StringBuilder SetTabText() => Helpers.CreateForRole(this); + StringBuilder SetTabText() => CustomRoleUtils.CreateForRole(this); /// /// Determine whether a given modifier can be applied to this role. diff --git a/MiraAPI/Utilities/Assets/MiraAssets.cs b/MiraAPI/Utilities/Assets/MiraAssets.cs index 514bd25..d2e855f 100644 --- a/MiraAPI/Utilities/Assets/MiraAssets.cs +++ b/MiraAPI/Utilities/Assets/MiraAssets.cs @@ -10,6 +10,7 @@ public static class MiraAssets public static readonly LoadableResourceAsset NextButtonActive = new("MiraAPI.Resources.NextButtonActive.png"); public static readonly LoadableResourceAsset Cog = new("MiraAPI.Resources.Cog.png"); public static readonly LoadableResourceAsset Checkmark = new("MiraAPI.Resources.Checkmark.png"); + public static readonly LoadableResourceAsset CategoryHeader = new("MiraAPI.Resources.CategoryHeader.png"); public static readonly LoadableResourceAsset CheckmarkBox = new("MiraAPI.Resources.CheckMarkBox.png"); 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 5d438de..7cb9d63 100644 --- a/MiraAPI/Utilities/Assets/SpriteTools.cs +++ b/MiraAPI/Utilities/Assets/SpriteTools.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Reflection; using Reactor.Utilities.Extensions; using UnityEngine; @@ -35,4 +36,20 @@ public static class SpriteTools sprite.name = resourcePath; return sprite; } + public static void Dump(this Sprite sprite, string path) => File.WriteAllBytes(path, sprite.texture.Decompress().EncodeToPNG()); + + public static Texture2D Decompress(this Texture2D source) + { + var renderTex = RenderTexture.GetTemporary(source.width, source.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear); + Graphics.Blit(source, renderTex); + var previous = RenderTexture.active; + RenderTexture.active = renderTex; + var readableText = new Texture2D(source.width, source.height); + readableText.ReadPixels(new(0, 0, renderTex.width, renderTex.height), 0, 0); + readableText.Apply(); + RenderTexture.active = previous; + RenderTexture.ReleaseTemporary(renderTex); + readableText.name = source.name; + return readableText; + } } diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 47a05f7..009be7f 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using HarmonyLib; using MiraAPI.GameOptions; using MiraAPI.Networking; @@ -39,6 +40,30 @@ public static class Extensions BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value))); } + /// + /// Gets a PlayerControl from their PlayerVoteArea in a meeting. + /// + /// The vote area. + /// The player's PlayerControl. + public static PlayerControl? GetPlayer(this PlayerVoteArea state) => GameData.Instance.GetPlayerById(state.TargetPlayerId)?.Object; + + /// + /// Gets an int representing the amount of tasks a player has left. + /// + /// The player. + /// A count of how many tasks the player has left. + public static int GetTasksLeft(this PlayerControl player) => player.Data.Tasks.ToArray().Count(x => !x.Complete); + + /// + /// Checks if a PlayerControl is the game's host. + /// + /// The player you're checking for. + /// If the player is the host, true, else false. + public static bool IsHost(this PlayerControl playerControl) + { + return TutorialManager.InstanceExists || AmongUsClient.Instance.HostId == playerControl.OwnerId; + } + /// /// Determines if a float is an integer. /// @@ -65,6 +90,17 @@ public static class Extensions .FirstOrDefault(); } + /// + /// Gets a proper string for an enum. (with spaces). + /// + /// The enum you would like to change. + /// A proper string for the enum. + public static string ToDisplayString(this Enum @enum) + { + var regex = new Regex(@"([^\^])([A-Z][a-z$])"); + return regex.Replace(@enum.ToString(), m => $"{m.Groups[1].Value} {m.Groups[2].Value}"); + } + /// /// Gets the constructors of a type that match the specified arguments. /// diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs index ab12bb8..a54b6cf 100644 --- a/MiraAPI/Utilities/Helpers.cs +++ b/MiraAPI/Utilities/Helpers.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text; +using AmongUs.GameOptions; using HarmonyLib; using MiraAPI.Roles; using Reactor.Utilities; @@ -29,6 +30,123 @@ public static class Helpers return PlayerControl.AllPlayerControls.ToArray().Where(x => !x.Data.IsDead).ToList(); } + /// + /// Determines whether a given probability check succeeds. + /// + /// An integer value representing the success probability (0-100). + /// True if the number falls in the range, false if not. + public static bool CheckChance(int probability) + { + switch (probability) + { + case 0: + return false; + case 100: + return true; + default: + { + var num = Random.RandomRangeInt(1, 101); + return num <= probability; + } + } + } + + /// + /// Retrieves a vent from the ID. + /// + /// The vent ID. + /// The vent. + public static Vent? GetVentById(int id) + { + return ShipStatus.Instance.AllVents.FirstOrDefault(vent => vent.Id == id); + } + + /// + /// Creates an arrow. + /// + /// The arrow gameObject's parent. + /// The color of the arrow. + /// The created ArrowBehaviour. + public static ArrowBehaviour CreateArrow(Transform parent, Color color) + { + var prefab = Object.FindObjectOfType(true); + var arrow = Object.Instantiate(prefab, parent); + arrow.image = arrow.gameObject.GetComponent(); + arrow.image.color = color; + arrow.gameObject.layer = 5; + arrow.gameObject.SetActive(true); + return arrow; + } + + /// + /// Get the closest object of a specific type. + /// + /// A list of all the objects you'd like to check the distance for. + /// The position of where you want to check from. For example: PlayerControl.LocalPlayer.transform.position. + /// The object type. + /// The closest object. + public static T? FindClosestObjectOfType(List objectList, Vector3 position) where T : MonoBehaviour + { + T? closest = null; + var closestDistanceSqr = Mathf.Infinity; + + foreach (var obj in objectList) + { + if (obj == null) + { + continue; + } + + var sqrDistance = (obj.transform.position - position).sqrMagnitude; + if (sqrDistance < closestDistanceSqr) + { + closestDistanceSqr = sqrDistance; + closest = obj; + } + } + + return closest; + } + + /// + /// Creates and shows a notification. + /// + /// The text you want to display. + /// The color of the text and image. + /// The sound you want to play with the notification. + /// The sprite beside the notification. + /// The created notification. + public static LobbyNotificationMessage CreateAndShowNotification(string text, Color color, AudioClip? clip = null, Sprite? spr = null) + { + return CreateAndShowNotification(text, color, new Vector3(0f, 0f, -2f), clip, spr); + } + + /// + /// Creates and shows a notification. + /// + /// The text you want to display. + /// The color of the text and image. + /// The position of the notification. + /// The sound you want to play with the notification. + /// The sprite beside the notification. + /// The created notification. + public static LobbyNotificationMessage CreateAndShowNotification(string text, Color color, Vector3 localPos, AudioClip? clip = null, Sprite? spr = null) + { + var popper = HudManager.Instance.Notifier; + var newMessage = Object.Instantiate(popper.notificationMessageOrigin, Vector3.zero, Quaternion.identity, popper.transform); + newMessage.transform.localPosition = localPos; + newMessage.SetUp(text, spr ?? null, color, new System.Action(() => popper.OnMessageDestroy(newMessage))); + popper.lastMessageKey = -1; + popper.ShiftMessages(); + popper.AddMessageToQueue(newMessage); + + if (clip == null) return newMessage; + SoundManager.Instance.StopSound(clip); + SoundManager.Instance.PlaySound(clip, false, 2f); + + return newMessage; + } + /// /// Returns an empty coroutine. /// @@ -270,16 +388,24 @@ public static class Helpers } /// - /// Creates a string builder for the Role Tab. + /// Converts the first letter of a string to uppercase. /// - /// The ICustomRole object. - /// A StringBuilder. - public static StringBuilder CreateForRole(ICustomRole role) + /// The string. + /// The fixed string. + public static string FirstLetterToUpper(string str) { - var taskStringBuilder = new StringBuilder(); - taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}You are a {role.RoleName}."); - taskStringBuilder.Append(""); - taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription}"); - return taskStringBuilder; + return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str.ToLower(CultureInfo.InvariantCulture)); + } + + /// + /// Gets a random string based on characters. + /// + /// The length of the string. + /// The characters in the random string. + /// The random string. + public static string RandomString(int length, string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + { + return new string(Enumerable.Repeat(chars, length) + .Select(s => s[Random.RandomRangeInt(0, s.Length)]).ToArray()); } } From 19ba9da2c362cdcc9faa4705241ea277ef3c07da Mon Sep 17 00:00:00 2001 From: ang-xd Date: Sat, 15 Mar 2025 17:22:01 -0400 Subject: [PATCH 5/5] Remove debug method --- MiraAPI/Utilities/Assets/SpriteTools.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/MiraAPI/Utilities/Assets/SpriteTools.cs b/MiraAPI/Utilities/Assets/SpriteTools.cs index 7cb9d63..6858dcc 100644 --- a/MiraAPI/Utilities/Assets/SpriteTools.cs +++ b/MiraAPI/Utilities/Assets/SpriteTools.cs @@ -36,20 +36,4 @@ public static class SpriteTools sprite.name = resourcePath; return sprite; } - public static void Dump(this Sprite sprite, string path) => File.WriteAllBytes(path, sprite.texture.Decompress().EncodeToPNG()); - - public static Texture2D Decompress(this Texture2D source) - { - var renderTex = RenderTexture.GetTemporary(source.width, source.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear); - Graphics.Blit(source, renderTex); - var previous = RenderTexture.active; - RenderTexture.active = renderTex; - var readableText = new Texture2D(source.width, source.height); - readableText.ReadPixels(new(0, 0, renderTex.width, renderTex.height), 0, 0); - readableText.Apply(); - RenderTexture.active = previous; - RenderTexture.ReleaseTemporary(renderTex); - readableText.name = source.name; - return readableText; - } }