1
0
Fork 0

Compare commits

...

4 Commits

Author SHA1 Message Date
ivan tkachenko 585ef604ff Release v1337.420.9003 2025-08-25 12:15:20 +03:00
ivan tkachenko 99babe8bdf Substitute placeholder nulls with per-light initial color for transitions
Fade out and first color transitions used to assume white as a default
color, which is not always the case e.g. in Mineshaft tunnel tiles. Use
nullable from/to/color fields, and substitute them with per-light
initial color data in a new virtual method that calculates colors.
2025-08-25 01:29:39 +03:00
ivan tkachenko bbd9b0204f Rename PoweredLightsAnimators.cs to PoweredLights.cs
For simplicity, but also because it already handles more than animators.
2025-08-24 22:34:33 +03:00
ivan tkachenko 70eabe75dd Bump version 2025-08-24 22:29:47 +03:00
5 changed files with 105 additions and 48 deletions

View File

@ -1,5 +1,9 @@
# Changelog # Changelog
## MuzikaGromche 1337.420.9003 - Lights Out Edition
- Fixed wrong colors during fade out transition, e.g. in Mineshaft tunnel tiles.
## MuzikaGromche 1337.420.9002 - Anime Edition ## MuzikaGromche 1337.420.9002 - Anime Edition
- Added a new track OnePartiyaUdar in Japanese language. - Added a new track OnePartiyaUdar in Japanese language.

View File

@ -8,7 +8,7 @@
<AssemblyName>Ratijas.MuzikaGromche</AssemblyName> <AssemblyName>Ratijas.MuzikaGromche</AssemblyName>
<Product>Muzika Gromche</Product> <Product>Muzika Gromche</Product>
<Description>Add some content to your inverse teleporter experience on Titan!</Description> <Description>Add some content to your inverse teleporter experience on Titan!</Description>
<Version>1337.420.9002</Version> <Version>1337.420.9003</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>

View File

