From 3bf066f38c0002d3b052b8f23d5e3eecda12629f Mon Sep 17 00:00:00 2001 From: Az <56699741+AlchlcDvl@users.noreply.github.com> Date: Tue, 29 Sep 2026 09:53:18 -0400 Subject: [PATCH 1/7] add variable color system + color spaces --- .editorconfig | 3 + MiraAPI/Colors/ColorSpaces/CmykColor.cs | 103 +++++++++++++++++ MiraAPI/Colors/ColorSpaces/HsbColor.cs | 114 +++++++++++++++++++ MiraAPI/Colors/ColorSpaces/HslColor.cs | 117 ++++++++++++++++++++ MiraAPI/Colors/CustomColor.cs | 2 +- MiraAPI/Colors/PaletteManager.cs | 56 ++++++++++ MiraAPI/Colors/VariableColor.cs | 69 ++++++++++++ MiraAPI/Colors/VariableColorBehaviour.cs | 46 ++++++++ MiraAPI/MiraApiSettings.cs | 6 + MiraAPI/Patches/Hud/VariableColorPatches.cs | 98 ++++++++++++++++ MiraAPI/PluginLoading/MiraPluginManager.cs | 5 +- MiraAPI/Resources/Locale/en_US.xml | 1 + 12 files changed, 618 insertions(+), 2 deletions(-) create mode 100644 MiraAPI/Colors/ColorSpaces/CmykColor.cs create mode 100644 MiraAPI/Colors/ColorSpaces/HsbColor.cs create mode 100644 MiraAPI/Colors/ColorSpaces/HslColor.cs create mode 100644 MiraAPI/Colors/VariableColor.cs create mode 100644 MiraAPI/Colors/VariableColorBehaviour.cs create mode 100644 MiraAPI/Patches/Hud/VariableColorPatches.cs diff --git a/.editorconfig b/.editorconfig index af9feb49..c0529a81 100644 --- a/.editorconfig +++ b/.editorconfig @@ -86,3 +86,6 @@ dotnet_diagnostic.SA1133.severity = none # IDE0001: Simplify Names dotnet_diagnostic.IDE0001.severity = warning + +# IDE0079: Remove unnecessary suppression +dotnet_diagnostic.IDE0079.severity = none diff --git a/MiraAPI/Colors/ColorSpaces/CmykColor.cs b/MiraAPI/Colors/ColorSpaces/CmykColor.cs new file mode 100644 index 00000000..569e848a --- /dev/null +++ b/MiraAPI/Colors/ColorSpaces/CmykColor.cs @@ -0,0 +1,103 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using UnityEngine; + +namespace MiraAPI.Colors.ColorSpaces; + +/// +/// A struct that denotes the CMYK (Cyan, Magenta, Yellow, Key) color space. +/// +/// The cyan component. +/// The magenta component. +/// The yellow component. +/// The key component. +/// The alpha component. +[Serializable] +[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Unity parity.")] +public record struct CmykColor(float c, float m, float y, float k, float a = 1f) +{ + /// + /// Implicitly converts a to a Unity . + /// + /// The color being converted. + public static implicit operator Color(CmykColor c) + { + return ToColor(c); + } + + /// + /// Implicitly converts a Unity to a . + /// + /// The color being converted. + public static implicit operator CmykColor(Color c) + { + return FromColor(c); + } + + /// + /// Converts a to a standard Unity RGB . + /// + /// The CMYK color to convert. + /// A Unity representing the same visual color. + public static Color ToColor(CmykColor cmyk) + { + var r = (1f - cmyk.c) * (1f - cmyk.k); + var g = (1f - cmyk.m) * (1f - cmyk.k); + var b = (1f - cmyk.y) * (1f - cmyk.k); + + return new Color(Mathf.Clamp01(r), Mathf.Clamp01(g), Mathf.Clamp01(b), cmyk.a); + } + + /// + /// Converts a standard Unity RGB to a . + /// + /// The Unity Color to convert. + /// A representing the same visual color. + public static CmykColor FromColor(Color color) + { + var r = color.r; + var g = color.g; + var b = color.b; + + var k = 1f - Mathf.Max(r, g, b); + + if (k >= 1f) + return new CmykColor(0f, 0f, 0f, 1f, color.a); + + var c = (1f - r - k) / (1f - k); + var m = (1f - g - k) / (1f - k); + var y = (1f - b - k) / (1f - k); + + return new CmykColor(Mathf.Clamp01(c), Mathf.Clamp01(m), Mathf.Clamp01(y), Mathf.Clamp01(k), color.a); + } + + /// + /// Linearly interpolates between two values. + /// + /// The starting color. + /// The ending color. + /// The interpolation value between the two colors, clamped to a 0 to 1 range. + /// The interpolated . + public static CmykColor Lerp(CmykColor a, CmykColor b, float t) + { + t = Mathf.Clamp01(t); + return LerpUnclamped(a, b, t); + } + + /// + /// Linearly interpolates between two values without clamping the interpolant. + /// + /// The starting color. + /// The ending color. + /// The interpolation value between the two colors. + /// The interpolated . + public static CmykColor LerpUnclamped(CmykColor a, CmykColor b, float t) + { + return new CmykColor( + Mathf.LerpUnclamped(a.c, b.c, t), + Mathf.LerpUnclamped(a.m, b.m, t), + Mathf.LerpUnclamped(a.y, b.y, t), + Mathf.LerpUnclamped(a.k, b.k, t), + Mathf.LerpUnclamped(a.a, b.a, t)); + } +} diff --git a/MiraAPI/Colors/ColorSpaces/HsbColor.cs b/MiraAPI/Colors/ColorSpaces/HsbColor.cs new file mode 100644 index 00000000..4dec19a3 --- /dev/null +++ b/MiraAPI/Colors/ColorSpaces/HsbColor.cs @@ -0,0 +1,114 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using UnityEngine; + +namespace MiraAPI.Colors.ColorSpaces; + +/// +/// A struct that denotes the HSB (Hue, Saturation, Brightness) color space. +/// +/// The hue component. +/// The saturation component. +/// The brightness component. +/// The alpha component. +[Serializable] +[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Unity parity.")] +public record struct HsbColor(float h, float s, float b, float a = 1f) +{ + /// + /// Implicitly converts an to a Unity . + /// + /// The color being converted. + public static implicit operator Color(HsbColor c) + { + return ToColor(c); + } + + /// + /// Implicitly converts a Unity to an . + /// + /// The color being converted. + public static implicit operator HsbColor(Color c) + { + return FromColor(c); + } + + /// + /// Converts an to a standard Unity RGB . + /// + /// The HSB color to convert. + /// A Unity representing the same visual color. + public static Color ToColor(HsbColor hsb) + { + var r = hsb.b; + var g = hsb.b; + var b = hsb.b; + + if (hsb.s <= Mathf.Epsilon) + return new Color(Mathf.Clamp01(r), Mathf.Clamp01(g), Mathf.Clamp01(b), hsb.a); + + var max = hsb.b; + var dif = hsb.b * hsb.s; + var min = hsb.b - dif; + + var h = hsb.h * 360f; + + (r, g, b) = h switch + { + < 60f => (max, h * dif / 60f + min, min), + < 120f => (-(h - 120f) * dif / 60f + min, max, min), + < 180f => (min, max, (h - 120f) * dif / 60f + min), + < 240f => (min, -(h - 240f) * dif / 60f + min, max), + < 300f => ((h - 240f) * dif / 60f + min, min, max), + <= 360f => (max, min, -(h - 360f) * dif / 60f + min), + _ => (0f, 0f, 0f), + }; + + return new Color(Mathf.Clamp01(r), Mathf.Clamp01(g), Mathf.Clamp01(b), hsb.a); + } + + /// + /// Converts a standard Unity RGB to an . + /// + /// The Unity Color to convert. + /// An representing the same visual color. + public static HsbColor FromColor(Color color) + { + Color.RGBToHSV(color, out var h, out var s, out var v); + return new HsbColor(h, s, v, color.a); + } + + /// + /// Linearly interpolates between two values, using the shortest path for the hue. + /// + /// The starting color. + /// The ending color. + /// The interpolation value between the two colors, clamped to a 0 to 1 range. + /// The interpolated . + public static HsbColor Lerp(HsbColor a, HsbColor b, float t) + { + t = Mathf.Clamp01(t); + return LerpUnclamped(a, b, t); + } + + /// + /// Linearly interpolates between two values without clamping the interpolant. + /// + /// The starting color. + /// The ending color. + /// The interpolation value between the two colors. + /// The interpolated . + public static HsbColor LerpUnclamped(HsbColor a, HsbColor b, float t) + { + var hueDifference = Mathf.Repeat(b.h - a.h, 1f); + + if (hueDifference > 0.5f) + hueDifference -= 1f; + + return new HsbColor( + Mathf.Repeat(a.h + (hueDifference * t), 1f), + Mathf.LerpUnclamped(a.s, b.s, t), + Mathf.LerpUnclamped(a.b, b.b, t), + Mathf.LerpUnclamped(a.a, b.a, t)); + } +} diff --git a/MiraAPI/Colors/ColorSpaces/HslColor.cs b/MiraAPI/Colors/ColorSpaces/HslColor.cs new file mode 100644 index 00000000..824a032c --- /dev/null +++ b/MiraAPI/Colors/ColorSpaces/HslColor.cs @@ -0,0 +1,117 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using UnityEngine; + +namespace MiraAPI.Colors.ColorSpaces; + +/// +/// A struct that denotes the HSL (Hue, Saturation, Lightness) color space. +/// +/// The hue component. +/// The saturation component. +/// The lightness component. +/// The alpha component. +[Serializable] +[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Unity parity.")] +public record struct HslColor(float h, float s, float l, float a = 1f) +{ + /// + /// Implicitly converts an to a Unity . + /// + /// The color being converted. + public static implicit operator Color(HslColor c) + { + return ToColor(c); + } + + /// + /// Implicitly converts a Unity to an . + /// + /// The color being converted. + public static implicit operator HslColor(Color c) + { + return FromColor(c); + } + + /// + /// Converts an to a standard Unity RGB . + /// + /// The HSL color to convert. + /// A Unity representing the same visual color. + public static Color ToColor(HslColor hsl) + { + if (hsl.s <= Mathf.Epsilon) + return new Color(Mathf.Clamp01(hsl.l), Mathf.Clamp01(hsl.l), Mathf.Clamp01(hsl.l), hsl.a); + + var c = (1f - Mathf.Abs(2f * hsl.l - 1f)) * hsl.s; + var hPrime = hsl.h * 6f; + var x = c * (1f - Mathf.Abs((hPrime % 2f) - 1f)); + var m = hsl.l - c / 2f; + + var (r, g, b) = hPrime switch + { + < 1f => (c, x, 0f), + < 2f => (x, c, 0f), + < 3f => (0f, c, x), + < 4f => (0f, x, c), + < 5f => (x, 0f, c), + < 6f => (c, 0f, x), + _ => (0f, 0f, 0f), + }; + + return new Color(Mathf.Clamp01(r + m), Mathf.Clamp01(g + m), Mathf.Clamp01(b + m), hsl.a); + } + + /// + /// Converts a standard Unity RGB to an . + /// + /// The Unity Color to convert. + /// An representing the same visual color. + public static HslColor FromColor(Color color) + { + var max = Mathf.Max(color.r, Mathf.Max(color.g, color.b)); + var min = Mathf.Min(color.r, Mathf.Min(color.g, color.b)); + var l = (max + min) / 2f; + var delta = max - min; + + var s = delta == 0f ? 0f : delta / (1f - Mathf.Abs(2f * l - 1f)); + + Color.RGBToHSV(color, out var h, out _, out _); + + return new HslColor(h, s, l, color.a); + } + + /// + /// Linearly interpolates between two values, using the shortest path for the hue. + /// + /// The starting color. + /// The ending color. + /// The interpolation value between the two colors, clamped to a 0 to 1 range. + /// The interpolated . + public static HslColor Lerp(HslColor a, HslColor b, float t) + { + t = Mathf.Clamp01(t); + return LerpUnclamped(a, b, t); + } + + /// + /// Linearly interpolates between two values without clamping the interpolant. + /// + /// The starting color. + /// The ending color. + /// The interpolation value between the two colors. + /// The interpolated . + public static HslColor LerpUnclamped(HslColor a, HslColor b, float t) + { + var hueDifference = Mathf.Repeat(b.h - a.h, 1f); + + if (hueDifference > 0.5f) + hueDifference -= 1f; + + return new HslColor( + Mathf.Repeat(a.h + (hueDifference * t), 1f), + Mathf.LerpUnclamped(a.s, b.s, t), + Mathf.LerpUnclamped(a.l, b.l, t), + Mathf.LerpUnclamped(a.a, b.a, t)); + } +} diff --git a/MiraAPI/Colors/CustomColor.cs b/MiraAPI/Colors/CustomColor.cs index a778e313..2c60dd53 100644 --- a/MiraAPI/Colors/CustomColor.cs +++ b/MiraAPI/Colors/CustomColor.cs @@ -10,7 +10,7 @@ namespace MiraAPI.Colors; /// The name of the option. /// The main color. /// The shadow color. -public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor) +public class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor) { /// /// Gets or sets the main color. diff --git a/MiraAPI/Colors/PaletteManager.cs b/MiraAPI/Colors/PaletteManager.cs index fe7e7250..50a0ed06 100644 --- a/MiraAPI/Colors/PaletteManager.cs +++ b/MiraAPI/Colors/PaletteManager.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using HarmonyLib; +using UnityEngine; namespace MiraAPI.Colors; @@ -15,6 +16,8 @@ public static class PaletteManager public static CustomColor[] RegisteredColors => [.. CustomColors]; internal static readonly List CustomColors = []; + internal static readonly HashSet VariableColorNames = []; + internal static readonly Dictionary ColorIdToColorMap = []; internal static void RegisterAllColors() { @@ -22,11 +25,64 @@ internal static void RegisterAllColors() var shadowColors = CustomColors.Select(x => x.ShadowColor).ToArray(); var stringNames = CustomColors.Select(x => x.Name).ToArray(); + var originalLength = Palette.PlayerColors.Length; + Palette.PlayerColors = Palette.PlayerColors.ToArray().AddRangeToArray(colors); Palette.ShadowColors = Palette.ShadowColors.ToArray().AddRangeToArray(shadowColors); Palette.ColorNames = Palette.ColorNames.ToArray().AddRangeToArray(stringNames); Palette.TextColors = Palette.TextColors.ToArray().AddRangeToArray(colors); Palette.TextOutlineColors = Palette.TextOutlineColors.ToArray().AddRangeToArray(shadowColors); + + for (var i = 0; i < originalLength + CustomColors.Count; i++) + ColorIdToColorMap[i + originalLength] = CustomColors[i]; + } + + /// + /// Checks to see if the provided color ID belongs to a variant color. + /// + /// The id of the color. + /// true if the color is a variable color; false otherwise. + public static bool IsVariable(int colorId) + { + return VariableColorNames.Contains(Palette.ColorNames[colorId]); + } + + /// + /// Checks to see if the provided color ID belongs to a custom color. + /// + /// The id of the color. + /// true if the color is a custom color; false otherwise. + public static bool IsCustomColor(int colorId) + { + return ColorIdToColorMap.ContainsKey(colorId); + } + + /// + /// Gets the main color of the provided color ID. + /// + /// The id of the color. + /// The main color associated with the provided ID. + public static Color32 GetMainColor(int colorId) + { + return !ColorIdToColorMap.TryGetValue(colorId, out var custom) + ? Palette.PlayerColors[colorId] + : (custom is VariableColor variable + ? variable.EvaluateMainColor() + : custom.MainColor); + } + + /// + /// Gets the shadow color of the provided color ID. + /// + /// The id of the color. + /// The shadow color associated with the provided ID. + public static Color32 GetShadowColor(int colorId) + { + return !ColorIdToColorMap.TryGetValue(colorId, out var custom) + ? Palette.ShadowColors[colorId] + : (custom is VariableColor variable + ? variable.EvaluateShadowColor() + : custom.ShadowColor); } } diff --git a/MiraAPI/Colors/VariableColor.cs b/MiraAPI/Colors/VariableColor.cs new file mode 100644 index 00000000..02064230 --- /dev/null +++ b/MiraAPI/Colors/VariableColor.cs @@ -0,0 +1,69 @@ +using System; +using MiraAPI.LocalSettings; +using MiraAPI.Translation; +using MiraAPI.Utilities; +using UnityEngine; + +namespace MiraAPI.Colors; + +/// +/// Represents a dynamic custom color that changes over time. +/// +/// The name of the color. +/// The function to evaluate the dynamic main color. +/// The function to evaluate the dynamic shadow color. +/// The static main color used when dynamic colors are disabled. +/// The static shadow color used when dynamic colors are disabled. +public sealed class VariableColor(StringNames name, Func mainEvaluator, Func? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null) + : CustomColor(name, mainColor ?? mainEvaluator(0f), shadowColor ?? shadowEvaluator?.Invoke(0f) ?? (mainColor ?? mainEvaluator(0f)).GetShadowColor(60)) +{ + /// + /// Gets or sets a delegate that takes a normalized time value and returns the dynamic main color. + /// + public Func MainEvaluator { get; set; } = mainEvaluator; + + /// + /// Gets or sets a delegate that takes a normalized time value and returns the dynamic shadow color. + /// + public Func ShadowEvaluator { get; set; } = shadowEvaluator ?? (t => mainEvaluator(t).GetShadowColor(60)); + + /// + public VariableColor(StringNames name, Func mainEvaluator, Color32? mainColor = null) + : this(name, mainEvaluator, null, mainColor, null) + { + } + + /// + public VariableColor(string name, Func mainEvaluator, Color32? mainColor = null) + : this(name, mainEvaluator, null, mainColor, null) + { + } + + /// + public VariableColor(string name, Func mainEvaluator, Func? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null) + : this(MiraLocaleManager.GetOrCreateLocaleString(name), mainEvaluator, shadowEvaluator, mainColor, shadowColor) + { + } + + /// + /// Evaluates the dynamic main color based on the current in-game time. + /// + /// The dynamically evaluated color, or the static if static variant colors are enabled in the user's settings. + public Color32 EvaluateMainColor() + { + return LocalSettingsTabSingleton.Instance.EnableVariableColors.Value + ? MainEvaluator(Time.time) + : MainColor; + } + + /// + /// Evaluates the dynamic shadow color based on the current in-game time. + /// + /// The dynamically evaluated color, or the static if static variant colors are enabled in the user's settings. + public Color32 EvaluateShadowColor() + { + return LocalSettingsTabSingleton.Instance.EnableVariableColors.Value + ? ShadowEvaluator(Time.time) + : ShadowColor; + } +} diff --git a/MiraAPI/Colors/VariableColorBehaviour.cs b/MiraAPI/Colors/VariableColorBehaviour.cs new file mode 100644 index 00000000..1b7efda1 --- /dev/null +++ b/MiraAPI/Colors/VariableColorBehaviour.cs @@ -0,0 +1,46 @@ +using System.Diagnostics.CodeAnalysis; +using Il2CppInterop.Runtime.Attributes; +using MiraAPI.Colors; +using MiraAPI.Utilities; +using UnityEngine; + +namespace MiraAPI; + +/// +/// A mono script that adjusts the material colors of a component that it's attached to based on the provided variable color. +/// +[SuppressMessage("Style", "IDE0051:Remove unused private members", Justification = "Unity methods.")] +[SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "Unity fields.")] +[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Read above.")] +public sealed class VariableColorBehaviour : MonoBehaviour +{ + private SpriteRenderer rend; + private Material material; + private VariableColor? color; + + /// + /// Sets the current variant color. + /// + /// The new variant color. + [HideFromIl2Cpp] + public void SetColor(VariableColor? col) + { + color = col; + } + + private void Awake() + { + rend = GetComponent(); + material = rend.material; + } + + private void Update() + { + if (!rend || color == null) + return; + + material.SetColor(ShaderID.BodyColor, color.EvaluateMainColor()); + material.SetColor(ShaderID.BackColor, color.EvaluateShadowColor()); + material.SetColor(ShaderID.VisorColor, Palette.VisorColor); + } +} diff --git a/MiraAPI/MiraApiSettings.cs b/MiraAPI/MiraApiSettings.cs index 1560955c..ca448b0f 100644 --- a/MiraAPI/MiraApiSettings.cs +++ b/MiraAPI/MiraApiSettings.cs @@ -233,4 +233,10 @@ public static void ResizeUI(float scaleFactor) /// [LocalToggleSetting] public ConfigEntry EnableSabotageBlares { get; private set; } = config.Bind("MiraApi.Accessibility", "MiraApi.EnableSabotageBlare", true); + + /// + /// Gets whether variant colors will remain static or not. + /// + [LocalToggleSetting] + public ConfigEntry EnableVariableColors { get; private set; } = config.Bind("MiraApi.Accessibility", "MiraApi.EnableVariableColors", true); } diff --git a/MiraAPI/Patches/Hud/VariableColorPatches.cs b/MiraAPI/Patches/Hud/VariableColorPatches.cs new file mode 100644 index 00000000..628a1f4f --- /dev/null +++ b/MiraAPI/Patches/Hud/VariableColorPatches.cs @@ -0,0 +1,98 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; +using AmongUs.Data; +using HarmonyLib; +using MiraAPI.Colors; +using UnityEngine; + +namespace MiraAPI.Patches.Hud; + +[HarmonyPatch(typeof(PlayerMaterial))] +internal static class SetPlayerMaterialPatch +{ + [HarmonyPatch(nameof(PlayerMaterial.SetColors), typeof(int), typeof(Renderer))] + public static bool Prefix(int colorId, Renderer rend) + { + var r = rend.gameObject.GetComponent() + ?? rend.gameObject.AddComponent(); + r.SetColor(PaletteManager.IsVariable(colorId) ? (VariableColor)PaletteManager.ColorIdToColorMap[colorId] : null); + return !PaletteManager.IsVariable(colorId); + } + + [HarmonyPatch(nameof(PlayerMaterial.SetColors), typeof(Color), typeof(Renderer))] + public static void Prefix(Renderer rend) + { + var r = rend.gameObject.GetComponent() + ?? rend.gameObject.AddComponent(); + r.SetColor(null); + } +} + +[HarmonyPatch(typeof(PlayerTab), nameof(PlayerTab.Update))] +internal static class PlayerTabPatch +{ + public static void Postfix(PlayerTab __instance) + { + for (var i = 0; i < __instance.ColorChips.Count; i++) + { + __instance.ColorChips[i].Inner.SpriteColor = PaletteManager.GetMainColor(i); + } + } +} + +[HarmonyPatch(typeof(ChatNotification), nameof(ChatNotification.Update))] +internal static class ChatNotifRainbowPatch +{ + public static void Postfix(ChatNotification __instance) + { + if (!__instance.gameObject.active || !PaletteManager.IsVariable(__instance.player.cosmetics.ColorId)) + return; + + var str = ColorUtility.ToHtmlStringRGB(PaletteManager.GetMainColor(__instance.player.cosmetics.ColorId)); + __instance.playerNameText.text = "" + __instance.playerNameText.text.WithoutRichText(); + } + + private static string WithoutRichText(this string text) + { + var richTagRegex = new Regex(@"<[^>]*>", default, Regex.InfiniteMatchTimeout); + return richTagRegex.Replace(text, string.Empty); + } +} + +[HarmonyPatch(typeof(HostInfoPanel), nameof(HostInfoPanel.Update))] +internal static class RainbowLobbyInfoPanePatch +{ + [SuppressMessage("Style", "IDE0045:Convert to conditional expression", Justification = "Operator becomes too large.")] + public static void Postfix(HostInfoPanel __instance) + { + if (!__instance.gameObject.activeInHierarchy || !PaletteManager.IsVariable(__instance.player.cosmetics.ColorId)) + { + return; + } + + var host = GameData.Instance.GetHost(); + var text = ColorUtility.ToHtmlStringRGB(PaletteManager.GetMainColor(__instance.player.cosmetics.ColorId)); + __instance.hostLabel.text = + TranslationController.Instance.GetString(StringNames.HostNounLabel); + if (__instance.ShouldBoldenHostLabel(DataManager.Settings.Language.CurrentLanguage)) + { + __instance.hostLabel.text = __instance.hostLabel.text.Insert(0, ""); + __instance.hostLabel.text = __instance.hostLabel.text.Insert(__instance.hostLabel.text.Length, ""); + } + + if (AmongUsClient.Instance.AmHost) + { + __instance.playerName.text = (string.IsNullOrEmpty(host.PlayerName) + ? "..." + : $"{host.PlayerName}") + + " " + + TranslationController.Instance.GetString(StringNames.HostYouLabel); + } + else + { + __instance.playerName.text = + (string.IsNullOrEmpty(host.PlayerName) ? "..." : $"{host.PlayerName}") + + " (" + __instance.player.ColorBlindName + ")"; + } + } +} diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index a3b5158d..9058eac7 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -404,7 +404,7 @@ private static void RegisterColorClasses(Type type) foreach (var property in type.GetProperties()) { - if (property.PropertyType != typeof(CustomColor)) + if (property.PropertyType.IsAssignableTo(typeof(CustomColor))) { continue; } @@ -416,6 +416,9 @@ private static void RegisterColorClasses(Type type) } PaletteManager.CustomColors.Add(color); + + if (color is VariableColor) + PaletteManager.VariableColorNames.Add(color.Name); } foreach (var field in type.GetFields().Where(f => f.FieldType.IsAssignableTo(typeof(CustomColor)))) diff --git a/MiraAPI/Resources/Locale/en_US.xml b/MiraAPI/Resources/Locale/en_US.xml index 7cd2c1c1..be537337 100644 --- a/MiraAPI/Resources/Locale/en_US.xml +++ b/MiraAPI/Resources/Locale/en_US.xml @@ -12,6 +12,7 @@ Display Wiki on Bottom Row Enable Sabotage Flashes Enable Sabotage Blare + Enable Variable Color Changes RETURN TO ROLE SETTINGS Custom Category 1 Custom Category 2 From a73d1df67e950936db80731cae1a9077f9140c03 Mon Sep 17 00:00:00 2001 From: Az <56699741+AlchlcDvl@users.noreply.github.com> Date: Tue, 29 Sep 2026 13:54:06 -0400 Subject: [PATCH 2/7] optimisations + example color --- .editorconfig | 6 ++ MiraAPI.Example/ExampleColors.cs | 7 ++ MiraAPI/Colors/ColorSpaces/CmykColor.cs | 40 ++++++----- MiraAPI/Colors/ColorSpaces/HsbColor.cs | 90 +++++++++++++++--------- MiraAPI/Colors/ColorSpaces/HslColor.cs | 61 ++++++++++------ MiraAPI/Colors/CustomColor.cs | 30 +++++++- MiraAPI/Colors/PaletteManager.cs | 8 +-- MiraAPI/Colors/VariableColor.cs | 20 +++--- MiraAPI/Colors/VariableColorBehaviour.cs | 4 +- MiraAPI/Utilities/Extensions.cs | 20 +++++- MiraAPI/Utilities/MathUtilities.cs | 82 +++++++++++++++++++++ 11 files changed, 272 insertions(+), 96 deletions(-) create mode 100644 MiraAPI/Utilities/MathUtilities.cs diff --git a/.editorconfig b/.editorconfig index c0529a81..01e10b81 100644 --- a/.editorconfig +++ b/.editorconfig @@ -89,3 +89,9 @@ dotnet_diagnostic.IDE0001.severity = warning # IDE0079: Remove unnecessary suppression dotnet_diagnostic.IDE0079.severity = none + +# S1244: Floating point numbers should not be tested for equality +dotnet_diagnostic.S1244.severity = none + +# IDE0045: Convert to conditional expression +dotnet_diagnostic.IDE0045.severity = none diff --git a/MiraAPI.Example/ExampleColors.cs b/MiraAPI.Example/ExampleColors.cs index cde5aaed..1a5efec3 100644 --- a/MiraAPI.Example/ExampleColors.cs +++ b/MiraAPI.Example/ExampleColors.cs @@ -1,4 +1,6 @@ using MiraAPI.Colors; +using MiraAPI.Colors.ColorSpaces; +using MiraAPI.Utilities; using UnityEngine; namespace MiraAPI.Example; @@ -14,4 +16,9 @@ public static class ExampleColors public static CustomColor Rose { get; } = new("color.rose", new Color(0.98f, 0.26f, 0.62f)); public static CustomColor Gold { get; } = new("color.gold", new Color(1.0f, 0.84f, 0.0f)); + + public static VariableColor Rainbow { get; } = new("color.rainbow", t => new HsbColor(MathUtilities.PingPong(t, 1f), 1f, 1f)) + { + ColorBrightness = CustomColorBrightness.Lighter, + }; } diff --git a/MiraAPI/Colors/ColorSpaces/CmykColor.cs b/MiraAPI/Colors/ColorSpaces/CmykColor.cs index 569e848a..268dc81a 100644 --- a/MiraAPI/Colors/ColorSpaces/CmykColor.cs +++ b/MiraAPI/Colors/ColorSpaces/CmykColor.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using MiraAPI.Utilities; using UnityEngine; namespace MiraAPI.Colors.ColorSpaces; @@ -20,7 +21,7 @@ public record struct CmykColor(float c, float m, float y, float k, float a = 1f) /// Implicitly converts a to a Unity . /// /// The color being converted. - public static implicit operator Color(CmykColor c) + public static implicit operator Color(in CmykColor c) { return ToColor(c); } @@ -29,7 +30,7 @@ public static implicit operator Color(CmykColor c) /// Implicitly converts a Unity to a . /// /// The color being converted. - public static implicit operator CmykColor(Color c) + public static implicit operator CmykColor(in Color c) { return FromColor(c); } @@ -39,13 +40,13 @@ public static implicit operator CmykColor(Color c) /// /// The CMYK color to convert. /// A Unity representing the same visual color. - public static Color ToColor(CmykColor cmyk) + public static Color ToColor(in CmykColor cmyk) { var r = (1f - cmyk.c) * (1f - cmyk.k); var g = (1f - cmyk.m) * (1f - cmyk.k); var b = (1f - cmyk.y) * (1f - cmyk.k); - return new Color(Mathf.Clamp01(r), Mathf.Clamp01(g), Mathf.Clamp01(b), cmyk.a); + return new Color(MathUtilities.Clamp01(r), MathUtilities.Clamp01(g), MathUtilities.Clamp01(b), cmyk.a); } /// @@ -53,22 +54,24 @@ public static Color ToColor(CmykColor cmyk) /// /// The Unity Color to convert. /// A representing the same visual color. - public static CmykColor FromColor(Color color) + public static CmykColor FromColor(in Color color) { var r = color.r; var g = color.g; var b = color.b; - var k = 1f - Mathf.Max(r, g, b); + var k = 1f - MathF.Max(r, MathF.Max(g, b)); if (k >= 1f) return new CmykColor(0f, 0f, 0f, 1f, color.a); - var c = (1f - r - k) / (1f - k); - var m = (1f - g - k) / (1f - k); - var y = (1f - b - k) / (1f - k); + var invK = 1f / (1f - k); - return new CmykColor(Mathf.Clamp01(c), Mathf.Clamp01(m), Mathf.Clamp01(y), Mathf.Clamp01(k), color.a); + var c = (1f - r - k) * invK; + var m = (1f - g - k) * invK; + var y = (1f - b - k) * invK; + + return new CmykColor(MathUtilities.Clamp01(c), MathUtilities.Clamp01(m), MathUtilities.Clamp01(y), MathUtilities.Clamp01(k), color.a); } /// @@ -78,10 +81,9 @@ public static CmykColor FromColor(Color color) /// The ending color. /// The interpolation value between the two colors, clamped to a 0 to 1 range. /// The interpolated . - public static CmykColor Lerp(CmykColor a, CmykColor b, float t) + public static CmykColor Lerp(in CmykColor a, in CmykColor b, float t) { - t = Mathf.Clamp01(t); - return LerpUnclamped(a, b, t); + return LerpUnclamped(a, b, MathUtilities.Clamp01(t)); } /// @@ -91,13 +93,13 @@ public static CmykColor Lerp(CmykColor a, CmykColor b, float t) /// The ending color. /// The interpolation value between the two colors. /// The interpolated . - public static CmykColor LerpUnclamped(CmykColor a, CmykColor b, float t) + public static CmykColor LerpUnclamped(in CmykColor a, in CmykColor b, float t) { return new CmykColor( - Mathf.LerpUnclamped(a.c, b.c, t), - Mathf.LerpUnclamped(a.m, b.m, t), - Mathf.LerpUnclamped(a.y, b.y, t), - Mathf.LerpUnclamped(a.k, b.k, t), - Mathf.LerpUnclamped(a.a, b.a, t)); + MathUtilities.LerpUnclamped(a.c, b.c, t), + MathUtilities.LerpUnclamped(a.m, b.m, t), + MathUtilities.LerpUnclamped(a.y, b.y, t), + MathUtilities.LerpUnclamped(a.k, b.k, t), + MathUtilities.LerpUnclamped(a.a, b.a, t)); } } diff --git a/MiraAPI/Colors/ColorSpaces/HsbColor.cs b/MiraAPI/Colors/ColorSpaces/HsbColor.cs index 4dec19a3..160ae85d 100644 --- a/MiraAPI/Colors/ColorSpaces/HsbColor.cs +++ b/MiraAPI/Colors/ColorSpaces/HsbColor.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using MiraAPI.Utilities; using UnityEngine; namespace MiraAPI.Colors.ColorSpaces; @@ -19,7 +20,7 @@ public record struct HsbColor(float h, float s, float b, float a = 1f) /// Implicitly converts an to a Unity . /// /// The color being converted. - public static implicit operator Color(HsbColor c) + public static implicit operator Color(in HsbColor c) { return ToColor(c); } @@ -28,7 +29,7 @@ public static implicit operator Color(HsbColor c) /// Implicitly converts a Unity to an . /// /// The color being converted. - public static implicit operator HsbColor(Color c) + public static implicit operator HsbColor(in Color c) { return FromColor(c); } @@ -38,33 +39,32 @@ public static implicit operator HsbColor(Color c) /// /// The HSB color to convert. /// A Unity representing the same visual color. - public static Color ToColor(HsbColor hsb) + public static Color ToColor(in HsbColor hsb) { - var r = hsb.b; - var g = hsb.b; - var b = hsb.b; + if (hsb.s <= float.Epsilon) + return new Color(MathUtilities.Clamp01(hsb.b), MathUtilities.Clamp01(hsb.b), MathUtilities.Clamp01(hsb.b), hsb.a); - if (hsb.s <= Mathf.Epsilon) - return new Color(Mathf.Clamp01(r), Mathf.Clamp01(g), Mathf.Clamp01(b), hsb.a); + var c = hsb.b * hsb.s; + var hPrime = hsb.h * 6f; + var x = c * (1f - MathF.Abs((hPrime % 2f) - 1f)); + var m = hsb.b - c; - var max = hsb.b; - var dif = hsb.b * hsb.s; - var min = hsb.b - dif; - - var h = hsb.h * 360f; - - (r, g, b) = h switch + var (r, g, b) = hPrime switch { - < 60f => (max, h * dif / 60f + min, min), - < 120f => (-(h - 120f) * dif / 60f + min, max, min), - < 180f => (min, max, (h - 120f) * dif / 60f + min), - < 240f => (min, -(h - 240f) * dif / 60f + min, max), - < 300f => ((h - 240f) * dif / 60f + min, min, max), - <= 360f => (max, min, -(h - 360f) * dif / 60f + min), + < 1f => (c, x, 0f), + < 2f => (x, c, 0f), + < 3f => (0f, c, x), + < 4f => (0f, x, c), + < 5f => (x, 0f, c), + <= 6f => (c, 0f, x), _ => (0f, 0f, 0f), }; - return new Color(Mathf.Clamp01(r), Mathf.Clamp01(g), Mathf.Clamp01(b), hsb.a); + return new Color( + MathUtilities.Clamp01(r + m), + MathUtilities.Clamp01(g + m), + MathUtilities.Clamp01(b + m), + hsb.a); } /// @@ -72,10 +72,33 @@ public static Color ToColor(HsbColor hsb) /// /// The Unity Color to convert. /// An representing the same visual color. - public static HsbColor FromColor(Color color) + public static HsbColor FromColor(in Color color) { - Color.RGBToHSV(color, out var h, out var s, out var v); - return new HsbColor(h, s, v, color.a); + var max = MathF.Max(color.r, MathF.Max(color.g, color.b)); + var min = MathF.Min(color.r, MathF.Min(color.g, color.b)); + var delta = max - min; + + var h = 0f; + + if (delta > 0f) + { + if (max == color.r) + h = (color.g - color.b) / delta % 6f; + else if (max == color.g) + h = (color.b - color.r) / delta + 2f; + else + h = (color.r - color.g) / delta + 4f; + + h /= 6f; + + if (h < 0f) + h += 1f; + } + + var s = max == 0f ? 0f : delta / max; + var b = max; + + return new HsbColor(h, s, b, color.a); } /// @@ -85,10 +108,9 @@ public static HsbColor FromColor(Color color) /// The ending color. /// The interpolation value between the two colors, clamped to a 0 to 1 range. /// The interpolated . - public static HsbColor Lerp(HsbColor a, HsbColor b, float t) + public static HsbColor Lerp(in HsbColor a, in HsbColor b, float t) { - t = Mathf.Clamp01(t); - return LerpUnclamped(a, b, t); + return LerpUnclamped(a, b, MathUtilities.Clamp01(t)); } /// @@ -98,17 +120,17 @@ public static HsbColor Lerp(HsbColor a, HsbColor b, float t) /// The ending color. /// The interpolation value between the two colors. /// The interpolated . - public static HsbColor LerpUnclamped(HsbColor a, HsbColor b, float t) + public static HsbColor LerpUnclamped(in HsbColor a, in HsbColor b, float t) { - var hueDifference = Mathf.Repeat(b.h - a.h, 1f); + var hueDifference = MathUtilities.Repeat(b.h - a.h, 1f); if (hueDifference > 0.5f) hueDifference -= 1f; return new HsbColor( - Mathf.Repeat(a.h + (hueDifference * t), 1f), - Mathf.LerpUnclamped(a.s, b.s, t), - Mathf.LerpUnclamped(a.b, b.b, t), - Mathf.LerpUnclamped(a.a, b.a, t)); + MathUtilities.Repeat(a.h + (hueDifference * t), 1f), + MathUtilities.LerpUnclamped(a.s, b.s, t), + MathUtilities.LerpUnclamped(a.b, b.b, t), + MathUtilities.LerpUnclamped(a.a, b.a, t)); } } diff --git a/MiraAPI/Colors/ColorSpaces/HslColor.cs b/MiraAPI/Colors/ColorSpaces/HslColor.cs index 824a032c..5ff70771 100644 --- a/MiraAPI/Colors/ColorSpaces/HslColor.cs +++ b/MiraAPI/Colors/ColorSpaces/HslColor.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using MiraAPI.Utilities; using UnityEngine; namespace MiraAPI.Colors.ColorSpaces; @@ -19,7 +20,7 @@ public record struct HslColor(float h, float s, float l, float a = 1f) /// Implicitly converts an to a Unity . /// /// The color being converted. - public static implicit operator Color(HslColor c) + public static implicit operator Color(in HslColor c) { return ToColor(c); } @@ -28,7 +29,7 @@ public static implicit operator Color(HslColor c) /// Implicitly converts a Unity to an . /// /// The color being converted. - public static implicit operator HslColor(Color c) + public static implicit operator HslColor(in Color c) { return FromColor(c); } @@ -38,14 +39,14 @@ public static implicit operator HslColor(Color c) /// /// The HSL color to convert. /// A Unity representing the same visual color. - public static Color ToColor(HslColor hsl) + public static Color ToColor(in HslColor hsl) { - if (hsl.s <= Mathf.Epsilon) - return new Color(Mathf.Clamp01(hsl.l), Mathf.Clamp01(hsl.l), Mathf.Clamp01(hsl.l), hsl.a); + if (hsl.s <= float.Epsilon) + return new Color(MathUtilities.Clamp01(hsl.l), MathUtilities.Clamp01(hsl.l), MathUtilities.Clamp01(hsl.l), hsl.a); - var c = (1f - Mathf.Abs(2f * hsl.l - 1f)) * hsl.s; + var c = (1f - MathF.Abs(2f * hsl.l - 1f)) * hsl.s; var hPrime = hsl.h * 6f; - var x = c * (1f - Mathf.Abs((hPrime % 2f) - 1f)); + var x = c * (1f - MathF.Abs((hPrime % 2f) - 1f)); var m = hsl.l - c / 2f; var (r, g, b) = hPrime switch @@ -55,11 +56,11 @@ public static Color ToColor(HslColor hsl) < 3f => (0f, c, x), < 4f => (0f, x, c), < 5f => (x, 0f, c), - < 6f => (c, 0f, x), + <= 6f => (c, 0f, x), _ => (0f, 0f, 0f), }; - return new Color(Mathf.Clamp01(r + m), Mathf.Clamp01(g + m), Mathf.Clamp01(b + m), hsl.a); + return new Color(MathUtilities.Clamp01(r + m), MathUtilities.Clamp01(g + m), MathUtilities.Clamp01(b + m), hsl.a); } /// @@ -67,16 +68,31 @@ public static Color ToColor(HslColor hsl) /// /// The Unity Color to convert. /// An representing the same visual color. - public static HslColor FromColor(Color color) + public static HslColor FromColor(in Color color) { - var max = Mathf.Max(color.r, Mathf.Max(color.g, color.b)); - var min = Mathf.Min(color.r, Mathf.Min(color.g, color.b)); + var max = MathF.Max(color.r, MathF.Max(color.g, color.b)); + var min = MathF.Min(color.r, MathF.Min(color.g, color.b)); var l = (max + min) / 2f; var delta = max - min; - var s = delta == 0f ? 0f : delta / (1f - Mathf.Abs(2f * l - 1f)); + var s = delta == 0f ? 0f : delta / (1f - MathF.Abs(2f * l - 1f)); - Color.RGBToHSV(color, out var h, out _, out _); + var h = 0f; + + if (delta > 0f) + { + if (max == color.r) + h = (color.g - color.b) / delta % 6f; + else if (max == color.g) + h = (color.b - color.r) / delta + 2f; + else + h = (color.r - color.g) / delta + 4f; + + h /= 6f; + + if (h < 0f) + h += 1f; + } return new HslColor(h, s, l, color.a); } @@ -88,10 +104,9 @@ public static HslColor FromColor(Color color) /// The ending color. /// The interpolation value between the two colors, clamped to a 0 to 1 range. /// The interpolated . - public static HslColor Lerp(HslColor a, HslColor b, float t) + public static HslColor Lerp(in HslColor a, in HslColor b, float t) { - t = Mathf.Clamp01(t); - return LerpUnclamped(a, b, t); + return LerpUnclamped(a, b, MathUtilities.Clamp01(t)); } /// @@ -101,17 +116,17 @@ public static HslColor Lerp(HslColor a, HslColor b, float t) /// The ending color. /// The interpolation value between the two colors. /// The interpolated . - public static HslColor LerpUnclamped(HslColor a, HslColor b, float t) + public static HslColor LerpUnclamped(in HslColor a, in HslColor b, float t) { - var hueDifference = Mathf.Repeat(b.h - a.h, 1f); + var hueDifference = MathUtilities.Repeat(b.h - a.h, 1f); if (hueDifference > 0.5f) hueDifference -= 1f; return new HslColor( - Mathf.Repeat(a.h + (hueDifference * t), 1f), - Mathf.LerpUnclamped(a.s, b.s, t), - Mathf.LerpUnclamped(a.l, b.l, t), - Mathf.LerpUnclamped(a.a, b.a, t)); + MathUtilities.Repeat(a.h + (hueDifference * t), 1f), + MathUtilities.LerpUnclamped(a.s, b.s, t), + MathUtilities.LerpUnclamped(a.l, b.l, t), + MathUtilities.LerpUnclamped(a.a, b.a, t)); } } diff --git a/MiraAPI/Colors/CustomColor.cs b/MiraAPI/Colors/CustomColor.cs index 2c60dd53..a49e2337 100644 --- a/MiraAPI/Colors/CustomColor.cs +++ b/MiraAPI/Colors/CustomColor.cs @@ -1,4 +1,5 @@ -using MiraAPI.Translation; +using System.Diagnostics.CodeAnalysis; +using MiraAPI.Translation; using MiraAPI.Utilities; using UnityEngine; @@ -10,17 +11,37 @@ namespace MiraAPI.Colors; /// The name of the option. /// The main color. /// The shadow color. +[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1513:Closing brace should be followed by blank line", Justification = "Looks weird.")] +[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Internal code.")] public class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor) { /// /// Gets or sets the main color. /// - public Color32 MainColor { get; set; } = mainColor; + public Color32 MainColor + { + get; + set + { + field = value; + InternalMainColor = value; + } + } + = mainColor; /// /// Gets or sets the shadow color. /// - public Color32 ShadowColor { get; set; } = shadowColor; + public Color32 ShadowColor + { + get; + set + { + field = value; + InternalShadowColor = value; + } + } + = shadowColor; /// /// Gets or sets the name of the color. @@ -32,6 +53,9 @@ public class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColo /// public CustomColorBrightness ColorBrightness { get; set; } = CustomColorBrightness.Darker; + internal Color InternalMainColor = mainColor; + internal Color InternalShadowColor = shadowColor; + /// public CustomColor(StringNames name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60)) diff --git a/MiraAPI/Colors/PaletteManager.cs b/MiraAPI/Colors/PaletteManager.cs index 50a0ed06..b552521a 100644 --- a/MiraAPI/Colors/PaletteManager.cs +++ b/MiraAPI/Colors/PaletteManager.cs @@ -63,13 +63,13 @@ public static bool IsCustomColor(int colorId) /// /// The id of the color. /// The main color associated with the provided ID. - public static Color32 GetMainColor(int colorId) + public static Color GetMainColor(int colorId) { return !ColorIdToColorMap.TryGetValue(colorId, out var custom) ? Palette.PlayerColors[colorId] : (custom is VariableColor variable ? variable.EvaluateMainColor() - : custom.MainColor); + : custom.InternalMainColor); } /// @@ -77,12 +77,12 @@ public static Color32 GetMainColor(int colorId) /// /// The id of the color. /// The shadow color associated with the provided ID. - public static Color32 GetShadowColor(int colorId) + public static Color GetShadowColor(int colorId) { return !ColorIdToColorMap.TryGetValue(colorId, out var custom) ? Palette.ShadowColors[colorId] : (custom is VariableColor variable ? variable.EvaluateShadowColor() - : custom.ShadowColor); + : custom.InternalShadowColor); } } diff --git a/MiraAPI/Colors/VariableColor.cs b/MiraAPI/Colors/VariableColor.cs index 02064230..2655ff50 100644 --- a/MiraAPI/Colors/VariableColor.cs +++ b/MiraAPI/Colors/VariableColor.cs @@ -14,33 +14,33 @@ namespace MiraAPI.Colors; /// The function to evaluate the dynamic shadow color. /// The static main color used when dynamic colors are disabled. /// The static shadow color used when dynamic colors are disabled. -public sealed class VariableColor(StringNames name, Func mainEvaluator, Func? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null) +public sealed class VariableColor(StringNames name, Func mainEvaluator, Func? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null) : CustomColor(name, mainColor ?? mainEvaluator(0f), shadowColor ?? shadowEvaluator?.Invoke(0f) ?? (mainColor ?? mainEvaluator(0f)).GetShadowColor(60)) { /// /// Gets or sets a delegate that takes a normalized time value and returns the dynamic main color. /// - public Func MainEvaluator { get; set; } = mainEvaluator; + public Func MainEvaluator { get; set; } = mainEvaluator; /// /// Gets or sets a delegate that takes a normalized time value and returns the dynamic shadow color. /// - public Func ShadowEvaluator { get; set; } = shadowEvaluator ?? (t => mainEvaluator(t).GetShadowColor(60)); + public Func ShadowEvaluator { get; set; } = shadowEvaluator ?? (t => mainEvaluator(t).GetShadowColor(0.24f)); /// - public VariableColor(StringNames name, Func mainEvaluator, Color32? mainColor = null) + public VariableColor(StringNames name, Func mainEvaluator, Color32? mainColor = null) : this(name, mainEvaluator, null, mainColor, null) { } /// - public VariableColor(string name, Func mainEvaluator, Color32? mainColor = null) + public VariableColor(string name, Func mainEvaluator, Color32? mainColor = null) : this(name, mainEvaluator, null, mainColor, null) { } /// - public VariableColor(string name, Func mainEvaluator, Func? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null) + public VariableColor(string name, Func mainEvaluator, Func? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null) : this(MiraLocaleManager.GetOrCreateLocaleString(name), mainEvaluator, shadowEvaluator, mainColor, shadowColor) { } @@ -49,21 +49,21 @@ public VariableColor(string name, Func mainEvaluator, Func /// The dynamically evaluated color, or the static if static variant colors are enabled in the user's settings. - public Color32 EvaluateMainColor() + public Color EvaluateMainColor() { return LocalSettingsTabSingleton.Instance.EnableVariableColors.Value ? MainEvaluator(Time.time) - : MainColor; + : InternalMainColor; } /// /// Evaluates the dynamic shadow color based on the current in-game time. /// /// The dynamically evaluated color, or the static if static variant colors are enabled in the user's settings. - public Color32 EvaluateShadowColor() + public Color EvaluateShadowColor() { return LocalSettingsTabSingleton.Instance.EnableVariableColors.Value ? ShadowEvaluator(Time.time) - : ShadowColor; + : InternalShadowColor; } } diff --git a/MiraAPI/Colors/VariableColorBehaviour.cs b/MiraAPI/Colors/VariableColorBehaviour.cs index 1b7efda1..f33a12c0 100644 --- a/MiraAPI/Colors/VariableColorBehaviour.cs +++ b/MiraAPI/Colors/VariableColorBehaviour.cs @@ -18,6 +18,8 @@ public sealed class VariableColorBehaviour : MonoBehaviour private Material material; private VariableColor? color; + private static readonly Color VisorColor = Palette.VisorColor; + /// /// Sets the current variant color. /// @@ -41,6 +43,6 @@ private void Update() material.SetColor(ShaderID.BodyColor, color.EvaluateMainColor()); material.SetColor(ShaderID.BackColor, color.EvaluateShadowColor()); - material.SetColor(ShaderID.VisorColor, Palette.VisorColor); + material.SetColor(ShaderID.VisorColor, VisorColor); } } diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 57cd64b5..9017d13b 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -620,7 +620,23 @@ public static Color32 GetShadowColor(this Color32 color, byte darknessAmount) (byte)Mathf.Clamp(color.r - darknessAmount, 0, 255), (byte)Mathf.Clamp(color.g - darknessAmount, 0, 255), (byte)Mathf.Clamp(color.b - darknessAmount, 0, 255), - byte.MaxValue); + color.a); + } + + /// + /// Gets a darkened version of a . + /// + /// The original . + /// A darkness amount between 0 and 1. + /// The darkened . + public static Color GetShadowColor(this Color color, float darknessAmount) + { + return + new Color( + MathUtilities.Clamp01(color.r - darknessAmount), + MathUtilities.Clamp01(color.g - darknessAmount), + MathUtilities.Clamp01(color.b - darknessAmount), + color.a); } /// @@ -796,7 +812,7 @@ public static Color LightenColor(this Color color, float amount = 0.45f) } /// - /// Fixed version of . + /// Fixed version of . /// /// The you want to update the outline for. /// The outline . diff --git a/MiraAPI/Utilities/MathUtilities.cs b/MiraAPI/Utilities/MathUtilities.cs new file mode 100644 index 00000000..a505a36a --- /dev/null +++ b/MiraAPI/Utilities/MathUtilities.cs @@ -0,0 +1,82 @@ +using System; + +namespace MiraAPI.Utilities; + +/// +/// Provides mathematical utility methods. +/// +public static class MathUtilities +{ + /// + /// Linearly interpolates between two values, clamping the interpolant between 0 and 1. + /// + /// The starting value. + /// The ending value. + /// The interpolation value, automatically clamped to a 0 to 1 range. + /// The interpolated float. + public static float Lerp(float a, float b, float t) + { + return a + ((b - a) * Clamp01(t)); + } + + /// + /// Linearly interpolates between two values without clamping the interpolant. + /// + /// The starting value. + /// The ending value. + /// The interpolation value. + /// The interpolated float. + public static float LerpUnclamped(float a, float b, float t) + { + return a + ((b - a) * t); + } + + /// + /// Clamps a floating-point value strictly between 0 and 1. + /// + /// The floating-point value to clamp. + /// The clamped value. + public static float Clamp01(float value) + { + return Math.Clamp(value, 0f, 1f); + } + + /// + /// Ping-pongs a value back and forth between a minimum and maximum value. + /// + /// The input time or interpolant. + /// The minimum boundary of the ping-pong wave. + /// The maximum boundary of the ping-pong wave. + /// The multiplier for the speed of the ping-pong effect. + /// A value oscillating continuously between and . + public static float PingPong(float t, float min, float max, float speed = 1f) + { + return min + PingPong(t, max - min, speed); + } + + /// + /// Ping-pongs a value back and forth between 0 and a specified length. + /// + /// The input time or interpolant. + /// The maximum value to bounce against, starting from 0. + /// The multiplier for the speed of the ping-pong effect. + /// A value oscillating continuously between 0 and . + public static float PingPong(float t, float length, float speed = 1f) + { + var dx = speed * t; + var floor = (int)MathF.Floor(dx); + var phase = floor % 2; // 0 = forward, 1 = backward + return Clamp01(((dx - floor) * ((2 * phase) - 1)) + 1 - phase) * length; + } + + /// + /// Loops a value strictly between 0 and a maximum length, behaving like a standard float modulo. + /// + /// The input value to loop. + /// The maximum length of the repeating sequence. + /// The wrapped value smoothly looping back to 0 upon reaching . + public static float Repeat(float t, float length) + { + return Math.Clamp(t - (MathF.Floor(t / length) * length), 0f, length); + } +} From 27ac201f8830d22b2870cdc5fc03bcce3bc66a48 Mon Sep 17 00:00:00 2001 From: Az <56699741+AlchlcDvl@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:06:49 -0400 Subject: [PATCH 3/7] fix oversights + errors --- MiraAPI/Colors/ColorSpaces/CmykColor.cs | 12 ++++++------ MiraAPI/Colors/ColorSpaces/HsbColor.cs | 12 ++++++------ MiraAPI/Colors/ColorSpaces/HslColor.cs | 12 ++++++------ MiraAPI/Colors/PaletteManager.cs | 6 +++--- MiraAPI/Colors/VariableColor.cs | 10 ++++++---- MiraAPI/Colors/VariableColorBehaviour.cs | 9 ++++++--- MiraAPI/Hud/CustomPaginableMenu.cs | 4 ++-- MiraAPI/Patches/Hud/VariableColorPatches.cs | 21 +++++++++++---------- MiraAPI/PluginLoading/MiraPluginManager.cs | 2 +- MiraAPI/Utilities/Extensions.cs | 13 +++++++++++++ 10 files changed, 60 insertions(+), 41 deletions(-) diff --git a/MiraAPI/Colors/ColorSpaces/CmykColor.cs b/MiraAPI/Colors/ColorSpaces/CmykColor.cs index 268dc81a..c94ebecc 100644 --- a/MiraAPI/Colors/ColorSpaces/CmykColor.cs +++ b/MiraAPI/Colors/ColorSpaces/CmykColor.cs @@ -21,7 +21,7 @@ public record struct CmykColor(float c, float m, float y, float k, float a = 1f) /// Implicitly converts a to a Unity . /// /// The color being converted. - public static implicit operator Color(in CmykColor c) + public static implicit operator Color(CmykColor c) { return ToColor(c); } @@ -30,7 +30,7 @@ public static implicit operator Color(in CmykColor c) /// Implicitly converts a Unity to a . /// /// The color being converted. - public static implicit operator CmykColor(in Color c) + public static implicit operator CmykColor(Color c) { return FromColor(c); } @@ -40,7 +40,7 @@ public static implicit operator CmykColor(in Color c) /// /// The CMYK color to convert. /// A Unity representing the same visual color. - public static Color ToColor(in CmykColor cmyk) + public static Color ToColor(CmykColor cmyk) { var r = (1f - cmyk.c) * (1f - cmyk.k); var g = (1f - cmyk.m) * (1f - cmyk.k); @@ -54,7 +54,7 @@ public static Color ToColor(in CmykColor cmyk) /// /// The Unity Color to convert. /// A representing the same visual color. - public static CmykColor FromColor(in Color color) + public static CmykColor FromColor(Color color) { var r = color.r; var g = color.g; @@ -81,7 +81,7 @@ public static CmykColor FromColor(in Color color) /// The ending color. /// The interpolation value between the two colors, clamped to a 0 to 1 range. /// The interpolated . - public static CmykColor Lerp(in CmykColor a, in CmykColor b, float t) + public static CmykColor Lerp(CmykColor a, CmykColor b, float t) { return LerpUnclamped(a, b, MathUtilities.Clamp01(t)); } @@ -93,7 +93,7 @@ public static CmykColor Lerp(in CmykColor a, in CmykColor b, float t) /// The ending color. /// The interpolation value between the two colors. /// The interpolated . - public static CmykColor LerpUnclamped(in CmykColor a, in CmykColor b, float t) + public static CmykColor LerpUnclamped(CmykColor a, CmykColor b, float t) { return new CmykColor( MathUtilities.LerpUnclamped(a.c, b.c, t), diff --git a/MiraAPI/Colors/ColorSpaces/HsbColor.cs b/MiraAPI/Colors/ColorSpaces/HsbColor.cs index 160ae85d..b566333e 100644 --- a/MiraAPI/Colors/ColorSpaces/HsbColor.cs +++ b/MiraAPI/Colors/ColorSpaces/HsbColor.cs @@ -20,7 +20,7 @@ public record struct HsbColor(float h, float s, float b, float a = 1f) /// Implicitly converts an to a Unity . /// /// The color being converted. - public static implicit operator Color(in HsbColor c) + public static implicit operator Color(HsbColor c) { return ToColor(c); } @@ -29,7 +29,7 @@ public static implicit operator Color(in HsbColor c) /// Implicitly converts a Unity to an . /// /// The color being converted. - public static implicit operator HsbColor(in Color c) + public static implicit operator HsbColor(Color c) { return FromColor(c); } @@ -39,7 +39,7 @@ public static implicit operator HsbColor(in Color c) /// /// The HSB color to convert. /// A Unity representing the same visual color. - public static Color ToColor(in HsbColor hsb) + public static Color ToColor(HsbColor hsb) { if (hsb.s <= float.Epsilon) return new Color(MathUtilities.Clamp01(hsb.b), MathUtilities.Clamp01(hsb.b), MathUtilities.Clamp01(hsb.b), hsb.a); @@ -72,7 +72,7 @@ public static Color ToColor(in HsbColor hsb) /// /// The Unity Color to convert. /// An representing the same visual color. - public static HsbColor FromColor(in Color color) + public static HsbColor FromColor(Color color) { var max = MathF.Max(color.r, MathF.Max(color.g, color.b)); var min = MathF.Min(color.r, MathF.Min(color.g, color.b)); @@ -108,7 +108,7 @@ public static HsbColor FromColor(in Color color) /// The ending color. /// The interpolation value between the two colors, clamped to a 0 to 1 range. /// The interpolated . - public static HsbColor Lerp(in HsbColor a, in HsbColor b, float t) + public static HsbColor Lerp(HsbColor a, HsbColor b, float t) { return LerpUnclamped(a, b, MathUtilities.Clamp01(t)); } @@ -120,7 +120,7 @@ public static HsbColor Lerp(in HsbColor a, in HsbColor b, float t) /// The ending color. /// The interpolation value between the two colors. /// The interpolated . - public static HsbColor LerpUnclamped(in HsbColor a, in HsbColor b, float t) + public static HsbColor LerpUnclamped(HsbColor a, HsbColor b, float t) { var hueDifference = MathUtilities.Repeat(b.h - a.h, 1f); diff --git a/MiraAPI/Colors/ColorSpaces/HslColor.cs b/MiraAPI/Colors/ColorSpaces/HslColor.cs index 5ff70771..1929e774 100644 --- a/MiraAPI/Colors/ColorSpaces/HslColor.cs +++ b/MiraAPI/Colors/ColorSpaces/HslColor.cs @@ -20,7 +20,7 @@ public record struct HslColor(float h, float s, float l, float a = 1f) /// Implicitly converts an to a Unity . /// /// The color being converted. - public static implicit operator Color(in HslColor c) + public static implicit operator Color(HslColor c) { return ToColor(c); } @@ -29,7 +29,7 @@ public static implicit operator Color(in HslColor c) /// Implicitly converts a Unity to an . /// /// The color being converted. - public static implicit operator HslColor(in Color c) + public static implicit operator HslColor(Color c) { return FromColor(c); } @@ -39,7 +39,7 @@ public static implicit operator HslColor(in Color c) /// /// The HSL color to convert. /// A Unity representing the same visual color. - public static Color ToColor(in HslColor hsl) + public static Color ToColor(HslColor hsl) { if (hsl.s <= float.Epsilon) return new Color(MathUtilities.Clamp01(hsl.l), MathUtilities.Clamp01(hsl.l), MathUtilities.Clamp01(hsl.l), hsl.a); @@ -68,7 +68,7 @@ public static Color ToColor(in HslColor hsl) /// /// The Unity Color to convert. /// An representing the same visual color. - public static HslColor FromColor(in Color color) + public static HslColor FromColor(Color color) { var max = MathF.Max(color.r, MathF.Max(color.g, color.b)); var min = MathF.Min(color.r, MathF.Min(color.g, color.b)); @@ -104,7 +104,7 @@ public static HslColor FromColor(in Color color) /// The ending color. /// The interpolation value between the two colors, clamped to a 0 to 1 range. /// The interpolated . - public static HslColor Lerp(in HslColor a, in HslColor b, float t) + public static HslColor Lerp(HslColor a, HslColor b, float t) { return LerpUnclamped(a, b, MathUtilities.Clamp01(t)); } @@ -116,7 +116,7 @@ public static HslColor Lerp(in HslColor a, in HslColor b, float t) /// The ending color. /// The interpolation value between the two colors. /// The interpolated . - public static HslColor LerpUnclamped(in HslColor a, in HslColor b, float t) + public static HslColor LerpUnclamped(HslColor a, HslColor b, float t) { var hueDifference = MathUtilities.Repeat(b.h - a.h, 1f); diff --git a/MiraAPI/Colors/PaletteManager.cs b/MiraAPI/Colors/PaletteManager.cs index b552521a..5fc961a2 100644 --- a/MiraAPI/Colors/PaletteManager.cs +++ b/MiraAPI/Colors/PaletteManager.cs @@ -34,7 +34,7 @@ internal static void RegisterAllColors() Palette.TextColors = Palette.TextColors.ToArray().AddRangeToArray(colors); Palette.TextOutlineColors = Palette.TextOutlineColors.ToArray().AddRangeToArray(shadowColors); - for (var i = 0; i < originalLength + CustomColors.Count; i++) + for (var i = 0; i < CustomColors.Count; i++) ColorIdToColorMap[i + originalLength] = CustomColors[i]; } @@ -68,7 +68,7 @@ public static Color GetMainColor(int colorId) return !ColorIdToColorMap.TryGetValue(colorId, out var custom) ? Palette.PlayerColors[colorId] : (custom is VariableColor variable - ? variable.EvaluateMainColor() + ? variable.EvaluateMainColor(Time.time) : custom.InternalMainColor); } @@ -82,7 +82,7 @@ public static Color GetShadowColor(int colorId) return !ColorIdToColorMap.TryGetValue(colorId, out var custom) ? Palette.ShadowColors[colorId] : (custom is VariableColor variable - ? variable.EvaluateShadowColor() + ? variable.EvaluateShadowColor(Time.time) : custom.InternalShadowColor); } } diff --git a/MiraAPI/Colors/VariableColor.cs b/MiraAPI/Colors/VariableColor.cs index 2655ff50..93f736d2 100644 --- a/MiraAPI/Colors/VariableColor.cs +++ b/MiraAPI/Colors/VariableColor.cs @@ -48,22 +48,24 @@ public VariableColor(string name, Func mainEvaluator, Func /// Evaluates the dynamic main color based on the current in-game time. /// + /// The time to evaluate the color against. /// The dynamically evaluated color, or the static if static variant colors are enabled in the user's settings. - public Color EvaluateMainColor() + public Color EvaluateMainColor(float time) { return LocalSettingsTabSingleton.Instance.EnableVariableColors.Value - ? MainEvaluator(Time.time) + ? MainEvaluator(time) : InternalMainColor; } /// /// Evaluates the dynamic shadow color based on the current in-game time. /// + /// The time to evaluate the color against. /// The dynamically evaluated color, or the static if static variant colors are enabled in the user's settings. - public Color EvaluateShadowColor() + public Color EvaluateShadowColor(float time) { return LocalSettingsTabSingleton.Instance.EnableVariableColors.Value - ? ShadowEvaluator(Time.time) + ? ShadowEvaluator(time) : InternalShadowColor; } } diff --git a/MiraAPI/Colors/VariableColorBehaviour.cs b/MiraAPI/Colors/VariableColorBehaviour.cs index f33a12c0..2d6b6d0d 100644 --- a/MiraAPI/Colors/VariableColorBehaviour.cs +++ b/MiraAPI/Colors/VariableColorBehaviour.cs @@ -2,6 +2,7 @@ using Il2CppInterop.Runtime.Attributes; using MiraAPI.Colors; using MiraAPI.Utilities; +using Reactor.Utilities.Attributes; using UnityEngine; namespace MiraAPI; @@ -9,6 +10,7 @@ namespace MiraAPI; /// /// A mono script that adjusts the material colors of a component that it's attached to based on the provided variable color. /// +[RegisterInIl2Cpp] [SuppressMessage("Style", "IDE0051:Remove unused private members", Justification = "Unity methods.")] [SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "Unity fields.")] [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Read above.")] @@ -34,6 +36,7 @@ private void Awake() { rend = GetComponent(); material = rend.material; + material.SetColor(ShaderID.VisorColor, VisorColor); } private void Update() @@ -41,8 +44,8 @@ private void Update() if (!rend || color == null) return; - material.SetColor(ShaderID.BodyColor, color.EvaluateMainColor()); - material.SetColor(ShaderID.BackColor, color.EvaluateShadowColor()); - material.SetColor(ShaderID.VisorColor, VisorColor); + var time = Time.time; + material.SetColor(ShaderID.BodyColor, color.EvaluateMainColor(time)); + material.SetColor(ShaderID.BackColor, color.EvaluateShadowColor(time)); } } diff --git a/MiraAPI/Hud/CustomPaginableMenu.cs b/MiraAPI/Hud/CustomPaginableMenu.cs index 27f7819f..c000fd84 100644 --- a/MiraAPI/Hud/CustomPaginableMenu.cs +++ b/MiraAPI/Hud/CustomPaginableMenu.cs @@ -5,6 +5,7 @@ using System.Linq; using MiraAPI.Patches.Stubs; using MiraAPI.Translation; +using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; using Reactor.Utilities; using Reactor.Utilities.Extensions; @@ -248,8 +249,7 @@ protected void EnsureSearchUi() var wikiClickSound = HudManager.Instance?.MapButton?.ClickSound; - var searchFocusButton = searchTextbox.gameObject.GetComponent() - ?? searchTextbox.gameObject.AddComponent(); + var searchFocusButton = searchTextbox.gameObject.GetOrAddComponent(); if (wikiClickSound != null) { searchFocusButton.ClickSound = wikiClickSound; diff --git a/MiraAPI/Patches/Hud/VariableColorPatches.cs b/MiraAPI/Patches/Hud/VariableColorPatches.cs index 628a1f4f..3b52b7b7 100644 --- a/MiraAPI/Patches/Hud/VariableColorPatches.cs +++ b/MiraAPI/Patches/Hud/VariableColorPatches.cs @@ -3,6 +3,8 @@ using AmongUs.Data; using HarmonyLib; using MiraAPI.Colors; +using MiraAPI.Utilities; +using Reactor.Utilities.Extensions; using UnityEngine; namespace MiraAPI.Patches.Hud; @@ -13,18 +15,17 @@ internal static class SetPlayerMaterialPatch [HarmonyPatch(nameof(PlayerMaterial.SetColors), typeof(int), typeof(Renderer))] public static bool Prefix(int colorId, Renderer rend) { - var r = rend.gameObject.GetComponent() - ?? rend.gameObject.AddComponent(); - r.SetColor(PaletteManager.IsVariable(colorId) ? (VariableColor)PaletteManager.ColorIdToColorMap[colorId] : null); + rend.gameObject.GetOrAddComponent().SetColor( + PaletteManager.ColorIdToColorMap.TryGetValue(colorId, out var color) && color is VariableColor variable + ? variable + : null); return !PaletteManager.IsVariable(colorId); } [HarmonyPatch(nameof(PlayerMaterial.SetColors), typeof(Color), typeof(Renderer))] public static void Prefix(Renderer rend) { - var r = rend.gameObject.GetComponent() - ?? rend.gameObject.AddComponent(); - r.SetColor(null); + rend.gameObject.GetOrAddComponent().SetColor(null); } } @@ -48,7 +49,7 @@ public static void Postfix(ChatNotification __instance) if (!__instance.gameObject.active || !PaletteManager.IsVariable(__instance.player.cosmetics.ColorId)) return; - var str = ColorUtility.ToHtmlStringRGB(PaletteManager.GetMainColor(__instance.player.cosmetics.ColorId)); + var str = PaletteManager.GetMainColor(__instance.player.cosmetics.ColorId).ToHtmlStringRGBA(); __instance.playerNameText.text = "" + __instance.playerNameText.text.WithoutRichText(); } @@ -71,9 +72,9 @@ public static void Postfix(HostInfoPanel __instance) } var host = GameData.Instance.GetHost(); - var text = ColorUtility.ToHtmlStringRGB(PaletteManager.GetMainColor(__instance.player.cosmetics.ColorId)); - __instance.hostLabel.text = - TranslationController.Instance.GetString(StringNames.HostNounLabel); + var text = PaletteManager.GetMainColor(__instance.player.cosmetics.ColorId).ToHtmlStringRGBA(); + __instance.hostLabel.text = TranslationController.Instance.GetString(StringNames.HostNounLabel); + if (__instance.ShouldBoldenHostLabel(DataManager.Settings.Language.CurrentLanguage)) { __instance.hostLabel.text = __instance.hostLabel.text.Insert(0, ""); diff --git a/MiraAPI/PluginLoading/MiraPluginManager.cs b/MiraAPI/PluginLoading/MiraPluginManager.cs index 9058eac7..b46d1554 100644 --- a/MiraAPI/PluginLoading/MiraPluginManager.cs +++ b/MiraAPI/PluginLoading/MiraPluginManager.cs @@ -404,7 +404,7 @@ private static void RegisterColorClasses(Type type) foreach (var property in type.GetProperties()) { - if (property.PropertyType.IsAssignableTo(typeof(CustomColor))) + if (!property.PropertyType.IsAssignableTo(typeof(CustomColor))) { continue; } diff --git a/MiraAPI/Utilities/Extensions.cs b/MiraAPI/Utilities/Extensions.cs index 9017d13b..c3874e1a 100644 --- a/MiraAPI/Utilities/Extensions.cs +++ b/MiraAPI/Utilities/Extensions.cs @@ -871,4 +871,17 @@ public static InputAction RegisterModBind(this UserData userData, string id, str userData.joystickMaps[0].actionElementMaps.Add(map); return action; } + + /// + /// Gets a component that is attached to the provided , attaching and returning the component if it wasn't found. + /// + /// The component type. + /// The to get the component from. + /// The component. + public static T GetOrAddComponent(this GameObject go) + where T : Component + { + var comp = go.GetComponent(); + return comp ? comp : go.AddComponent(); + } } From f3ddf8d2abed04b1af275ea53d6e53bdf5a0d375 Mon Sep 17 00:00:00 2001 From: Az <56699741+AlchlcDvl@users.noreply.github.com> Date: Wed, 30 Sep 2026 10:00:26 -0400 Subject: [PATCH 4/7] squash final bugs --- MiraAPI/Colors/CustomColor.cs | 2 +- MiraAPI/Colors/VariableColorBehaviour.cs | 5 ++--- MiraAPI/Patches/Hud/VariableColorPatches.cs | 10 +++------- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/MiraAPI/Colors/CustomColor.cs b/MiraAPI/Colors/CustomColor.cs index a49e2337..ea54e720 100644 --- a/MiraAPI/Colors/CustomColor.cs +++ b/MiraAPI/Colors/CustomColor.cs @@ -11,8 +11,8 @@ namespace MiraAPI.Colors; /// The name of the option. /// The main color. /// The shadow color. -[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1513:Closing brace should be followed by blank line", Justification = "Looks weird.")] [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Internal code.")] +[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1513:Closing brace should be followed by blank line", Justification = "Looks weird.")] public class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor) { /// diff --git a/MiraAPI/Colors/VariableColorBehaviour.cs b/MiraAPI/Colors/VariableColorBehaviour.cs index 2d6b6d0d..dbc2e67d 100644 --- a/MiraAPI/Colors/VariableColorBehaviour.cs +++ b/MiraAPI/Colors/VariableColorBehaviour.cs @@ -17,7 +17,6 @@ namespace MiraAPI; public sealed class VariableColorBehaviour : MonoBehaviour { private SpriteRenderer rend; - private Material material; private VariableColor? color; private static readonly Color VisorColor = Palette.VisorColor; @@ -35,8 +34,7 @@ public void SetColor(VariableColor? col) private void Awake() { rend = GetComponent(); - material = rend.material; - material.SetColor(ShaderID.VisorColor, VisorColor); + rend.material.SetColor(ShaderID.VisorColor, VisorColor); } private void Update() @@ -45,6 +43,7 @@ private void Update() return; var time = Time.time; + var material = rend.material; material.SetColor(ShaderID.BodyColor, color.EvaluateMainColor(time)); material.SetColor(ShaderID.BackColor, color.EvaluateShadowColor(time)); } diff --git a/MiraAPI/Patches/Hud/VariableColorPatches.cs b/MiraAPI/Patches/Hud/VariableColorPatches.cs index 3b52b7b7..9bf9e32c 100644 --- a/MiraAPI/Patches/Hud/VariableColorPatches.cs +++ b/MiraAPI/Patches/Hud/VariableColorPatches.cs @@ -44,19 +44,15 @@ public static void Postfix(PlayerTab __instance) [HarmonyPatch(typeof(ChatNotification), nameof(ChatNotification.Update))] internal static class ChatNotifRainbowPatch { + private static readonly Regex RichTags = new(@"<[^>]*>", default, Regex.InfiniteMatchTimeout); + public static void Postfix(ChatNotification __instance) { if (!__instance.gameObject.active || !PaletteManager.IsVariable(__instance.player.cosmetics.ColorId)) return; var str = PaletteManager.GetMainColor(__instance.player.cosmetics.ColorId).ToHtmlStringRGBA(); - __instance.playerNameText.text = "" + __instance.playerNameText.text.WithoutRichText(); - } - - private static string WithoutRichText(this string text) - { - var richTagRegex = new Regex(@"<[^>]*>", default, Regex.InfiniteMatchTimeout); - return richTagRegex.Replace(text, string.Empty); + __instance.playerNameText.text = "" + RichTags.Replace(__instance.playerNameText.text, string.Empty); } } From cd753d554b679ff8a3eb39014a680db64d60bab6 Mon Sep 17 00:00:00 2001 From: Az <56699741+AlchlcDvl@users.noreply.github.com> Date: Wed, 30 Sep 2026 10:28:26 -0400 Subject: [PATCH 5/7] fix naming it originally was called "VariantColor" before i switched it to "VariableColor" --- MiraAPI/Colors/PaletteManager.cs | 2 +- MiraAPI/Colors/VariableColor.cs | 4 ++-- MiraAPI/Colors/VariableColorBehaviour.cs | 4 ++-- MiraAPI/MiraApiSettings.cs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/MiraAPI/Colors/PaletteManager.cs b/MiraAPI/Colors/PaletteManager.cs index 5fc961a2..286cd258 100644 --- a/MiraAPI/Colors/PaletteManager.cs +++ b/MiraAPI/Colors/PaletteManager.cs @@ -39,7 +39,7 @@ internal static void RegisterAllColors() } /// - /// Checks to see if the provided color ID belongs to a variant color. + /// Checks to see if the provided color ID belongs to a variable color. /// /// The id of the color. /// true if the color is a variable color; false otherwise. diff --git a/MiraAPI/Colors/VariableColor.cs b/MiraAPI/Colors/VariableColor.cs index 93f736d2..44f2408f 100644 --- a/MiraAPI/Colors/VariableColor.cs +++ b/MiraAPI/Colors/VariableColor.cs @@ -49,7 +49,7 @@ public VariableColor(string name, Func mainEvaluator, Func /// The time to evaluate the color against. - /// The dynamically evaluated color, or the static if static variant colors are enabled in the user's settings. + /// The dynamically evaluated color, or the static if static variable colors are enabled in the user's settings. public Color EvaluateMainColor(float time) { return LocalSettingsTabSingleton.Instance.EnableVariableColors.Value @@ -61,7 +61,7 @@ public Color EvaluateMainColor(float time) /// Evaluates the dynamic shadow color based on the current in-game time. /// /// The time to evaluate the color against. - /// The dynamically evaluated color, or the static if static variant colors are enabled in the user's settings. + /// The dynamically evaluated color, or the static if static variable colors are enabled in the user's settings. public Color EvaluateShadowColor(float time) { return LocalSettingsTabSingleton.Instance.EnableVariableColors.Value diff --git a/MiraAPI/Colors/VariableColorBehaviour.cs b/MiraAPI/Colors/VariableColorBehaviour.cs index dbc2e67d..d827e61d 100644 --- a/MiraAPI/Colors/VariableColorBehaviour.cs +++ b/MiraAPI/Colors/VariableColorBehaviour.cs @@ -22,9 +22,9 @@ public sealed class VariableColorBehaviour : MonoBehaviour private static readonly Color VisorColor = Palette.VisorColor; /// - /// Sets the current variant color. + /// Sets the current variable color. /// - /// The new variant color. + /// The new variable color. [HideFromIl2Cpp] public void SetColor(VariableColor? col) { diff --git a/MiraAPI/MiraApiSettings.cs b/MiraAPI/MiraApiSettings.cs index ca448b0f..bb6ed512 100644 --- a/MiraAPI/MiraApiSettings.cs +++ b/MiraAPI/MiraApiSettings.cs @@ -235,7 +235,7 @@ public static void ResizeUI(float scaleFactor) public ConfigEntry EnableSabotageBlares { get; private set; } = config.Bind("MiraApi.Accessibility", "MiraApi.EnableSabotageBlare", true); /// - /// Gets whether variant colors will remain static or not. + /// Gets whether variable colors will remain static or not. /// [LocalToggleSetting] public ConfigEntry EnableVariableColors { get; private set; } = config.Bind("MiraApi.Accessibility", "MiraApi.EnableVariableColors", true); From c2c7c63b9ba17f13664dcd928149f5276e3ea93e Mon Sep 17 00:00:00 2001 From: Az <56699741+AlchlcDvl@users.noreply.github.com> Date: Wed, 30 Sep 2026 19:02:40 -0400 Subject: [PATCH 6/7] correct documentation --- MiraAPI/Colors/VariableColor.cs | 8 ++++---- MiraAPI/Patches/.editorconfig | 5 +---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/MiraAPI/Colors/VariableColor.cs b/MiraAPI/Colors/VariableColor.cs index 44f2408f..264b06e3 100644 --- a/MiraAPI/Colors/VariableColor.cs +++ b/MiraAPI/Colors/VariableColor.cs @@ -18,12 +18,12 @@ public sealed class VariableColor(StringNames name, Func mainEvalu : CustomColor(name, mainColor ?? mainEvaluator(0f), shadowColor ?? shadowEvaluator?.Invoke(0f) ?? (mainColor ?? mainEvaluator(0f)).GetShadowColor(60)) { /// - /// Gets or sets a delegate that takes a normalized time value and returns the dynamic main color. + /// Gets or sets a delegate that takes in a time value and returns the dynamic main color. /// public Func MainEvaluator { get; set; } = mainEvaluator; /// - /// Gets or sets a delegate that takes a normalized time value and returns the dynamic shadow color. + /// Gets or sets a delegate that takes in a time value and returns the dynamic shadow color. /// public Func ShadowEvaluator { get; set; } = shadowEvaluator ?? (t => mainEvaluator(t).GetShadowColor(0.24f)); @@ -46,7 +46,7 @@ public VariableColor(string name, Func mainEvaluator, Func - /// Evaluates the dynamic main color based on the current in-game time. + /// Evaluates the dynamic main color based on the provided time value. /// /// The time to evaluate the color against. /// The dynamically evaluated color, or the static if static variable colors are enabled in the user's settings. @@ -58,7 +58,7 @@ public Color EvaluateMainColor(float time) } /// - /// Evaluates the dynamic shadow color based on the current in-game time. + /// Evaluates the dynamic shadow color based on the provided time value. /// /// The time to evaluate the color against. /// The dynamically evaluated color, or the static if static variable colors are enabled in the user's settings. diff --git a/MiraAPI/Patches/.editorconfig b/MiraAPI/Patches/.editorconfig index 2622fc75..d345b9eb 100644 --- a/MiraAPI/Patches/.editorconfig +++ b/MiraAPI/Patches/.editorconfig @@ -10,7 +10,4 @@ dotnet_diagnostic.CS1591.severity = none dotnet_diagnostic.SA1611.severity = none # SA1615: Element return value should be documented. -dotnet_diagnostic.SA1615.severity = none - -# S125: Sections of code should not be commented out. -dotnet_diagnostic.S125.severity = none \ No newline at end of file +dotnet_diagnostic.SA1615.severity = none \ No newline at end of file From 2e212dee47ecfd2ce3f09196b3771c357d419bf6 Mon Sep 17 00:00:00 2001 From: Az <56699741+AlchlcDvl@users.noreply.github.com> Date: Thu, 1 Oct 2026 01:50:39 -0400 Subject: [PATCH 7/7] bounds checks --- .editorconfig | 3 +++ MiraAPI/Colors/PaletteManager.cs | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/.editorconfig b/.editorconfig index 01e10b81..1691a662 100644 --- a/.editorconfig +++ b/.editorconfig @@ -95,3 +95,6 @@ dotnet_diagnostic.S1244.severity = none # IDE0045: Convert to conditional expression dotnet_diagnostic.IDE0045.severity = none + +# IDE0046: Convert to conditional expression +dotnet_diagnostic.IDE0046.severity = none diff --git a/MiraAPI/Colors/PaletteManager.cs b/MiraAPI/Colors/PaletteManager.cs index 286cd258..9127bb62 100644 --- a/MiraAPI/Colors/PaletteManager.cs +++ b/MiraAPI/Colors/PaletteManager.cs @@ -45,6 +45,9 @@ internal static void RegisterAllColors() /// true if the color is a variable color; false otherwise. public static bool IsVariable(int colorId) { + if (colorId < 0 || colorId >= Palette.ColorNames.Length) + return false; + return VariableColorNames.Contains(Palette.ColorNames[colorId]); } @@ -65,6 +68,9 @@ public static bool IsCustomColor(int colorId) /// The main color associated with the provided ID. public static Color GetMainColor(int colorId) { + if (colorId < 0 || colorId >= Palette.ColorNames.Length) + return Color.white; + return !ColorIdToColorMap.TryGetValue(colorId, out var custom) ? Palette.PlayerColors[colorId] : (custom is VariableColor variable @@ -79,6 +85,9 @@ public static Color GetMainColor(int colorId) /// The shadow color associated with the provided ID. public static Color GetShadowColor(int colorId) { + if (colorId < 0 || colorId >= Palette.ColorNames.Length) + return Color.white; + return !ColorIdToColorMap.TryGetValue(colorId, out var custom) ? Palette.ShadowColors[colorId] : (custom is VariableColor variable