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 01/12] 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 af9feb4..c0529a8 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 0000000..569e848
--- /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 0000000..4dec19a
--- /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 0000000..824a032
--- /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 a778e31..2c60dd5 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 fe7e725..50a0ed0 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 @@ public static class PaletteManager
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 0000000..0206423
--- /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 0000000..1b7efda
--- /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 1560955..ca448b0 100644
--- a/MiraAPI/MiraApiSettings.cs
+++ b/MiraAPI/MiraApiSettings.cs
@@ -233,4 +233,10 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
///
[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 0000000..628a1f4
--- /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 a3b5158..9058eac 100644
--- a/MiraAPI/PluginLoading/MiraPluginManager.cs
+++ b/MiraAPI/PluginLoading/MiraPluginManager.cs
@@ -404,7 +404,7 @@ public sealed class MiraPluginManager
foreach (var property in type.GetProperties())
{
- if (property.PropertyType != typeof(CustomColor))
+ if (property.PropertyType.IsAssignableTo(typeof(CustomColor)))
{
continue;
}
@@ -416,6 +416,9 @@ public sealed class MiraPluginManager
}
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 7cd2c1c..be53733 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 02/12] 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 c0529a8..01e10b8 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 cde5aae..1a5efec 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 569e848..268dc81 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 record struct CmykColor(float c, float m, float y, float k, float a = 1f)
/// 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 record struct CmykColor(float c, float m, float y, float k, float a = 1f)
///
/// 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 record struct CmykColor(float c, float m, float y, float k, float a = 1f)
///
/// 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 record struct CmykColor(float c, float m, float y, float k, float a = 1f)
/// 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 record struct CmykColor(float c, float m, float y, float k, float a = 1f)
/// 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 4dec19a..160ae85 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 record struct HsbColor(float h, float s, float b, float a = 1f)
/// 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 record struct HsbColor(float h, float s, float b, float a = 1f)
///
/// 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 record struct HsbColor(float h, float s, float b, float a = 1f)
///
/// 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 record struct HsbColor(float h, float s, float b, float a = 1f)
/// 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 record struct HsbColor(float h, float s, float b, float a = 1f)
/// 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 824a032..5ff7077 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 record struct HslColor(float h, float s, float l, float a = 1f)
/// 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 record struct HslColor(float h, float s, float l, float a = 1f)
///
/// 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 record struct HslColor(float h, float s, float l, float a = 1f)
< 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 record struct HslColor(float h, float s, float l, float a = 1f)
///
/// 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 record struct HslColor(float h, float s, float l, float a = 1f)
/// 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 record struct HslColor(float h, float s, float l, float a = 1f)
/// 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 2c60dd5..a49e233 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 50a0ed0..b552521 100644
--- a/MiraAPI/Colors/PaletteManager.cs
+++ b/MiraAPI/Colors/PaletteManager.cs
@@ -63,13 +63,13 @@ public static class PaletteManager
///
/// 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 class PaletteManager
///
/// 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 0206423..2655ff5 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 sealed class VariableColor(StringNames name, Func mainEva
/// 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()
+ 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 1b7efda..f33a12c 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 @@ public sealed class VariableColorBehaviour : MonoBehaviour
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 57cd64b..9017d13 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -620,7 +620,23 @@ public static class Extensions
(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 class Extensions
}
///
- /// 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 0000000..a505a36
--- /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 03/12] 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 268dc81..c94ebec 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 record struct CmykColor(float c, float m, float y, float k, float a = 1f)
/// 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 record struct CmykColor(float c, float m, float y, float k, float a = 1f)
///
/// 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 record struct CmykColor(float c, float m, float y, float k, float a = 1f)
///
/// 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 record struct CmykColor(float c, float m, float y, float k, float a = 1f)
/// 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 record struct CmykColor(float c, float m, float y, float k, float a = 1f)
/// 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 160ae85..b566333 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 record struct HsbColor(float h, float s, float b, float a = 1f)
/// 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 record struct HsbColor(float h, float s, float b, float a = 1f)
///
/// 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 record struct HsbColor(float h, float s, float b, float a = 1f)
///
/// 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 record struct HsbColor(float h, float s, float b, float a = 1f)
/// 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 record struct HsbColor(float h, float s, float b, float a = 1f)
/// 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 5ff7077..1929e77 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 record struct HslColor(float h, float s, float l, float a = 1f)
/// 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 record struct HslColor(float h, float s, float l, float a = 1f)
///
/// 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 record struct HslColor(float h, float s, float l, float a = 1f)
///
/// 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 record struct HslColor(float h, float s, float l, float a = 1f)
/// 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 record struct HslColor(float h, float s, float l, float a = 1f)
/// 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 b552521..5fc961a 100644
--- a/MiraAPI/Colors/PaletteManager.cs
+++ b/MiraAPI/Colors/PaletteManager.cs
@@ -34,7 +34,7 @@ public static class PaletteManager
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 class PaletteManager
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 class PaletteManager
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 2655ff5..93f736d 100644
--- a/MiraAPI/Colors/VariableColor.cs
+++ b/MiraAPI/Colors/VariableColor.cs
@@ -48,22 +48,24 @@ public sealed class VariableColor(StringNames name, Func mainEvalu
///
/// 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 f33a12c..2d6b6d0 100644
--- a/MiraAPI/Colors/VariableColorBehaviour.cs
+++ b/MiraAPI/Colors/VariableColorBehaviour.cs
@@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
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 @@ public sealed class VariableColorBehaviour : MonoBehaviour
{
rend = GetComponent();
material = rend.material;
+ material.SetColor(ShaderID.VisorColor, VisorColor);
}
private void Update()
@@ -41,8 +44,8 @@ public sealed class VariableColorBehaviour : MonoBehaviour
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 27f7819..c000fd8 100644
--- a/MiraAPI/Hud/CustomPaginableMenu.cs
+++ b/MiraAPI/Hud/CustomPaginableMenu.cs
@@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis;
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 @@ public abstract class CustomPaginableMenu : CustomPhoneMenu()
- ?? 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 628a1f4..3b52b7b 100644
--- a/MiraAPI/Patches/Hud/VariableColorPatches.cs
+++ b/MiraAPI/Patches/Hud/VariableColorPatches.cs
@@ -3,6 +3,8 @@ using System.Text.RegularExpressions;
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 @@ internal static class ChatNotifRainbowPatch
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 @@ internal static class RainbowLobbyInfoPanePatch
}
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 9058eac..b46d155 100644
--- a/MiraAPI/PluginLoading/MiraPluginManager.cs
+++ b/MiraAPI/PluginLoading/MiraPluginManager.cs
@@ -404,7 +404,7 @@ public sealed class MiraPluginManager
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 9017d13..c3874e1 100644
--- a/MiraAPI/Utilities/Extensions.cs
+++ b/MiraAPI/Utilities/Extensions.cs
@@ -871,4 +871,17 @@ public static class Extensions
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 04/12] 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 a49e233..ea54e72 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 2d6b6d0..dbc2e67 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 sealed class VariableColorBehaviour : MonoBehaviour
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 @@ public sealed class VariableColorBehaviour : MonoBehaviour
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 3b52b7b..9bf9e32 100644
--- a/MiraAPI/Patches/Hud/VariableColorPatches.cs
+++ b/MiraAPI/Patches/Hud/VariableColorPatches.cs
@@ -44,19 +44,15 @@ internal static class PlayerTabPatch
[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 05/12] 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 5fc961a..286cd25 100644
--- a/MiraAPI/Colors/PaletteManager.cs
+++ b/MiraAPI/Colors/PaletteManager.cs
@@ -39,7 +39,7 @@ public static class PaletteManager
}
///
- /// 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 93f736d..44f2408 100644
--- a/MiraAPI/Colors/VariableColor.cs
+++ b/MiraAPI/Colors/VariableColor.cs
@@ -49,7 +49,7 @@ public sealed class VariableColor(StringNames name, Func mainEvalu
/// 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.
+ /// 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 sealed class VariableColor(StringNames name, Func mainEvalu
/// 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 dbc2e67..d827e61 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 ca448b0..bb6ed51 100644
--- a/MiraAPI/MiraApiSettings.cs
+++ b/MiraAPI/MiraApiSettings.cs
@@ -235,7 +235,7 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
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 06/12] 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 44f2408..264b06e 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 sealed class VariableColor(StringNames name, Func mainEvalu
}
///
- /// 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 sealed class VariableColor(StringNames name, Func mainEvalu
}
///
- /// 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 2622fc7..d345b9e 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 07/12] bounds checks
---
.editorconfig | 3 +++
MiraAPI/Colors/PaletteManager.cs | 9 +++++++++
2 files changed, 12 insertions(+)
diff --git a/.editorconfig b/.editorconfig
index 01e10b8..1691a66 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 286cd25..9127bb6 100644
--- a/MiraAPI/Colors/PaletteManager.cs
+++ b/MiraAPI/Colors/PaletteManager.cs
@@ -45,6 +45,9 @@ public static class PaletteManager
/// 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 class PaletteManager
/// 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 class PaletteManager
/// 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
From 85f8da54dd6753f6586a8e4d18b94eb1fe0dff7c Mon Sep 17 00:00:00 2001
From: Sameer Umar <72575280+XtraCube@users.noreply.github.com>
Date: Thu, 1 Oct 2026 23:07:12 -0400
Subject: [PATCH 08/12] Update README with new badge and language links
---
README.md | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 84823f4..28d32bd 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,5 @@
-[](https://discord.gg/AEfHJGwggC)
-
-[English] | [简体中文](Translated/README_zh.md)
+[](https://starlight.allofus.dev/mods/mira.api)
+[](https://discord.gg/AEfHJGwggC)
> This mod is not affiliated with Among Us or Innersloth LLC, and the content contained therein is not endorsed or otherwise sponsored by Innersloth LLC. Portions of the materials contained herein are property of Innersloth LLC. © Innersloth LLC.
From 5303f09627682a18ac823695f7a879acc5a9b3e4 Mon Sep 17 00:00:00 2001
From: Sameer Umar <72575280+XtraCube@users.noreply.github.com>
Date: Thu, 1 Oct 2026 23:07:47 -0400
Subject: [PATCH 09/12] Add language links and badges to README
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index 28d32bd..59dfa6e 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
+[English] | [简体中文](Translated/README_zh.md)
+
[](https://starlight.allofus.dev/mods/mira.api)
[](https://discord.gg/AEfHJGwggC)
From 19dd1bb497b96ea57508d6d2d6ebb9dfc399293c Mon Sep 17 00:00:00 2001
From: Az <56699741+AlchlcDvl@users.noreply.github.com>
Date: Wed, 14 Oct 2026 00:18:56 -0400
Subject: [PATCH 10/12] fix mentioned issues + add more functionality
---
MiraAPI.Example/ExampleColors.cs | 2 +-
MiraAPI.Example/Resources/Locale/en_US.xml | 1 +
MiraAPI/Colors/CustomColor.cs | 5 ++
MiraAPI/Colors/PaletteManager.cs | 78 +++++++++++++---
MiraAPI/Colors/VariableColor.cs | 17 ++--
MiraAPI/Colors/VariableColorBehaviour.cs | 88 +++++++++++++++----
.../{Hud => Colors}/VariableColorPatches.cs | 22 +++--
7 files changed, 167 insertions(+), 46 deletions(-)
rename MiraAPI/Patches/{Hud => Colors}/VariableColorPatches.cs (83%)
diff --git a/MiraAPI.Example/ExampleColors.cs b/MiraAPI.Example/ExampleColors.cs
index 1a5efec..297a370 100644
--- a/MiraAPI.Example/ExampleColors.cs
+++ b/MiraAPI.Example/ExampleColors.cs
@@ -17,7 +17,7 @@ public static class ExampleColors
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))
+ public static VariableColor Rainbow { get; } = new("color.rainbow", t => new HsbColor(MathUtilities.PingPong(t, 1f, 0.3f), 1f, 1f))
{
ColorBrightness = CustomColorBrightness.Lighter,
};
diff --git a/MiraAPI.Example/Resources/Locale/en_US.xml b/MiraAPI.Example/Resources/Locale/en_US.xml
index e370dc3..d44100c 100644
--- a/MiraAPI.Example/Resources/Locale/en_US.xml
+++ b/MiraAPI.Example/Resources/Locale/en_US.xml
@@ -80,4 +80,5 @@ Eliminate every other faction.
Cerulean
Rose
Gold
+ Rainbow
diff --git a/MiraAPI/Colors/CustomColor.cs b/MiraAPI/Colors/CustomColor.cs
index ea54e72..db91356 100644
--- a/MiraAPI/Colors/CustomColor.cs
+++ b/MiraAPI/Colors/CustomColor.cs
@@ -80,6 +80,11 @@ public class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColo
///
public enum CustomColorBrightness
{
+ ///
+ /// An invalid color.
+ ///
+ Invalid = -1,
+
///
/// A lighter color.
///
diff --git a/MiraAPI/Colors/PaletteManager.cs b/MiraAPI/Colors/PaletteManager.cs
index 9127bb6..3b9cd4a 100644
--- a/MiraAPI/Colors/PaletteManager.cs
+++ b/MiraAPI/Colors/PaletteManager.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using HarmonyLib;
using UnityEngine;
@@ -17,25 +18,45 @@ public static class PaletteManager
internal static readonly List CustomColors = [];
internal static readonly HashSet VariableColorNames = [];
- internal static readonly Dictionary ColorIdToColorMap = [];
+
+ private static readonly CustomColorBrightness[] ColorBrightnessMap =
+ [
+ CustomColorBrightness.Darker, // Red
+ CustomColorBrightness.Darker, // Blue
+ CustomColorBrightness.Darker, // Green
+ CustomColorBrightness.Lighter, // Pink
+ CustomColorBrightness.Lighter, // Orange
+ CustomColorBrightness.Lighter, // Yellow
+ CustomColorBrightness.Darker, // Black
+ CustomColorBrightness.Lighter, // White
+ CustomColorBrightness.Darker, // Purple
+ CustomColorBrightness.Darker, // Brown
+ CustomColorBrightness.Lighter, // Cyan
+ CustomColorBrightness.Lighter, // Lime
+ CustomColorBrightness.Darker, // Maroon
+ CustomColorBrightness.Lighter, // Rose
+ CustomColorBrightness.Lighter, // Banana
+ CustomColorBrightness.Darker, // Gray
+ CustomColorBrightness.Darker, // Tan
+ CustomColorBrightness.Lighter, // Coral
+ ];
+
+ private static int originalLength;
internal static void RegisterAllColors()
{
+ originalLength = Palette.PlayerColors.Length;
+
var colors = CustomColors.Select(x => x.MainColor).ToArray();
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 < CustomColors.Count; i++)
- ColorIdToColorMap[i + originalLength] = CustomColors[i];
}
///
@@ -45,7 +66,7 @@ public static class PaletteManager
/// true if the color is a variable color; false otherwise.
public static bool IsVariable(int colorId)
{
- if (colorId < 0 || colorId >= Palette.ColorNames.Length)
+ if ((uint)colorId >= (uint)Palette.ColorNames.Length)
return false;
return VariableColorNames.Contains(Palette.ColorNames[colorId]);
@@ -54,11 +75,11 @@ public static class PaletteManager
///
/// Checks to see if the provided color ID belongs to a custom color.
///
- /// The id of the 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);
+ return (uint)(colorId - originalLength) < (uint)CustomColors.Count;
}
///
@@ -68,10 +89,10 @@ public static class PaletteManager
/// The main color associated with the provided ID.
public static Color GetMainColor(int colorId)
{
- if (colorId < 0 || colorId >= Palette.ColorNames.Length)
+ if ((uint)colorId >= (uint)Palette.ColorNames.Length)
return Color.white;
- return !ColorIdToColorMap.TryGetValue(colorId, out var custom)
+ return !TryGetCustomColor(colorId, out var custom)
? Palette.PlayerColors[colorId]
: (custom is VariableColor variable
? variable.EvaluateMainColor(Time.time)
@@ -81,17 +102,46 @@ public static class PaletteManager
///
/// Gets the shadow color of the provided color ID.
///
- /// The id of the color.
+ /// The ID of the color.
/// The shadow color associated with the provided ID.
public static Color GetShadowColor(int colorId)
{
- if (colorId < 0 || colorId >= Palette.ColorNames.Length)
+ if ((uint)colorId >= (uint)Palette.ColorNames.Length)
return Color.white;
- return !ColorIdToColorMap.TryGetValue(colorId, out var custom)
+ return !TryGetCustomColor(colorId, out var custom)
? Palette.ShadowColors[colorId]
: (custom is VariableColor variable
? variable.EvaluateShadowColor(Time.time)
: custom.InternalShadowColor);
}
+
+ ///
+ /// Gets the brightness type of the color associated with the provided ID.
+ ///
+ /// The ID of the color.
+ /// The brightness type of the provided ID.
+ public static CustomColorBrightness GetBrightness(int colorId)
+ {
+ if ((uint)colorId >= (uint)Palette.ColorNames.Length)
+ return CustomColorBrightness.Invalid;
+
+ return TryGetCustomColor(colorId, out var custom)
+ ? custom.ColorBrightness
+ : ColorBrightnessMap[colorId];
+ }
+
+ internal static bool TryGetCustomColor(int colorId, [NotNullWhen(true)] out CustomColor? custom)
+ {
+ var index = colorId - originalLength;
+
+ if ((uint)index >= (uint)CustomColors.Count)
+ {
+ custom = null;
+ return false;
+ }
+
+ custom = CustomColors[index];
+ return true;
+ }
}
diff --git a/MiraAPI/Colors/VariableColor.cs b/MiraAPI/Colors/VariableColor.cs
index 264b06e..1ca610f 100644
--- a/MiraAPI/Colors/VariableColor.cs
+++ b/MiraAPI/Colors/VariableColor.cs
@@ -14,8 +14,9 @@ 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)
- : CustomColor(name, mainColor ?? mainEvaluator(0f), shadowColor ?? shadowEvaluator?.Invoke(0f) ?? (mainColor ?? mainEvaluator(0f)).GetShadowColor(60))
+/// The default time that is used to automatically fill in the static colors when the latter are not provided.
+public sealed class VariableColor(StringNames name, Func mainEvaluator, Func? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null, float defaultTime = 0f)
+ : CustomColor(name, mainColor ?? mainEvaluator(defaultTime), shadowColor ?? shadowEvaluator?.Invoke(defaultTime) ?? (mainColor ?? mainEvaluator(defaultTime)).GetShadowColor(60))
{
///
/// Gets or sets a delegate that takes in a time value and returns the dynamic main color.
@@ -28,20 +29,20 @@ public sealed class VariableColor(StringNames name, Func mainEvalu
public Func ShadowEvaluator { get; set; } = shadowEvaluator ?? (t => mainEvaluator(t).GetShadowColor(0.24f));
///
- public VariableColor(StringNames name, Func mainEvaluator, Color32? mainColor = null)
- : this(name, mainEvaluator, null, mainColor, null)
+ public VariableColor(StringNames name, Func mainEvaluator, Color32? mainColor = null, float defaultTime = 0f)
+ : this(name, mainEvaluator, null, mainColor, null, defaultTime)
{
}
///
- public VariableColor(string name, Func mainEvaluator, Color32? mainColor = null)
- : this(name, mainEvaluator, null, mainColor, null)
+ public VariableColor(string name, Func mainEvaluator, Color32? mainColor = null, float defaultTime = 0f)
+ : this(name, mainEvaluator, null, mainColor, null, defaultTime)
{
}
///
- public VariableColor(string name, Func mainEvaluator, Func? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null)
- : this(MiraLocaleManager.GetOrCreateLocaleString(name), mainEvaluator, shadowEvaluator, mainColor, shadowColor)
+ public VariableColor(string name, Func mainEvaluator, Func? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null, float defaultTime = 0f)
+ : this(MiraLocaleManager.GetOrCreateLocaleString(name), mainEvaluator, shadowEvaluator, mainColor, shadowColor, defaultTime)
{
}
diff --git a/MiraAPI/Colors/VariableColorBehaviour.cs b/MiraAPI/Colors/VariableColorBehaviour.cs
index d827e61..e0d4cea 100644
--- a/MiraAPI/Colors/VariableColorBehaviour.cs
+++ b/MiraAPI/Colors/VariableColorBehaviour.cs
@@ -8,43 +8,95 @@ 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.
+/// The base class for variable color behaviour on s.
///
-[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.")]
-public sealed class VariableColorBehaviour : MonoBehaviour
+public abstract class BaseVariableColorBehaviour : MonoBehaviour
{
- private SpriteRenderer rend;
- private VariableColor? color;
-
- private static readonly Color VisorColor = Palette.VisorColor;
+ ///
+ /// The to apply.
+ ///
+ protected VariableColor? color;
///
/// Sets the current variable color.
///
- /// The new variable color.
+ /// The new .
[HideFromIl2Cpp]
- public void SetColor(VariableColor? col)
+ public void SetColor(VariableColor? newColor)
{
- color = col;
+ color = newColor;
+ }
+}
+
+///
+/// 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.")]
+public sealed class VariableColorBehaviour : BaseVariableColorBehaviour
+{
+ private Renderer? rend;
+
+ private static readonly Color VisorColor = Palette.VisorColor;
+
+ ///
+ /// Sets the new target.
+ ///
+ /// The new that the script must target.
+ public void SetRend(Renderer? newRend)
+ {
+ rend = newRend;
+ }
+
+ private void Awake()
+ {
+ rend = GetComponent();
+ }
+
+ private void Update()
+ {
+ if (color == null || rend == null)
+ return;
+
+ var time = Time.time;
+ var material = rend.material;
+
+ material.SetColor(ShaderID.VisorColor, VisorColor);
+
+ material.SetColor(ShaderID.BodyColor, color.EvaluateMainColor(time));
+ material.SetColor(ShaderID.BackColor, color.EvaluateShadowColor(time));
+ }
+}
+
+///
+/// A mono script that adjusts the graphic 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.")]
+public sealed class BasicVariableColorBehaviour : BaseVariableColorBehaviour
+{
+ private SpriteRenderer? rend;
+
+ ///
+ /// Sets the new target.
+ ///
+ /// The new that the script must target.
+ public void SetRend(SpriteRenderer? newRend)
+ {
+ rend = newRend;
}
private void Awake()
{
rend = GetComponent();
- rend.material.SetColor(ShaderID.VisorColor, VisorColor);
}
private void Update()
{
- if (!rend || color == null)
- return;
-
- var time = Time.time;
- var material = rend.material;
- material.SetColor(ShaderID.BodyColor, color.EvaluateMainColor(time));
- material.SetColor(ShaderID.BackColor, color.EvaluateShadowColor(time));
+ if (color != null && rend != null)
+ rend.color = color.EvaluateMainColor(Time.time);
}
}
diff --git a/MiraAPI/Patches/Hud/VariableColorPatches.cs b/MiraAPI/Patches/Colors/VariableColorPatches.cs
similarity index 83%
rename from MiraAPI/Patches/Hud/VariableColorPatches.cs
rename to MiraAPI/Patches/Colors/VariableColorPatches.cs
index 9bf9e32..cf64d6d 100644
--- a/MiraAPI/Patches/Hud/VariableColorPatches.cs
+++ b/MiraAPI/Patches/Colors/VariableColorPatches.cs
@@ -7,7 +7,7 @@ using MiraAPI.Utilities;
using Reactor.Utilities.Extensions;
using UnityEngine;
-namespace MiraAPI.Patches.Hud;
+namespace MiraAPI.Patches.Colors;
[HarmonyPatch(typeof(PlayerMaterial))]
internal static class SetPlayerMaterialPatch
@@ -16,7 +16,7 @@ internal static class SetPlayerMaterialPatch
public static bool Prefix(int colorId, Renderer rend)
{
rend.gameObject.GetOrAddComponent().SetColor(
- PaletteManager.ColorIdToColorMap.TryGetValue(colorId, out var color) && color is VariableColor variable
+ PaletteManager.TryGetCustomColor(colorId, out var color) && color is VariableColor variable
? variable
: null);
return !PaletteManager.IsVariable(colorId);
@@ -48,11 +48,19 @@ internal static class ChatNotifRainbowPatch
public static void Postfix(ChatNotification __instance)
{
- if (!__instance.gameObject.active || !PaletteManager.IsVariable(__instance.player.cosmetics.ColorId))
+ if (!__instance.gameObject.active)
return;
- var str = PaletteManager.GetMainColor(__instance.player.cosmetics.ColorId).ToHtmlStringRGBA();
- __instance.playerNameText.text = "" + RichTags.Replace(__instance.playerNameText.text, string.Empty);
+ var colorId = __instance.player.cosmetics.ColorId;
+
+ if (!PaletteManager.IsVariable(colorId))
+ return;
+
+ var color = PaletteManager.GetMainColor(colorId);
+ var str = color.ToHtmlStringRGBA();
+ var nameText = __instance.playerNameText;
+ nameText.text = "" + RichTags.Replace(nameText.text, string.Empty);
+ nameText.outlineColor = color.GetShadowColor(0.5f);
}
}
@@ -68,6 +76,10 @@ internal static class RainbowLobbyInfoPanePatch
}
var host = GameData.Instance.GetHost();
+
+ if (!host)
+ return;
+
var text = PaletteManager.GetMainColor(__instance.player.cosmetics.ColorId).ToHtmlStringRGBA();
__instance.hostLabel.text = TranslationController.Instance.GetString(StringNames.HostNounLabel);
From 50312e31c278d069ac87eae514c536dd4e2475bf Mon Sep 17 00:00:00 2001
From: Az <56699741+AlchlcDvl@users.noreply.github.com>
Date: Wed, 14 Oct 2026 00:48:08 -0400
Subject: [PATCH 11/12] resolve and suppress warnings
---
.editorconfig | 12 ++
MiraAPI.Example/.editorconfig | 4 +
MiraAPI.Example/Resources/Locale/en_US.xml | 12 +-
MiraAPI/GameModes/HideAndSeekMode.cs | 2 -
.../OptionTypes/ModdedPlayerOption.cs | 2 +-
MiraAPI/Hud/CustomPaginableMenu.cs | 1 -
MiraAPI/MiraApiPlugin.cs | 8 +-
MiraAPI/Patches/.editorconfig | 4 +-
.../Patches/Options/GameOptionsMenuPatch.cs | 6 +-
MiraAPI/Patches/Options/SettingPatches.cs | 4 +-
MiraAPI/Patches/RoleGuidePatches.cs | 126 +++++++-----------
MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs | 4 +-
MiraAPI/Presets/SavePresetPopup.cs | 1 +
MiraAPI/Roles/ICustomRole.cs | 9 +-
MiraAPI/Translation/MiraLocaleManager.cs | 2 -
MiraAPI/Utilities/ControllableComparer.cs | 2 -
MiraAPI/Utilities/Helpers.cs | 13 +-
17 files changed, 106 insertions(+), 106 deletions(-)
create mode 100644 MiraAPI.Example/.editorconfig
diff --git a/.editorconfig b/.editorconfig
index af9feb4..2f48813 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -86,3 +86,15 @@ dotnet_diagnostic.SA1133.severity = none
# IDE0001: Simplify Names
dotnet_diagnostic.IDE0001.severity = warning
+
+# IDE0079: Remove unnecessary suppression
+dotnet_diagnostic.IDE0079.severity = none
+
+# S3903: Types should be defined in named namespaces
+dotnet_diagnostic.S3903.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.Example/.editorconfig b/MiraAPI.Example/.editorconfig
new file mode 100644
index 0000000..171e612
--- /dev/null
+++ b/MiraAPI.Example/.editorconfig
@@ -0,0 +1,4 @@
+[*.{cs,vb}]
+
+# SA1602: Enumeration items should be documented.
+dotnet_diagnostic.SA1602.severity = none
diff --git a/MiraAPI.Example/Resources/Locale/en_US.xml b/MiraAPI.Example/Resources/Locale/en_US.xml
index e370dc3..56f74c8 100644
--- a/MiraAPI.Example/Resources/Locale/en_US.xml
+++ b/MiraAPI.Example/Resources/Locale/en_US.xml
@@ -16,11 +16,11 @@
Your votes have double the power.
You get one extra vote in the meeting!
The Mayor is a Crewmate role that gets two votes in the meeting, instead of the singular vote other Crewmates get.
- Chamelon
- Become one with the environment.
- Fade out of everyone's vision by not moving.
- Stay invisible while not moving.
- The Chamelon is a Crewmate role that slowly becomes invisible while standing still.
+ Chameleon
+ Become one with the environment.
+ Fade out of everyone's vision by not moving.
+ Stay invisible while not moving.
+ The Chameleon is a Crewmate role that slowly becomes invisible while standing still.
Outcast Killer
Kill everyone else.
Be the last outcast standing.
@@ -73,7 +73,7 @@ Eliminate every other faction.
Example Bool 2
Slider
Example Number
- Example Multplier
+ Example Multiplier
Example Enum
Example Button
Outcast Killer Wins!
diff --git a/MiraAPI/GameModes/HideAndSeekMode.cs b/MiraAPI/GameModes/HideAndSeekMode.cs
index 316c3cf..ec99ce9 100644
--- a/MiraAPI/GameModes/HideAndSeekMode.cs
+++ b/MiraAPI/GameModes/HideAndSeekMode.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using AmongUs.Data;
@@ -462,7 +461,6 @@ public class HideAndSeekMode : AbstractGameMode
}
///
- [SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Warning cascades into forcing the entire tree to be ternary operators.")]
public override PlayerBodyTypes GetBodyType(PlayerControl player)
{
if (player == null || player.Data == null || player.Data.Role == null)
diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs
index 646de56..40cf0f8 100644
--- a/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs
+++ b/MiraAPI/GameOptions/OptionTypes/ModdedPlayerOption.cs
@@ -43,7 +43,7 @@ public class ModdedPlayerOption : ModdedOption
data.Title = StringName;
data.Type = global::OptionTypes.Player;
- Values = new();
+ Values = [];
AllowNone = allowNone;
Value = DefaultValue;
}
diff --git a/MiraAPI/Hud/CustomPaginableMenu.cs b/MiraAPI/Hud/CustomPaginableMenu.cs
index 27f7819..f24ccee 100644
--- a/MiraAPI/Hud/CustomPaginableMenu.cs
+++ b/MiraAPI/Hud/CustomPaginableMenu.cs
@@ -100,7 +100,6 @@ public abstract class CustomPaginableMenu : CustomPhoneMenu GetFilteredEntries()
{
var query = NormalizeForSearch(searchText);
diff --git a/MiraAPI/MiraApiPlugin.cs b/MiraAPI/MiraApiPlugin.cs
index 4da8968..a07c36d 100644
--- a/MiraAPI/MiraApiPlugin.cs
+++ b/MiraAPI/MiraApiPlugin.cs
@@ -64,11 +64,9 @@ public partial class MiraApiPlugin : BasePlugin, IMiraPlugin
private static MiraPluginManager? PluginManager { get; set; }
internal Harmony Harmony { get; } = new(Id);
- public static ConfigEntry WikiSortingGroup { get; private set; }
-
- public static ConfigEntry WikiSortingMethod { get; private set; }
-
- public static ConfigEntry WikiSortingFilter { get; private set; }
+ internal static ConfigEntry WikiSortingGroup { get; private set; }
+ internal static ConfigEntry WikiSortingMethod { get; private set; }
+ internal static ConfigEntry WikiSortingFilter { get; private set; }
///
public override void Load()
diff --git a/MiraAPI/Patches/.editorconfig b/MiraAPI/Patches/.editorconfig
index 2622fc7..a019ed9 100644
--- a/MiraAPI/Patches/.editorconfig
+++ b/MiraAPI/Patches/.editorconfig
@@ -12,5 +12,5 @@ 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
+# SA1602: Enumeration items should be documented.
+dotnet_diagnostic.SA1602.severity = none
\ No newline at end of file
diff --git a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs
index e65f4c3..7e2b312 100644
--- a/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs
+++ b/MiraAPI/Patches/Options/GameOptionsMenuPatch.cs
@@ -25,9 +25,9 @@ namespace MiraAPI.Patches.Options;
[HarmonyPatch(typeof(GameOptionsMenu))]
internal static class GameOptionsMenuPatch
{
- private static readonly List VanillaHeaders = new();
- private static readonly List VanillaOptions = new();
- private static readonly List MainOptions = new();
+ private static readonly List VanillaHeaders = [];
+ private static readonly List VanillaOptions = [];
+ private static readonly List MainOptions = [];
private static readonly Dictionary> GameModeGroups = [];
private static readonly Dictionary> GameModeOptions = [];
private static readonly Dictionary> GameModeHeaders = [];
diff --git a/MiraAPI/Patches/Options/SettingPatches.cs b/MiraAPI/Patches/Options/SettingPatches.cs
index 8fd6581..59b956c 100644
--- a/MiraAPI/Patches/Options/SettingPatches.cs
+++ b/MiraAPI/Patches/Options/SettingPatches.cs
@@ -1,5 +1,4 @@
-using System.Diagnostics.CodeAnalysis;
-using System.Globalization;
+using System.Globalization;
using System.Linq;
using HarmonyLib;
using MiraAPI.GameOptions;
@@ -24,7 +23,6 @@ public static class SettingPatches
/// to skip original method.
[HarmonyPrefix]
[HarmonyPatch(typeof(FloatGameSetting), nameof(FloatGameSetting.GetValueString))]
- [SuppressMessage("Style", "IDE0045:Convert to conditional expression", Justification = "Warning cascades into forcing the entire tree to be ternary operators.")]
public static bool ValueStringPatch(
FloatGameSetting __instance,
ref string __result,
diff --git a/MiraAPI/Patches/RoleGuidePatches.cs b/MiraAPI/Patches/RoleGuidePatches.cs
index 92080e8..508b3f7 100644
--- a/MiraAPI/Patches/RoleGuidePatches.cs
+++ b/MiraAPI/Patches/RoleGuidePatches.cs
@@ -33,20 +33,13 @@ public static class RoleGuidePatches
private static readonly List RoleEntries = [];
private static readonly List ModifierEntries = [];
- public static GameObject SearchComponentContainer;
- public static PassiveButton SearchIconButton;
+ private static GameObject searchComponentContainer;
private static SpriteRenderer _searchIconIdle;
private static SpriteRenderer _searchIconHover;
- public static TextMeshPro OrderSortingTmp;
- public static PassiveButton SearchSortingOrderButton;
private static SpriteRenderer _searchSortingOrderIdle;
private static SpriteRenderer _searchSortingOrderHover;
- public static TextMeshPro GroupSortingTmp;
- public static PassiveButton SearchSortingGroupButton;
private static SpriteRenderer _searchSortingGroupIdle;
private static SpriteRenderer _searchSortingGroupHover;
- public static TextMeshPro FilterSortingTmp;
- public static PassiveButton SearchSortingFilterButton;
private static SpriteRenderer _searchSortingFilterIdle;
private static SpriteRenderer _searchSortingFilterHover;
private static Scroller _rolesScroller;
@@ -153,10 +146,6 @@ public static class RoleGuidePatches
_advancedInfoTabScroller = null!;
_currentAdvancedTabObject = null!;
_loadingSpinner = null;
- SearchIconButton = null!;
- SearchSortingOrderButton = null!;
- SearchSortingGroupButton = null!;
- SearchSortingFilterButton = null!;
_searchIconIdle = null!;
_searchIconHover = null!;
_searchSortingOrderIdle = null!;
@@ -260,6 +249,7 @@ public static class RoleGuidePatches
_titleText.transform.GetComponent().Destroy();
}
}
+
if (!_titleIcon)
{
var titleIconObj = new GameObject("TitleIcon");
@@ -369,18 +359,18 @@ public static class RoleGuidePatches
_guide = instance;
_loading = false;
- SearchComponentContainer = new GameObject();
- SearchComponentContainer.transform.SetParent(instance.MatchInfoParent.transform);
- SearchComponentContainer.transform.localPosition = Vector3.zero;
- instance.MatchInfoParent.transform.GetChild(1).transform.SetParent(SearchComponentContainer.transform);
+ searchComponentContainer = new GameObject();
+ searchComponentContainer.transform.SetParent(instance.MatchInfoParent.transform);
+ searchComponentContainer.transform.localPosition = Vector3.zero;
+ instance.MatchInfoParent.transform.GetChild(1).transform.SetParent(searchComponentContainer.transform);
var sortingOrderButton = Object.Instantiate(
HudManager.Instance.SettingsButton,
- SearchComponentContainer.transform);
+ searchComponentContainer.transform);
sortingOrderButton.gameObject.SetActive(true);
- SearchSortingOrderButton = sortingOrderButton.GetComponent();
+ var searchSortingOrderButton = sortingOrderButton.GetComponent();
SetupSearchButton(
- SearchSortingOrderButton,
+ searchSortingOrderButton,
"SearchSortingOrderButton",
SwitchMethod,
GetSortMethodSprite(_sortMethod, false),
@@ -391,11 +381,11 @@ public static class RoleGuidePatches
var sortingGroupButton = Object.Instantiate(
sortingOrderButton,
- SearchComponentContainer.transform);
+ searchComponentContainer.transform);
sortingGroupButton.gameObject.SetActive(true);
- SearchSortingGroupButton = sortingGroupButton.GetComponent();
+ var searchSortingGroupButton = sortingGroupButton.GetComponent();
SetupSearchButton(
- SearchSortingGroupButton,
+ searchSortingGroupButton,
"SearchSortingGroupButton",
SwitchGrouping,
GetSortGroupSprite(_sortGrouping, false),
@@ -406,11 +396,11 @@ public static class RoleGuidePatches
var sortingFilterButton = Object.Instantiate(
sortingOrderButton,
- SearchComponentContainer.transform);
+ searchComponentContainer.transform);
sortingFilterButton.gameObject.SetActive(true);
- SearchSortingFilterButton = sortingFilterButton.GetComponent();
+ var searchSortingFilterButton = sortingFilterButton.GetComponent();
SetupSearchButton(
- SearchSortingFilterButton,
+ searchSortingFilterButton,
"SearchSortingFilterButton",
SwitchFilter,
GetSortFilterSprite(_sortFilter, false),
@@ -463,9 +453,8 @@ public static class RoleGuidePatches
sortingOrderButton,
sortingOrderButton.transform.parent);
searchIconButton.gameObject.SetActive(true);
- SearchIconButton = searchIconButton.GetComponent();
SetupSearchButton(
- SearchIconButton,
+ searchIconButton.GetComponent(),
"SearchIconButton",
ClearSearchText,
MiraAssets.SearchIconIdleSprite,
@@ -476,7 +465,7 @@ public static class RoleGuidePatches
var searchBox = Object.Instantiate(
instance.TabButtons[2].gameObject,
- SearchComponentContainer.transform);
+ searchComponentContainer.transform);
var searchButton = searchBox.GetComponent();
var tmpText = searchButton.transform.GetChild(0).GetComponent();
tmpText.GetComponent().Destroy();
@@ -621,26 +610,26 @@ public static class RoleGuidePatches
modifiersButton.OnClick.AddListener((Action)(() => OpenModifiersTab()));
instance.TabButtons.Add(modifiersButton);
- OrderSortingTmp = Object.Instantiate(
+ var orderSortingTmp = Object.Instantiate(
_titleText,
sortingOrderButton.transform);
- OrderSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.OrderLabel");
- OrderSortingTmp.fontSize = OrderSortingTmp.fontSizeMax = 3f;
- OrderSortingTmp.alignment = TextAlignmentOptions.Center;
- OrderSortingTmp.horizontalAlignment = HorizontalAlignmentOptions.Center;
- OrderSortingTmp.transform.localPosition = new Vector3(-0.65f, 0.4f, 0);
+ orderSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.OrderLabel");
+ orderSortingTmp.fontSize = orderSortingTmp.fontSizeMax = 3f;
+ orderSortingTmp.alignment = TextAlignmentOptions.Center;
+ orderSortingTmp.horizontalAlignment = HorizontalAlignmentOptions.Center;
+ orderSortingTmp.transform.localPosition = new Vector3(-0.65f, 0.4f, 0);
- GroupSortingTmp = Object.Instantiate(
- OrderSortingTmp,
+ var groupSortingTmp = Object.Instantiate(
+ orderSortingTmp,
sortingGroupButton.transform);
- OrderSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.GroupingLabel");
- GroupSortingTmp.transform.localPosition = new Vector3(-0.65f, 0.4f, 0);
+ orderSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.GroupingLabel");
+ groupSortingTmp.transform.localPosition = new Vector3(-0.65f, 0.4f, 0);
- FilterSortingTmp = Object.Instantiate(
- OrderSortingTmp,
+ var filterSortingTmp = Object.Instantiate(
+ orderSortingTmp,
sortingFilterButton.transform);
- FilterSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.FilterLabel");
- FilterSortingTmp.transform.localPosition = new Vector3(-0.65f, 0.4f, 0);
+ filterSortingTmp.text = MiraLocaleManager.Get("MiraApi.Wiki.FilterLabel");
+ filterSortingTmp.transform.localPosition = new Vector3(-0.65f, 0.4f, 0);
playerButton.transform.localPosition = new Vector3(-3.6f, 0.656f, -0.2f);
settingButton.transform.localPosition = new Vector3(-3.6f, 0.056f, -0.2f);
@@ -731,11 +720,17 @@ public static class RoleGuidePatches
}
}
- public static void OpenModifiersTab() => OpenTab(ModifiersTabIndex);
+ public static void OpenModifiersTab()
+ {
+ OpenTab(ModifiersTabIndex);
+ }
- public static void OpenRolesTab() => OpenTab(RolesTabIndex);
+ public static void OpenRolesTab()
+ {
+ OpenTab(RolesTabIndex);
+ }
- public sealed class DetailedPanel(string title, string category, string modId, RoleBehaviour? role, GameModifier? modifier)
+ private sealed class DetailedPanel(string title, string category, string modId, RoleBehaviour? role, GameModifier? modifier)
{
public MatchInfoRolePanel? Panel { get; set; }
public string Title { get; } = title;
@@ -803,14 +798,7 @@ public static class RoleGuidePatches
private static IEnumerator CoPopulate(float delay)
{
- if (delay > 0f)
- {
- yield return new WaitForSeconds(delay);
- }
- else
- {
- yield return null;
- }
+ yield return delay > 0f ? new WaitForSeconds(delay) : null;
if (!_guide)
{
@@ -1048,14 +1036,8 @@ public static class RoleGuidePatches
public static void SwitchFilter()
{
var stepUp = (SortingFilter)((int)_sortFilter + 1);
- if (Enum.IsDefined(stepUp))
- {
- _sortFilter = stepUp;
- }
- else
- {
- _sortFilter = SortingFilter.EnabledOnly;
- }
+
+ _sortFilter = Enum.IsDefined(stepUp) ? stepUp : SortingFilter.EnabledOnly;
MiraApiPlugin.WikiSortingFilter.Value = _sortFilter;
@@ -1094,14 +1076,8 @@ public static class RoleGuidePatches
public static void SwitchMethod()
{
var stepUp = (SortingMethod)((int)_sortMethod + 1);
- if (Enum.IsDefined(stepUp))
- {
- _sortMethod = stepUp;
- }
- else
- {
- _sortMethod = SortingMethod.Alphabetical;
- }
+
+ _sortMethod = Enum.IsDefined(stepUp) ? stepUp : SortingMethod.Alphabetical;
MiraApiPlugin.WikiSortingMethod.Value = _sortMethod;
@@ -1288,7 +1264,7 @@ public static class RoleGuidePatches
}
_advancedInfoTabScroller?.gameObject.SetActive(false);
- SearchComponentContainer?.SetActive(true);
+ searchComponentContainer?.SetActive(true);
UpdateSpinner();
}
@@ -1341,9 +1317,9 @@ public static class RoleGuidePatches
_currentAdvancedTabObject.Destroy();
}
- if (SearchComponentContainer)
+ if (searchComponentContainer)
{
- SearchComponentContainer.SetActive(false);
+ searchComponentContainer.SetActive(false);
}
if (_advancedInfoTabScroller == null)
@@ -1543,20 +1519,20 @@ public static class RoleGuidePatches
}
}
-public enum SortingGroups
+internal enum SortingGroups
{
Ungrouped,
Faction,
AmountChance,
}
-public enum SortingMethod
+internal enum SortingMethod
{
Alphabetical,
AlphabeticalDescending,
}
-public enum SortingFilter
+internal enum SortingFilter
{
EnabledOnly,
DisabledOnly,
diff --git a/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs b/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs
index f047fd3..b856d22 100644
--- a/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs
+++ b/MiraAPI/Patches/Stubs/RoleBehaviourStubs.cs
@@ -39,10 +39,10 @@ public static class RoleBehaviourStubs
}
///
- /// Stub method for .
+ /// Stub method for .
///
/// The object.
- /// The to append the task hint to.
+ /// The to append the task hint to.
[HarmonyReversePatch]
[HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.AppendTaskHint), typeof(Il2CppSystem.Text.StringBuilder))]
[MethodImpl(MethodImplOptions.NoInlining)]
diff --git a/MiraAPI/Presets/SavePresetPopup.cs b/MiraAPI/Presets/SavePresetPopup.cs
index b9932ff..9405e83 100644
--- a/MiraAPI/Presets/SavePresetPopup.cs
+++ b/MiraAPI/Presets/SavePresetPopup.cs
@@ -16,6 +16,7 @@ namespace MiraAPI.Presets;
[RegisterInIl2Cpp]
[SuppressMessage("Style", "IDE0051:Remove unused private members", Justification = "Unity convention.")]
+[SuppressMessage("Design", "CA1050:Declare types in namespaces", Justification = "Unity script.")]
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member (Justification: Unity fields; ignore.)
public class SavePresetPopup(nint cppPtr) : Minigame(cppPtr)
{
diff --git a/MiraAPI/Roles/ICustomRole.cs b/MiraAPI/Roles/ICustomRole.cs
index 4d5e247..88b1f85 100644
--- a/MiraAPI/Roles/ICustomRole.cs
+++ b/MiraAPI/Roles/ICustomRole.cs
@@ -345,7 +345,14 @@ public interface ICustomRole : IOptionable
/// Gets the list of abilities or other similar information to display below the text of a role's wiki page.
///
[HideFromIl2Cpp]
- public List WikiAbilities => [];
+ List WikiAbilities => [];
}
+///
+/// A data holder of an ability's wiki entry.
+///
+/// The name of the ability.
+/// The type of the ability.
+/// The description of the ability.
+/// The icon sprite of the ability.
public record struct AdvancedWikiAbilityDescription(string Name, string AbilityType, string Description, LoadableAsset Icon);
diff --git a/MiraAPI/Translation/MiraLocaleManager.cs b/MiraAPI/Translation/MiraLocaleManager.cs
index d26f422..0bce8ca 100644
--- a/MiraAPI/Translation/MiraLocaleManager.cs
+++ b/MiraAPI/Translation/MiraLocaleManager.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -162,7 +161,6 @@ public static class MiraLocaleManager
/// The string id to find.
/// Fallback string to use if no translation is found.
/// A based on the key provided.
- [SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Warning cascades into forcing the entire tree to be ternary operators.")]
public static string Get(MiraLanguage language, string key, string fallback = "")
{
if (Locale.TryGetValue(language, out var translations) &&
diff --git a/MiraAPI/Utilities/ControllableComparer.cs b/MiraAPI/Utilities/ControllableComparer.cs
index 49c4b0e..5e7ac70 100644
--- a/MiraAPI/Utilities/ControllableComparer.cs
+++ b/MiraAPI/Utilities/ControllableComparer.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace MiraAPI.Utilities;
@@ -9,7 +8,6 @@ internal sealed class ControllableComparer(T[] forcedToBottom, T[] forcedToTo
where T : IComparable
{
///
- [SuppressMessage("Style", "IDE0046:Convert to conditional expression", Justification = "Warning cascades into forcing the entire tree to be ternary operators.")]
public int Compare(T? x, T? y)
{
if ((forcedToBottom.Contains(x) && forcedToBottom.Contains(y)) || (forcedToTop.Contains(x) && forcedToTop.Contains(y)))
diff --git a/MiraAPI/Utilities/Helpers.cs b/MiraAPI/Utilities/Helpers.cs
index 773a4ae..413fb59 100644
--- a/MiraAPI/Utilities/Helpers.cs
+++ b/MiraAPI/Utilities/Helpers.cs
@@ -32,6 +32,11 @@ public static class Helpers
{
private static readonly Regex TrailingZeroRegex = new(@"\.0+(?!\d)", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
+ ///
+ /// Gets all of the options stored within the provided type.
+ ///
+ /// The type to get the options of.
+ /// A readonly list of options from the provided type.
public static ReadOnlyCollection? GetModdedOptionsForType(Type classType)
{
var group = ModdedOptionsManager.Groups.FirstOrDefault(x => x.OptionableType == classType);
@@ -39,6 +44,11 @@ public static class Helpers
return group?.Children;
}
+ ///
+ /// Gets the option text of the provided type.
+ ///
+ /// The type to get the text for.
+ /// The options text of the provided type.
public static string GetOptionsText(Type classType)
{
var group = ModdedOptionsManager.Groups.FirstOrDefault(x => x.OptionableType == classType);
@@ -836,7 +846,7 @@ public static class Helpers
obj.transform.SetParent(parent.Inner.transform);
obj.transform.localPosition = new Vector3(0f, 0.5f, 0f);
var scrollHeight = variableTextSizing
- ? (desc.textBounds.size.y - 2.5f) + maxTextSize + 0.475f
+ ? desc.textBounds.size.y - 2.5f + maxTextSize + 0.475f
: desc.textBounds.size.y - 2.5f + Mathf.Ceil(num / 2f) * 1.45f;
parent.SetYBoundsMax(Mathf.Clamp(scrollHeight, 0f, 999f));
return obj;
@@ -857,6 +867,7 @@ public static class Helpers
panel.roleDescription.fontSizeMin = 1.5f;
panel.roleDescription.ForceMeshUpdate();
}
+
panel.roleIcon.sprite = ability.Icon.LoadAsset();
panel.roleIcon.SetSizeLimit(0.13f);
From 1027e9044757eec4a2a7d4f099d3ea3022b70b77 Mon Sep 17 00:00:00 2001
From: Az <56699741+AlchlcDvl@users.noreply.github.com>
Date: Wed, 14 Oct 2026 03:49:21 -0400
Subject: [PATCH 12/12] minor improvements
---
MiraAPI/Colors/ColorSpaces/CmykColor.cs | 8 +++++---
MiraAPI/Colors/ColorSpaces/HslColor.cs | 2 +-
MiraAPI/Colors/VariableColor.cs | 2 +-
MiraAPI/Patches/Colors/VariableColorPatches.cs | 17 ++++++++++-------
4 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/MiraAPI/Colors/ColorSpaces/CmykColor.cs b/MiraAPI/Colors/ColorSpaces/CmykColor.cs
index c94ebec..11ec563 100644
--- a/MiraAPI/Colors/ColorSpaces/CmykColor.cs
+++ b/MiraAPI/Colors/ColorSpaces/CmykColor.cs
@@ -42,9 +42,11 @@ public record struct CmykColor(float c, float m, float y, float k, float a = 1f)
/// 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);
+ var key = 1f - cmyk.k;
+
+ var r = (1f - cmyk.c) * key;
+ var g = (1f - cmyk.m) * key;
+ var b = (1f - cmyk.y) * key;
return new Color(MathUtilities.Clamp01(r), MathUtilities.Clamp01(g), MathUtilities.Clamp01(b), cmyk.a);
}
diff --git a/MiraAPI/Colors/ColorSpaces/HslColor.cs b/MiraAPI/Colors/ColorSpaces/HslColor.cs
index 1929e77..466f159 100644
--- a/MiraAPI/Colors/ColorSpaces/HslColor.cs
+++ b/MiraAPI/Colors/ColorSpaces/HslColor.cs
@@ -75,7 +75,7 @@ public record struct HslColor(float h, float s, float l, float a = 1f)
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));
var h = 0f;
diff --git a/MiraAPI/Colors/VariableColor.cs b/MiraAPI/Colors/VariableColor.cs
index 1ca610f..bfb9717 100644
--- a/MiraAPI/Colors/VariableColor.cs
+++ b/MiraAPI/Colors/VariableColor.cs
@@ -14,7 +14,7 @@ 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.
-/// The default time that is used to automatically fill in the static colors when the latter are not provided.
+/// The default time that is used to automatically fill in the static colors when the latter is not provided.
public sealed class VariableColor(StringNames name, Func mainEvaluator, Func? shadowEvaluator, Color32? mainColor = null, Color32? shadowColor = null, float defaultTime = 0f)
: CustomColor(name, mainColor ?? mainEvaluator(defaultTime), shadowColor ?? shadowEvaluator?.Invoke(defaultTime) ?? (mainColor ?? mainEvaluator(defaultTime)).GetShadowColor(60))
{
diff --git a/MiraAPI/Patches/Colors/VariableColorPatches.cs b/MiraAPI/Patches/Colors/VariableColorPatches.cs
index cf64d6d..98bed64 100644
--- a/MiraAPI/Patches/Colors/VariableColorPatches.cs
+++ b/MiraAPI/Patches/Colors/VariableColorPatches.cs
@@ -70,23 +70,26 @@ 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))
- {
+ if (!__instance.gameObject.activeInHierarchy)
+ return;
+
+ var colorId = __instance.player.cosmetics.ColorId;
+
+ if (!PaletteManager.IsVariable(colorId))
return;
- }
var host = GameData.Instance.GetHost();
if (!host)
return;
- var text = PaletteManager.GetMainColor(__instance.player.cosmetics.ColorId).ToHtmlStringRGBA();
- __instance.hostLabel.text = TranslationController.Instance.GetString(StringNames.HostNounLabel);
+ var text = PaletteManager.GetMainColor(colorId).ToHtmlStringRGBA();
+ var label = __instance.hostLabel;
+ label.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, "");
+ label.text = "" + label.text + "";
}
if (AmongUsClient.Instance.AmHost)