@ -597,22 +597,6 @@ namespace MuzikaGromche
return Tracks.SelectMany(track => track.GetTracks()).FirstOrDefault(track => track.Name == name); 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 // Max audible distance for AudioSource and LyricsEvent
public const float AudioMaxDistance = 150; public const float AudioMaxDistance = 150;
@ -1559,9 +1543,7 @@ namespace MuzikaGromche
if (windUpOffsetTimestamp.Beat < 0f && track.FadeOutBeat < loopOffsetSpan.BeatToInclusive && loopOffsetSpan.BeatFromExclusive <= fadeOutEnd) if (windUpOffsetTimestamp.Beat < 0f && track.FadeOutBeat < loopOffsetSpan.BeatToInclusive && loopOffsetSpan.BeatFromExclusive <= fadeOutEnd)
{ {
var t = (loopOffsetSpan.BeatToInclusive - track.FadeOutBeat) / track.FadeOutDuration; var t = (loopOffsetSpan.BeatToInclusive - track.FadeOutBeat) / track.FadeOutDuration;
// TODO: assumes that default lights color is white return new SetLightsColorTransitionEvent(/* use initial light color */null, Color.black, Easing.Linear, t);
var DefaultLightsColor = Color.white;
return new SetLightsColorTransitionEvent(DefaultLightsColor, Color.black, Easing.Linear, t);
} }
else else
{ {
@ -1610,9 +1592,9 @@ namespace MuzikaGromche
} }
} }
// default // 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 transitionStart = clipsBoundary - track.ColorTransitionIn;
var transitionEnd = clipsBoundary + track.ColorTransitionOut; var transitionEnd = clipsBoundary + track.ColorTransitionOut;
@ -1625,7 +1607,7 @@ namespace MuzikaGromche
return new SetLightsColorTransitionEvent(ColorAtWholeBeat(transitionStart), ColorAtWholeBeat(transitionEnd), track.ColorTransitionEasing, t); return new SetLightsColorTransitionEvent(ColorAtWholeBeat(transitionStart), ColorAtWholeBeat(transitionEnd), track.ColorTransitionEasing, t);
} }
Color ColorAtWholeBeat(BeatTimestamp timestamp) Color? ColorAtWholeBeat(BeatTimestamp timestamp)
{ {
if (timestamp.Beat >= 0f) if (timestamp.Beat >= 0f)
{ {
@ -1634,9 +1616,7 @@ namespace MuzikaGromche
} }
else else
{ {
// TODO: assumes that default lights color is white return float.IsNaN(track.FadeOutBeat) ? /* use initial light color */ null : Color.black;
var DefaultLightsColor = Color.white;
return float.IsNaN(track.FadeOutBeat) ? DefaultLightsColor : Color.black;
} }
} }
} }
@ -1644,26 +1624,55 @@ namespace MuzikaGromche
abstract class BaseEvent; abstract class BaseEvent;
class SetLightsColorEvent(Color color) : BaseEvent abstract class SetLightsColorEvent : BaseEvent
{ {
public readonly Color Color = color; // Calculate final color, substituting null with initialColor if needed.
public override string ToString() 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) class SetLightsColorStaticEvent(Color? color) : SetLightsColorEvent
: SetLightsColorEvent(Color.Lerp(from, to, Mathf.Clamp(easing.Eval(t), 0f, 1f)))
{ {
// Additional context for debugging public readonly Color? Color = color;
public readonly Color From = from;
public readonly Color To = to; public override Color GetColor(Color initialColor)
public readonly Easing Easing = easing; {
public readonly float T = t; return Color ?? initialColor;
}
public override string ToString() 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 // transition away from state 2 ("poppedOut"), normally to state 0
if (__state.previousState == 2 && __instance.previousState != 2) if (__state.previousState == 2 && __instance.previousState != 2)
{ {
Plugin.ResetLightColor(); PoweredLightsBehaviour.Instance.ResetLightColor();
DiscoBallManager.Disable(); DiscoBallManager.Disable();
// Rotate track groups // Rotate track groups
behaviour.ChooseTrackServerRpc(); behaviour.ChooseTrackServerRpc();
@ -2458,7 +2467,7 @@ namespace MuzikaGromche
break; break;
case SetLightsColorEvent e: case SetLightsColorEvent e:
Plugin.SetLightColor(e.Color); PoweredLightsBehaviour.Instance.SetLightColor(e);
break; break;
case FlickerLightsEvent: case FlickerLightsEvent:
@ -2488,7 +2497,7 @@ namespace MuzikaGromche
{ {
if (__instance is JesterAI) if (__instance is JesterAI)
{ {
Plugin.ResetLightColor(); PoweredLightsBehaviour.Instance.ResetLightColor();
DiscoBallManager.Disable(); DiscoBallManager.Disable();
// Just in case if players have spawned multiple Jesters, // Just in case if players have spawned multiple Jesters,
// Don't reset Plugin.CurrentTrack to null, // Don't reset Plugin.CurrentTrack to null,

View File

@ -252,7 +252,10 @@ namespace MuzikaGromche
static bool OnRefreshLightsList(RoundManager __instance) static bool OnRefreshLightsList(RoundManager __instance)
{ {
RefreshLightsListPatched(__instance); RefreshLightsListPatched(__instance);
LoadInitialLightsColors(__instance);
var behaviour = __instance.gameObject.GetComponent<PoweredLightsBehaviour>() ?? __instance.gameObject.AddComponent<PoweredLightsBehaviour>();
behaviour.Refresh();
// Skip the original method // Skip the original method
return false; return false;
} }
@ -287,15 +290,56 @@ namespace MuzikaGromche
animator.SetFloat("flickerSpeed", UnityEngine.Random.Range(0.6f, 1.4f)); 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<Light, Color>(); public Light Light;
foreach (var light in self.allPoweredLights) public Color InitialColor;
}
private readonly List<LightData> 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;
} }
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "MuzikaGromche", "name": "MuzikaGromche",
"version_number": "1337.420.9002", "version_number": "1337.420.9003",
"author": "Ratijas", "author": "Ratijas",
"description": "Add some content to your inverse teleporter experience on Titan!", "description": "Add some content to your inverse teleporter experience on Titan!",
"website_url": "https://git.vilunov.me/ratijas/muzika-gromche", "website_url": "https://git.vilunov.me/ratijas/muzika-gromche",