From 3bf066f38c0002d3b052b8f23d5e3eecda12629f Mon Sep 17 00:00:00 2001
From: Az <56699741+AlchlcDvl@users.noreply.github.com>
Date: Tue, 29 Sep 2026 09:53:18 -0400
Subject: [PATCH 1/9] 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 2/9] 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 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 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 8/9] 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 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 9/9] 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)