diff --git a/CHANGELOG.md b/CHANGELOG.md index 900016a..1b1ad45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## MuzikaGromche 1337.420.9003 +- Fixed wrong colors during fade out transition, e.g. in Mineshaft tunnel tiles. ## MuzikaGromche 1337.420.9002 - Anime Edition diff --git a/MuzikaGromche/Plugin.cs b/MuzikaGromche/Plugin.cs index 7cbd490..d9c7ade 100644 --- a/MuzikaGromche/Plugin.cs +++ b/MuzikaGromche/Plugin.cs @@ -597,22 +597,6 @@ namespace MuzikaGromche return Tracks.SelectMany(track => track.GetTracks()).FirstOrDefault(track => track.Name == name); } - public static void SetLightColor(Color color) - { - foreach (var light in RoundManager.Instance.allPoweredLights) - { - light.color = color; - } - } - - public static void ResetLightColor() - { - foreach (var (light, color) in InitialLightsColors) - { - light.color = color; - } - } - // Max audible distance for AudioSource and LyricsEvent public const float AudioMaxDistance = 150; @@ -1559,9 +1543,7 @@ namespace MuzikaGromche if (windUpOffsetTimestamp.Beat < 0f && track.FadeOutBeat < loopOffsetSpan.BeatToInclusive && loopOffsetSpan.BeatFromExclusive <= fadeOutEnd) { var t = (loopOffsetSpan.BeatToInclusive - track.FadeOutBeat) / track.FadeOutDuration; - // TODO: assumes that default lights color is white - var DefaultLightsColor = Color.white; - return new SetLightsColorTransitionEvent(DefaultLightsColor, Color.black, Easing.Linear, t); + return new SetLightsColorTransitionEvent(/* use initial light color */null, Color.black, Easing.Linear, t); } else { @@ -1610,9 +1592,9 @@ namespace MuzikaGromche } } // default - return new SetLightsColorEvent(ColorAtWholeBeat(timestamp)); + return new SetLightsColorStaticEvent(ColorAtWholeBeat(timestamp)); - SetLightsColorEvent ColorTransition(BeatTimestamp clipsBoundary) + SetLightsColorTransitionEvent ColorTransition(BeatTimestamp clipsBoundary) { var transitionStart = clipsBoundary - track.ColorTransitionIn; var transitionEnd = clipsBoundary + track.ColorTransitionOut; @@ -1625,7 +1607,7 @@ namespace MuzikaGromche return new SetLightsColorTransitionEvent(ColorAtWholeBeat(transitionStart), ColorAtWholeBeat(transitionEnd), track.ColorTransitionEasing, t); } - Color ColorAtWholeBeat(BeatTimestamp timestamp) + Color? ColorAtWholeBeat(BeatTimestamp timestamp) { if (timestamp.Beat >= 0f) { @@ -1634,9 +1616,7 @@ namespace MuzikaGromche } else { - // TODO: assumes that default lights color is white - var DefaultLightsColor = Color.white; - return float.IsNaN(track.FadeOutBeat) ? DefaultLightsColor : Color.black; + return float.IsNaN(track.FadeOutBeat) ? /* use initial light color */ null : Color.black; } } } @@ -1644,26 +1624,55 @@ namespace MuzikaGromche abstract class BaseEvent; - class SetLightsColorEvent(Color color) : BaseEvent + abstract class SetLightsColorEvent : BaseEvent { - public readonly Color Color = color; - public override string ToString() + // Calculate final color, substituting null with initialColor if needed. + public abstract Color GetColor(Color initialColor); + + protected string NullableColorToString(Color? color) { - return $"Color(#{ColorUtility.ToHtmlStringRGB(Color)})"; + return color is { } c ? ColorUtility.ToHtmlStringRGB(c) : "??????"; } } - class SetLightsColorTransitionEvent(Color from, Color to, Easing easing, float t) - : SetLightsColorEvent(Color.Lerp(from, to, Mathf.Clamp(easing.Eval(t), 0f, 1f))) + class SetLightsColorStaticEvent(Color? color) : SetLightsColorEvent { - // Additional context for debugging - public readonly Color From = from; - public readonly Color To = to; - public readonly Easing Easing = easing; - public readonly float T = t; + public readonly Color? Color = color; + + public override Color GetColor(Color initialColor) + { + return Color ?? initialColor; + } + public override string ToString() { - return $"Color(#{ColorUtility.ToHtmlStringRGB(Color)} = #{ColorUtility.ToHtmlStringRGB(From)}..#{ColorUtility.ToHtmlStringRGB(To)} {Easing} {T:N4})"; + return $"Color(#{NullableColorToString(Color)})"; + } + } + + class SetLightsColorTransitionEvent(Color? from, Color? to, Easing easing, float t) : SetLightsColorEvent + { + // Additional context for debugging + public readonly Color? From = from; + public readonly Color? To = to; + public readonly Easing Easing = easing; + public readonly float T = t; + + public override Color GetColor(Color initialColor) + { + var from = From ?? initialColor; + var to = To ?? initialColor; + return Color.Lerp(from, to, Mathf.Clamp(Easing.Eval(T), 0f, 1f)); + } + + private Color? GetNullableColor() + { + return From is { } from && To is { } to ? Color.Lerp(from, to, Mathf.Clamp(Easing.Eval(T), 0f, 1f)) : null; + } + + public override string ToString() + { + return $"Color(#{NullableColorToString(GetNullableColor())} = #{NullableColorToString(From)}..#{NullableColorToString(To)} {Easing} {T:N4})"; } } @@ -2438,7 +2447,7 @@ namespace MuzikaGromche // transition away from state 2 ("poppedOut"), normally to state 0 if (__state.previousState == 2 && __instance.previousState != 2) { - Plugin.ResetLightColor(); + PoweredLightsBehaviour.Instance.ResetLightColor(); DiscoBallManager.Disable(); // Rotate track groups behaviour.ChooseTrackServerRpc(); @@ -2458,7 +2467,7 @@ namespace MuzikaGromche break; case SetLightsColorEvent e: - Plugin.SetLightColor(e.Color); + PoweredLightsBehaviour.Instance.SetLightColor(e); break; case FlickerLightsEvent: @@ -2488,7 +2497,7 @@ namespace MuzikaGromche { if (__instance is JesterAI) { - Plugin.ResetLightColor(); + PoweredLightsBehaviour.Instance.ResetLightColor(); DiscoBallManager.Disable(); // Just in case if players have spawned multiple Jesters, // Don't reset Plugin.CurrentTrack to null, diff --git a/MuzikaGromche/PoweredLights.cs b/MuzikaGromche/PoweredLights.cs index b32e172..0b55433 100644 --- a/MuzikaGromche/PoweredLights.cs +++ b/MuzikaGromche/PoweredLights.cs @@ -252,7 +252,10 @@ namespace MuzikaGromche static bool OnRefreshLightsList(RoundManager __instance) { RefreshLightsListPatched(__instance); - LoadInitialLightsColors(__instance); + + var behaviour = __instance.gameObject.GetComponent() ?? __instance.gameObject.AddComponent(); + behaviour.Refresh(); + // Skip the original method return false; } @@ -287,15 +290,56 @@ namespace MuzikaGromche animator.SetFloat("flickerSpeed", UnityEngine.Random.Range(0.6f, 1.4f)); } } + } - static void LoadInitialLightsColors(RoundManager self) + internal class PoweredLightsBehaviour : MonoBehaviour + { + private struct LightData { - var originalColors = new Dictionary(); - foreach (var light in self.allPoweredLights) + public Light Light; + public Color InitialColor; + } + + private readonly List AllPoweredLights = []; + + public static PoweredLightsBehaviour Instance { get; private set; } = null!; + + void Awake() + { + if (Instance == null) { - originalColors[light] = light.color; + Instance = this; + } + } + + public void Refresh() + { + AllPoweredLights.Clear(); + foreach (var light in RoundManager.Instance.allPoweredLights) + { + AllPoweredLights.Add(new LightData + { + Light = light, + InitialColor = light.color, + }); + } + } + + public void SetLightColor(SetLightsColorEvent e) + { + foreach (var data in AllPoweredLights) + { + var color = e.GetColor(data.InitialColor); + data.Light.color = color; + } + } + + public void ResetLightColor() + { + foreach (var data in AllPoweredLights) + { + data.Light.color = data.InitialColor; } - Plugin.InitialLightsColors = originalColors; } } }