forked from nikita/muzika-gromche
				
			Compare commits
	
		
			No commits in common. "d02e594457e772f83b798a6ee84a32469cbdc398" and "c7b67b9042bb185d5ac6d8f7670673e4aed5c2d9" have entirely different histories.
		
	
	
		
			d02e594457
			...
			c7b67b9042
		
	
		|  | @ -2,9 +2,6 @@ | ||||||
| 
 | 
 | ||||||
| ## MuzikaGromche 13.37.9001 | ## MuzikaGromche 13.37.9001 | ||||||
| 
 | 
 | ||||||
| - Fixed more missing flickering behaviours for some animators controllers. |  | ||||||
| - Fixed some powered lights not fully turning off or flickering when there are multiple Light components per container. |  | ||||||
| - Improved performance by pre-loading certain assets at the start of round instead of at a timing-critical frame update. |  | ||||||
| 
 | 
 | ||||||
| ## MuzikaGromche 13.37.1337 - Photosensitivity Warning Edition | ## MuzikaGromche 13.37.1337 - Photosensitivity Warning Edition | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,6 +1,4 @@ | ||||||
| using DunGen; | using DunGen; | ||||||
| using HarmonyLib; |  | ||||||
| using System; |  | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Linq; | using System.Linq; | ||||||
|  | @ -9,21 +7,54 @@ using UnityEngine; | ||||||
| 
 | 
 | ||||||
| namespace MuzikaGromche | namespace MuzikaGromche | ||||||
| { | { | ||||||
|     public static class DiscoBallManager |     public class DiscoBallManager : MonoBehaviour | ||||||
|     { |     { | ||||||
|         // A struct holding a disco ball container object and the name of a tile for which it was designed. |         // A struct holding a disco ball container object and the name of a tile for which it was designed. | ||||||
|         private readonly record struct TilePatch(string TileName, GameObject DiscoBallContainer) |         public readonly record struct Data(string TileName, GameObject DiscoBallContainer) | ||||||
|         { |         { | ||||||
|             // We are specifically looking for cloned tiles, not the original prototypes. |             // We are specifically looking for cloned tiles, not the original prototypes. | ||||||
|             public readonly string TileCloneName = $"{TileName}(Clone)"; |             public readonly string TileCloneName = $"{TileName}(Clone)"; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private static TilePatch[] Patches = []; |         public static readonly List<Data> Containers = []; | ||||||
|  |         private static readonly List<GameObject> InstantiatedContainers = []; | ||||||
| 
 | 
 | ||||||
|         private static readonly List<GameObject> CachedDiscoBalls = []; |         public static void Initialize() | ||||||
|         private static readonly List<Animator> CachedDiscoBallAnimators = []; |         { | ||||||
|  |             string bundlePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "muzikagromche_discoball"); | ||||||
|  |             var bundle = AssetBundle.LoadFromFile(bundlePath); | ||||||
| 
 | 
 | ||||||
|         private static readonly string[] AnimatorContainersNames = [ |             foreach ((string prefabPath, string tileName) in new[] { | ||||||
|  |                 ("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerManor.prefab", "ManorStartRoomSmall"), | ||||||
|  |                 ("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerManorOLD.prefab", "ManorStartRoom"), | ||||||
|  |                 ("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerFactory.prefab", "StartRoom"), | ||||||
|  |                 ("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerMineShaft.prefab", "MineshaftStartTile"), | ||||||
|  |                 ("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerLargeForkTileB.prefab", "LargeForkTileB"), | ||||||
|  |                 ("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerBirthdayRoomTile.prefab", "BirthdayRoomTile"), | ||||||
|  |             }) | ||||||
|  |             { | ||||||
|  |                 var container = bundle.LoadAsset<GameObject>(prefabPath); | ||||||
|  |                 Containers.Add(new(tileName, container)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public static void Enable() | ||||||
|  |         { | ||||||
|  |             // Just in case | ||||||
|  |             Disable(); | ||||||
|  | 
 | ||||||
|  |             var query = from tile in Resources.FindObjectsOfTypeAll<Tile>() | ||||||
|  |                         join container in Containers | ||||||
|  |                         on tile.gameObject.name equals container.TileCloneName | ||||||
|  |                         select (tile, container); | ||||||
|  | 
 | ||||||
|  |             foreach (var (tile, container) in query) | ||||||
|  |             { | ||||||
|  |                 Enable(tile, container); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         private static readonly string[] animatorNames = [ | ||||||
|             "DiscoBallProp/AnimContainer", |             "DiscoBallProp/AnimContainer", | ||||||
|             "DiscoBallProp1/AnimContainer", |             "DiscoBallProp1/AnimContainer", | ||||||
|             "DiscoBallProp2/AnimContainer", |             "DiscoBallProp2/AnimContainer", | ||||||
|  | @ -32,134 +63,29 @@ namespace MuzikaGromche | ||||||
|             "DiscoBallProp5/AnimContainer", |             "DiscoBallProp5/AnimContainer", | ||||||
|         ]; |         ]; | ||||||
| 
 | 
 | ||||||
|         public static void Load() |         private static void Enable(Tile tile, Data container) | ||||||
|         { |         { | ||||||
|             const string BundleFileName = "muzikagromche_discoball"; |             Debug.Log($"{nameof(MuzikaGromche)} {nameof(DiscoBallManager)} Enabling at '{tile.gameObject.name}'"); | ||||||
|             string bundlePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), BundleFileName); |             var discoBall = Instantiate(container.DiscoBallContainer, tile.transform); | ||||||
|             var assetBundle = AssetBundle.LoadFromFile(bundlePath) |             InstantiatedContainers.Add(discoBall); | ||||||
|                 ?? throw new NullReferenceException("Failed to load bundle"); |  | ||||||
| 
 | 
 | ||||||
|             (string PrefabPath, string TileName)[] patchDescriptors = |             foreach (var animatorName in animatorNames) | ||||||
|             [ |  | ||||||
|                 ("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerManor.prefab", "ManorStartRoomSmall"), |  | ||||||
|                 ("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerManorOLD.prefab", "ManorStartRoom"), |  | ||||||
|                 ("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerFactory.prefab", "StartRoom"), |  | ||||||
|                 ("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerMineShaft.prefab", "MineshaftStartTile"), |  | ||||||
|                 ("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerLargeForkTileB.prefab", "LargeForkTileB"), |  | ||||||
|                 ("Assets/LethalCompany/Mods/MuzikaGromche/DiscoBallContainerBirthdayRoomTile.prefab", "BirthdayRoomTile"), |  | ||||||
|             ]; |  | ||||||
| 
 |  | ||||||
|             Patches = [.. patchDescriptors.Select(d => |  | ||||||
|                 new TilePatch(d.TileName, assetBundle.LoadAsset<GameObject>(d.PrefabPath)) |  | ||||||
|             )]; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         internal static void Patch(Tile tile) |  | ||||||
|             { |             { | ||||||
|             var query = from patch in Patches |                 if (discoBall.transform.Find(animatorName)?.gameObject is GameObject animator) | ||||||
|                         where tile.gameObject.name == patch.TileCloneName |  | ||||||
|                         select patch; |  | ||||||
| 
 |  | ||||||
|             // Should be just one, but FirstOrDefault() isn't usable with structs |  | ||||||
|             foreach (var patch in query) |  | ||||||
|                 { |                 { | ||||||
|                 Patch(tile, patch); |                     animator.GetComponent<Animator>().SetBool("on", true); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|         static void Patch(Tile tile, TilePatch patch) |  | ||||||
|         { |  | ||||||
|             var discoBall = UnityEngine.Object.Instantiate(patch.DiscoBallContainer, tile.transform); |  | ||||||
|             if (discoBall == null) |  | ||||||
|             { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             foreach (var animator in FindDiscoBallAnimators(discoBall)) |  | ||||||
|             { |  | ||||||
|                 CachedDiscoBallAnimators.Add(animator); |  | ||||||
|             } |  | ||||||
|             CachedDiscoBalls.Add(discoBall); |  | ||||||
|             discoBall.SetActive(false); |  | ||||||
| 
 |  | ||||||
|             Debug.Log($"{nameof(MuzikaGromche)} {nameof(DiscoBallManager)} Patched tile '{tile.gameObject.name}'"); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         static IEnumerable<Animator> FindDiscoBallAnimators(GameObject discoBall) |  | ||||||
|         { |  | ||||||
|             foreach (var animatorContainerName in AnimatorContainersNames) |  | ||||||
|             { |  | ||||||
|                 var transform = discoBall.transform.Find(animatorContainerName); |  | ||||||
|                 if (transform == null) |  | ||||||
|                 { |  | ||||||
|                     // Not all prefabs have all possible animators, and it's OK |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 var animator = transform.gameObject?.GetComponent<Animator>(); |  | ||||||
|                 if (animator == null) |  | ||||||
|                 { |  | ||||||
|                     // This would be weird |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 yield return animator; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public static void Toggle(bool on) |  | ||||||
|         { |  | ||||||
|             Debug.Log($"{nameof(MuzikaGromche)} {nameof(DiscoBallManager)} Toggle {(on ? "ON" : "OFF")} {CachedDiscoBallAnimators.Count} animators"); |  | ||||||
| 
 |  | ||||||
|             foreach (var discoBall in CachedDiscoBalls) |  | ||||||
|             { |  | ||||||
|                 discoBall.SetActive(true); |  | ||||||
|             } |  | ||||||
|             foreach (var animator in CachedDiscoBallAnimators) |  | ||||||
|             { |  | ||||||
|                 animator?.SetBool("on", on); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public static void Enable() |  | ||||||
|         { |  | ||||||
|             Toggle(true); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public static void Disable() |         public static void Disable() | ||||||
|         { |         { | ||||||
|             Toggle(false); |             foreach (var discoBall in InstantiatedContainers) | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         internal static void Clear() |  | ||||||
|             { |             { | ||||||
|             Debug.Log($"{nameof(MuzikaGromche)} {nameof(DiscoBallManager)} Clearing {CachedDiscoBalls.Count} disco balls & {CachedDiscoBallAnimators.Count} animators"); |                 Debug.Log($"{nameof(MuzikaGromche)} {nameof(DiscoBallManager)}: Disabling {discoBall.name}"); | ||||||
|             CachedDiscoBallAnimators.Clear(); |                 Destroy(discoBall); | ||||||
|             CachedDiscoBalls.Clear(); |  | ||||||
|             } |             } | ||||||
|     } |             InstantiatedContainers.Clear(); | ||||||
| 
 |  | ||||||
|     [HarmonyPatch(typeof(Tile))] |  | ||||||
|     static class DiscoBallTilePatch |  | ||||||
|     { |  | ||||||
|         [HarmonyPatch(nameof(Tile.AddTriggerVolume))] |  | ||||||
|         [HarmonyPostfix] |  | ||||||
|         static void OnAddTriggerVolume(Tile __instance) |  | ||||||
|         { |  | ||||||
|             DiscoBallManager.Patch(__instance); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     [HarmonyPatch(typeof(RoundManager))] |  | ||||||
|     static class DiscoBallDespawnPatch |  | ||||||
|     { |  | ||||||
|         [HarmonyPatch(nameof(RoundManager.DespawnPropsAtEndOfRound))] |  | ||||||
|         [HarmonyPatch(nameof(RoundManager.OnDestroy))] |  | ||||||
|         [HarmonyPrefix] |  | ||||||
|         static void OnDestroy(RoundManager __instance) |  | ||||||
|         { |  | ||||||
|             var _ = __instance; |  | ||||||
|             DiscoBallManager.Clear(); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -41,7 +41,7 @@ namespace MuzikaGromche | ||||||
|                 .Select(a => $" Trying... {a}") |                 .Select(a => $" Trying... {a}") | ||||||
|         ]; |         ]; | ||||||
| 
 | 
 | ||||||
|         public static readonly Track[] Tracks = [ |         public static Track[] Tracks = [ | ||||||
|             new Track |             new Track | ||||||
|             { |             { | ||||||
|                 Name = "MuzikaGromche", |                 Name = "MuzikaGromche", | ||||||
|  | @ -482,8 +482,8 @@ namespace MuzikaGromche | ||||||
|             return tracks[trackId]; |             return tracks[trackId]; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         internal static Track? CurrentTrack; |         public static Track? CurrentTrack; | ||||||
|         internal static BeatTimeState? BeatTimeState; |         public static BeatTimeState? BeatTimeState; | ||||||
| 
 | 
 | ||||||
|         public static void SetLightColor(Color color) |         public static void SetLightColor(Color color) | ||||||
|         { |         { | ||||||
|  | @ -513,14 +513,7 @@ namespace MuzikaGromche | ||||||
|             return distance <= AudioMaxDistance; |             return distance <= AudioMaxDistance; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public static void DisplayLyrics(string text) |         private void Awake() | ||||||
|         { |  | ||||||
|             HUDManager.Instance.DisplayTip("[Lyrics]", text); |  | ||||||
|             // Don't interrupt the music with constant HUD audio pings |  | ||||||
|             HUDManager.Instance.UIAudio.Stop(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         void Awake() |  | ||||||
|         { |         { | ||||||
|             string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); |             string dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); | ||||||
|             UnityWebRequest[] requests = new UnityWebRequest[Tracks.Length * 2]; |             UnityWebRequest[] requests = new UnityWebRequest[Tracks.Length * 2]; | ||||||
|  | @ -544,16 +537,12 @@ namespace MuzikaGromche | ||||||
|                     track.LoadedLoop = DownloadHandlerAudioClip.GetContent(requests[i * 2 + 1]); |                     track.LoadedLoop = DownloadHandlerAudioClip.GetContent(requests[i * 2 + 1]); | ||||||
|                 } |                 } | ||||||
|                 Config = new Config(base.Config); |                 Config = new Config(base.Config); | ||||||
|                 DiscoBallManager.Load(); |                 DiscoBallManager.Initialize(); | ||||||
|                 PoweredLightsAnimators.Load(); |                 PoweredLightsAnimators.Load(); | ||||||
|                 var harmony = new Harmony(PluginInfo.PLUGIN_NAME); |                 var harmony = new Harmony(PluginInfo.PLUGIN_NAME); | ||||||
|                 harmony.PatchAll(typeof(JesterPatch)); |                 harmony.PatchAll(typeof(JesterPatch)); | ||||||
|                 harmony.PatchAll(typeof(EnemyAIPatch)); |                 harmony.PatchAll(typeof(EnemyAIPatch)); | ||||||
|                 harmony.PatchAll(typeof(PoweredLightsAnimatorsPatch)); |                 harmony.PatchAll(typeof(PoweredLightsAnimatorsPatch)); | ||||||
|                 harmony.PatchAll(typeof(AllPoweredLightsPatch)); |  | ||||||
|                 harmony.PatchAll(typeof(DiscoBallTilePatch)); |  | ||||||
|                 harmony.PatchAll(typeof(DiscoBallDespawnPatch)); |  | ||||||
|                 harmony.PatchAll(typeof(SpawnRatePatch)); |  | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
|             { |             { | ||||||
|  | @ -563,7 +552,7 @@ namespace MuzikaGromche | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     public readonly record struct Language(string Short, string Full) |     public record Language(string Short, string Full) | ||||||
|     { |     { | ||||||
|         public static readonly Language ENGLISH = new("EN", "English"); |         public static readonly Language ENGLISH = new("EN", "English"); | ||||||
|         public static readonly Language RUSSIAN = new("RU", "Russian"); |         public static readonly Language RUSSIAN = new("RU", "Russian"); | ||||||
|  | @ -588,9 +577,9 @@ namespace MuzikaGromche | ||||||
|                 ? Mathf.Pow(2f, 20f * x - 10f) / 2f |                 ? Mathf.Pow(2f, 20f * x - 10f) / 2f | ||||||
|                 : (2f - Mathf.Pow(2f, -20f * x + 10f)) / 2f); |                 : (2f - Mathf.Pow(2f, -20f * x + 10f)) / 2f); | ||||||
| 
 | 
 | ||||||
|         public static readonly Easing[] All = [Linear, InCubic, OutCubic, InOutCubic, InExpo, OutExpo, InOutExpo]; |         public static Easing[] All = [Linear, InCubic, OutCubic, InOutCubic, InExpo, OutExpo, InOutExpo]; | ||||||
| 
 | 
 | ||||||
|         public static readonly string[] AllNames = [.. All.Select(easing => easing.Name)]; |         public static string[] AllNames => [.. All.Select(easing => easing.Name)]; | ||||||
| 
 | 
 | ||||||
|         public static Easing FindByName(string Name) |         public static Easing FindByName(string Name) | ||||||
|         { |         { | ||||||
|  | @ -603,9 +592,9 @@ namespace MuzikaGromche | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public readonly record struct Palette(Color[] Colors) |     public record Palette(Color[] Colors) | ||||||
|     { |     { | ||||||
|         public static readonly Palette DEFAULT = new([Color.magenta, Color.cyan, Color.green, Color.yellow]); |         public static Palette DEFAULT = new([Color.magenta, Color.cyan, Color.green, Color.yellow]); | ||||||
| 
 | 
 | ||||||
|         public static Palette Parse(string[] hexColors) |         public static Palette Parse(string[] hexColors) | ||||||
|         { |         { | ||||||
|  | @ -787,7 +776,7 @@ namespace MuzikaGromche | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     readonly record struct BeatTimestamp |     public readonly record struct BeatTimestamp | ||||||
|     { |     { | ||||||
|         // Number of beats in the loop audio segment. |         // Number of beats in the loop audio segment. | ||||||
|         public readonly int LoopBeats; |         public readonly int LoopBeats; | ||||||
|  | @ -837,7 +826,7 @@ namespace MuzikaGromche | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     readonly record struct BeatTimeSpan |     public readonly record struct BeatTimeSpan | ||||||
|     { |     { | ||||||
|         public readonly int LoopBeats; |         public readonly int LoopBeats; | ||||||
|         public readonly float HalfLoopBeats => LoopBeats / 2f; |         public readonly float HalfLoopBeats => LoopBeats / 2f; | ||||||
|  | @ -987,7 +976,7 @@ namespace MuzikaGromche | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     class BeatTimeState |     public class BeatTimeState | ||||||
|     { |     { | ||||||
|         // The object is newly created, the Start audio began to play but its time hasn't adjanced from 0.0f yet. |         // The object is newly created, the Start audio began to play but its time hasn't adjanced from 0.0f yet. | ||||||
|         private bool hasStarted = false; |         private bool hasStarted = false; | ||||||
|  | @ -1254,9 +1243,9 @@ namespace MuzikaGromche | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     abstract class BaseEvent; |     public abstract class BaseEvent; | ||||||
| 
 | 
 | ||||||
|     class SetLightsColorEvent(Color color) : BaseEvent |     public class SetLightsColorEvent(Color color) : BaseEvent | ||||||
|     { |     { | ||||||
|         public readonly Color Color = color; |         public readonly Color Color = color; | ||||||
|         public override string ToString() |         public override string ToString() | ||||||
|  | @ -1265,7 +1254,7 @@ namespace MuzikaGromche | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     class SetLightsColorTransitionEvent(Color from, Color to, Easing easing, float t) |     public class SetLightsColorTransitionEvent(Color from, Color to, Easing easing, float t) | ||||||
|         : SetLightsColorEvent(Color.Lerp(from, to, Mathf.Clamp(easing.Eval(t), 0f, 1f))) |         : SetLightsColorEvent(Color.Lerp(from, to, Mathf.Clamp(easing.Eval(t), 0f, 1f))) | ||||||
|     { |     { | ||||||
|         // Additional context for debugging |         // Additional context for debugging | ||||||
|  | @ -1279,12 +1268,12 @@ namespace MuzikaGromche | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     class FlickerLightsEvent : BaseEvent |     public class FlickerLightsEvent : BaseEvent | ||||||
|     { |     { | ||||||
|         public override string ToString() => "Flicker"; |         public override string ToString() => "Flicker"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     class LyricsEvent(string text) : BaseEvent |     public class LyricsEvent(string text) : BaseEvent | ||||||
|     { |     { | ||||||
|         public readonly string Text = text; |         public readonly string Text = text; | ||||||
|         public override string ToString() |         public override string ToString() | ||||||
|  | @ -1293,14 +1282,14 @@ namespace MuzikaGromche | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     class WindUpZeroBeatEvent : BaseEvent |     public class WindUpZeroBeatEvent : BaseEvent | ||||||
|     { |     { | ||||||
|         public override string ToString() => "WindUp"; |         public override string ToString() => "WindUp"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Default C#/.NET remainder operator % returns negative result for negative input |     // Default C#/.NET remainder operator % returns negative result for negative input | ||||||
|     // which is unsuitable as an index for an array. |     // which is unsuitable as an index for an array. | ||||||
|     static class Mod |     public static class Mod | ||||||
|     { |     { | ||||||
|         public static int Positive(int x, int m) |         public static int Positive(int x, int m) | ||||||
|         { |         { | ||||||
|  | @ -1320,7 +1309,7 @@ namespace MuzikaGromche | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     readonly struct RandomWeightedIndex |     public readonly struct RandomWeightedIndex | ||||||
|     { |     { | ||||||
|         public RandomWeightedIndex(int[] weights) |         public RandomWeightedIndex(int[] weights) | ||||||
|         { |         { | ||||||
|  | @ -1407,7 +1396,7 @@ namespace MuzikaGromche | ||||||
|         readonly public int TotalWeights { get; } |         readonly public int TotalWeights { get; } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     static class SyncedEntryExtensions |     public static class SyncedEntryExtensions | ||||||
|     { |     { | ||||||
|         // Update local values on clients. Even though the clients couldn't |         // Update local values on clients. Even though the clients couldn't | ||||||
|         // edit them, they could at least see the new values. |         // edit them, they could at least see the new values. | ||||||
|  | @ -1420,7 +1409,7 @@ namespace MuzikaGromche | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     class Config : SyncedConfig2<Config> |     public class Config : SyncedConfig2<Config> | ||||||
|     { |     { | ||||||
|         public static ConfigEntry<bool> DisplayLyrics { get; private set; } = null!; |         public static ConfigEntry<bool> DisplayLyrics { get; private set; } = null!; | ||||||
| 
 | 
 | ||||||
|  | @ -1428,8 +1417,6 @@ namespace MuzikaGromche | ||||||
| 
 | 
 | ||||||
|         public static ConfigEntry<bool> SkipExplicitTracks { get; private set; } = null!; |         public static ConfigEntry<bool> SkipExplicitTracks { get; private set; } = null!; | ||||||
| 
 | 
 | ||||||
|         public static SyncedEntry<bool> OverrideSpawnRates { get; private set; } = null!; |  | ||||||
| 
 |  | ||||||
|         public static bool ShouldSkipWindingPhase { get; private set; } = false; |         public static bool ShouldSkipWindingPhase { get; private set; } = false; | ||||||
| 
 | 
 | ||||||
|         public static Palette? PaletteOverride { get; private set; } = null; |         public static Palette? PaletteOverride { get; private set; } = null; | ||||||
|  | @ -1443,7 +1430,7 @@ namespace MuzikaGromche | ||||||
|         public static float? ColorTransitionOutOverride { get; private set; } = null; |         public static float? ColorTransitionOutOverride { get; private set; } = null; | ||||||
|         public static string? ColorTransitionEasingOverride { get; private set; } = null; |         public static string? ColorTransitionEasingOverride { get; private set; } = null; | ||||||
| 
 | 
 | ||||||
|         internal Config(ConfigFile configFile) : base(PluginInfo.PLUGIN_GUID) |         public Config(ConfigFile configFile) : base(PluginInfo.PLUGIN_GUID) | ||||||
|         { |         { | ||||||
|             DisplayLyrics = configFile.Bind("General", "Display Lyrics", true, |             DisplayLyrics = configFile.Bind("General", "Display Lyrics", true, | ||||||
|                 new ConfigDescription("Display lyrics in the HUD tooltip when you hear the music.")); |                 new ConfigDescription("Display lyrics in the HUD tooltip when you hear the music.")); | ||||||
|  | @ -1458,11 +1445,6 @@ namespace MuzikaGromche | ||||||
|                 new ConfigDescription("When choosing tracks at random, skip the ones with Explicit Content/Lyrics.")); |                 new ConfigDescription("When choosing tracks at random, skip the ones with Explicit Content/Lyrics.")); | ||||||
|             LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(SkipExplicitTracks, requiresRestart: false)); |             LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(SkipExplicitTracks, requiresRestart: false)); | ||||||
| 
 | 
 | ||||||
|             OverrideSpawnRates = configFile.BindSyncedEntry("General", "Override Spawn Rates", false, |  | ||||||
|                 new ConfigDescription("Deviate from vanilla spawn rates to experience content of this mod more often.")); |  | ||||||
|             LethalConfigManager.AddConfigItem(new BoolCheckBoxConfigItem(OverrideSpawnRates.Entry, Default(new BoolCheckBoxOptions()))); |  | ||||||
|             CSyncHackAddSyncedEntry(OverrideSpawnRates); |  | ||||||
| 
 |  | ||||||
| #if DEBUG | #if DEBUG | ||||||
|             SetupEntriesToSkipWinding(configFile); |             SetupEntriesToSkipWinding(configFile); | ||||||
|             SetupEntriesForPaletteOverride(configFile); |             SetupEntriesForPaletteOverride(configFile); | ||||||
|  | @ -1777,26 +1759,19 @@ namespace MuzikaGromche | ||||||
|     // farAudio is during windup, Start overrides popGoesTheWeaselTheme |     // farAudio is during windup, Start overrides popGoesTheWeaselTheme | ||||||
|     // creatureVoice is when popped, Loop overrides screamingSFX |     // creatureVoice is when popped, Loop overrides screamingSFX | ||||||
|     [HarmonyPatch(typeof(JesterAI))] |     [HarmonyPatch(typeof(JesterAI))] | ||||||
|     static class JesterPatch |     internal class JesterPatch | ||||||
|     { |     { | ||||||
| #if DEBUG | #if DEBUG | ||||||
|         [HarmonyPatch(nameof(JesterAI.SetJesterInitialValues))] |         [HarmonyPatch(nameof(JesterAI.SetJesterInitialValues))] | ||||||
|         [HarmonyPostfix] |         [HarmonyPostfix] | ||||||
|         static void AlmostInstantFollowTimerPostfix(JesterAI __instance) |         public static void AlmostInstantFollowTimerPostfix(JesterAI __instance) | ||||||
|         { |         { | ||||||
|             __instance.beginCrankingTimer = 1f; |             __instance.beginCrankingTimer = 1f; | ||||||
|         } |         } | ||||||
| #endif | #endif | ||||||
| 
 |  | ||||||
|         class State |  | ||||||
|         { |  | ||||||
|             public required AudioSource farAudio; |  | ||||||
|             public required int previousState; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         [HarmonyPatch(nameof(JesterAI.Update))] |         [HarmonyPatch(nameof(JesterAI.Update))] | ||||||
|         [HarmonyPrefix] |         [HarmonyPrefix] | ||||||
|         static void JesterUpdatePrefix(JesterAI __instance, out State __state) |         public static void DoNotStopTheMusicPrefix(JesterAI __instance, out State __state) | ||||||
|         { |         { | ||||||
|             __state = new State |             __state = new State | ||||||
|             { |             { | ||||||
|  | @ -1818,7 +1793,7 @@ namespace MuzikaGromche | ||||||
| 
 | 
 | ||||||
|         [HarmonyPatch(nameof(JesterAI.Update))] |         [HarmonyPatch(nameof(JesterAI.Update))] | ||||||
|         [HarmonyPostfix] |         [HarmonyPostfix] | ||||||
|         static void JesterUpdatePostfix(JesterAI __instance, State __state) |         public static void DoNotStopTheMusic(JesterAI __instance, State __state) | ||||||
|         { |         { | ||||||
|             if (__instance.previousState == 1 && __state.previousState != 1) |             if (__instance.previousState == 1 && __state.previousState != 1) | ||||||
|             { |             { | ||||||
|  | @ -1900,7 +1875,9 @@ namespace MuzikaGromche | ||||||
|                         case LyricsEvent e: |                         case LyricsEvent e: | ||||||
|                             if (Plugin.LocalPlayerCanHearMusic(__instance)) |                             if (Plugin.LocalPlayerCanHearMusic(__instance)) | ||||||
|                             { |                             { | ||||||
|                                 Plugin.DisplayLyrics(e.Text); |                                 HUDManager.Instance.DisplayTip("[Lyrics]", e.Text); | ||||||
|  |                                 // Don't interrupt the music with constant HUD audio pings | ||||||
|  |                                 HUDManager.Instance.UIAudio.Stop(); | ||||||
|                             } |                             } | ||||||
|                             break; |                             break; | ||||||
|                     } |                     } | ||||||
|  | @ -1910,13 +1887,13 @@ namespace MuzikaGromche | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     [HarmonyPatch(typeof(EnemyAI))] |     [HarmonyPatch(typeof(EnemyAI))] | ||||||
|     static class EnemyAIPatch |     internal class EnemyAIPatch | ||||||
|     { |     { | ||||||
|         // JesterAI class does not override abstract method OnDestroy, |         // JesterAI class does not override abstract method OnDestroy, | ||||||
|         // so we have to patch its superclass directly. |         // so we have to patch its superclass directly. | ||||||
|         [HarmonyPatch(nameof(EnemyAI.OnDestroy))] |         [HarmonyPatch(nameof(EnemyAI.OnDestroy))] | ||||||
|         [HarmonyPrefix] |         [HarmonyPrefix] | ||||||
|         static void CleanUpOnDestroy(EnemyAI __instance) |         public static void CleanUpOnDestroy(EnemyAI __instance) | ||||||
|         { |         { | ||||||
|             if (__instance is JesterAI) |             if (__instance is JesterAI) | ||||||
|             { |             { | ||||||
|  | @ -1928,4 +1905,10 @@ namespace MuzikaGromche | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     internal class State | ||||||
|  |     { | ||||||
|  |         public required AudioSource farAudio; | ||||||
|  |         public required int previousState; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -9,17 +9,9 @@ using UnityEngine; | ||||||
| 
 | 
 | ||||||
| namespace MuzikaGromche | namespace MuzikaGromche | ||||||
| { | { | ||||||
|     static class PoweredLightsAnimators |     internal class PoweredLightsAnimators | ||||||
|     { |     { | ||||||
|         private const string PoweredLightTag = "PoweredLight"; |         private readonly record struct AnimatorPatch(string AnimatorContainerPath, RuntimeAnimatorController AnimatorController); | ||||||
| 
 |  | ||||||
|         private delegate void ManualPatch(GameObject animatorContainer); |  | ||||||
| 
 |  | ||||||
|         private readonly record struct AnimatorPatch( |  | ||||||
|             string AnimatorContainerPath, |  | ||||||
|             RuntimeAnimatorController AnimatorController, |  | ||||||
|             bool AddTagAndAnimator, |  | ||||||
|             ManualPatch? ManualPatch); |  | ||||||
| 
 | 
 | ||||||
|         private readonly record struct TilePatch(string TileName, AnimatorPatch[] Patches) |         private readonly record struct TilePatch(string TileName, AnimatorPatch[] Patches) | ||||||
|         { |         { | ||||||
|  | @ -27,17 +19,13 @@ namespace MuzikaGromche | ||||||
|             public readonly string TileCloneName = $"{TileName}(Clone)"; |             public readonly string TileCloneName = $"{TileName}(Clone)"; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private readonly record struct AnimatorPatchDescriptor( |         private readonly record struct AnimatorPatchDescriptor(string AnimatorContainerPath, string AnimatorControllerAssetPath) | ||||||
|             string AnimatorContainerPath, |  | ||||||
|             string AnimatorControllerAssetPath, |  | ||||||
|             bool AddTagAndAnimator = false, |  | ||||||
|             ManualPatch? ManualPatch = null) |  | ||||||
|         { |         { | ||||||
|             public AnimatorPatch Load(AssetBundle assetBundle) |             public AnimatorPatch Load(AssetBundle assetBundle) | ||||||
|             { |             { | ||||||
|                 var animationController = assetBundle.LoadAsset<RuntimeAnimatorController>(AnimatorControllerAssetPath) |                 var animationController = assetBundle.LoadAsset<RuntimeAnimatorController>(AnimatorControllerAssetPath) | ||||||
|                     ?? throw new FileNotFoundException($"RuntimeAnimatorController not found: {AnimatorControllerAssetPath}", AnimatorControllerAssetPath); |                     ?? throw new FileNotFoundException($"RuntimeAnimatorController not found: {AnimatorControllerAssetPath}", AnimatorControllerAssetPath); | ||||||
|                 return new(AnimatorContainerPath, animationController, AddTagAndAnimator, ManualPatch); |                 return new(AnimatorContainerPath, animationController); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | @ -57,10 +45,6 @@ namespace MuzikaGromche | ||||||
| 
 | 
 | ||||||
|         private static IDictionary<string, TilePatch> Patches = new Dictionary<string, TilePatch>(); |         private static IDictionary<string, TilePatch> Patches = new Dictionary<string, TilePatch>(); | ||||||
| 
 | 
 | ||||||
|         private static AudioClip AudioClipOn = null!; |  | ||||||
|         private static AudioClip AudioClipOff = null!; |  | ||||||
|         private static AudioClip AudioClipFlicker = null!; |  | ||||||
| 
 |  | ||||||
|         public static void Load() |         public static void Load() | ||||||
|         { |         { | ||||||
|             const string BundleFileName = "muzikagromche_poweredlightsanimators"; |             const string BundleFileName = "muzikagromche_poweredlightsanimators"; | ||||||
|  | @ -68,26 +52,16 @@ namespace MuzikaGromche | ||||||
|             var assetBundle = AssetBundle.LoadFromFile(bundlePath) |             var assetBundle = AssetBundle.LoadFromFile(bundlePath) | ||||||
|                 ?? throw new NullReferenceException("Failed to load bundle"); |                 ?? throw new NullReferenceException("Failed to load bundle"); | ||||||
| 
 | 
 | ||||||
|             AudioClipOn = assetBundle.LoadAsset<AudioClip>("Assets/LethalCompany/Mods/MuzikaGromche/AudioClips/LightOn.ogg"); |  | ||||||
|             AudioClipOff = assetBundle.LoadAsset<AudioClip>("Assets/LethalCompany/Mods/MuzikaGromche/AudioClips/LightOff.ogg"); |  | ||||||
|             AudioClipFlicker = assetBundle.LoadAsset<AudioClip>("Assets/LethalCompany/Mods/MuzikaGromche/AudioClips/LightFlicker.ogg"); |  | ||||||
| 
 |  | ||||||
|             const string BasePath = "Assets/LethalCompany/Mods/MuzikaGromche/AnimatorControllers/"; |             const string BasePath = "Assets/LethalCompany/Mods/MuzikaGromche/AnimatorControllers/"; | ||||||
| 
 | 
 | ||||||
|             const string PointLight4 = $"{BasePath}Point Light (4) (Patched).controller"; |             const string PointLight4 = $"{BasePath}Point Light (4) (Patched).controller"; | ||||||
|             const string MineshaftSpotlight = $"{BasePath}MineshaftSpotlight (Patched).controller"; |             const string MineshaftSpotlight = $"{BasePath}MineshaftSpotlight (Patched).controller"; | ||||||
|             const string LightbulbsLine = $"{BasePath}lightbulbsLineMesh (Patched).controller"; |             const string LightbulbsLine = $"{BasePath}lightbulbsLineMesh (Patched).controller"; | ||||||
|             const string CeilingFan = $"{BasePath}CeilingFan (originally GameObject) (Patched).controller"; |             const string CeilingFan = $"{BasePath}CeilingFan (originally GameObject) (Patched).controller"; | ||||||
|             const string LEDHangingLight = $"{BasePath}LEDHangingLight (Patched).controller"; | 
 | ||||||
|             const string MineshaftStartTileSpotlight = $"{BasePath}MineshaftStartTileSpotlight (New).controller"; |  | ||||||
| 
 | 
 | ||||||
|             TilePatchDescriptor[] descriptors = |             TilePatchDescriptor[] descriptors = | ||||||
|             [ |             [ | ||||||
|                 // any version |  | ||||||
|                 new("KitchenTile", [ |  | ||||||
|                     new("PoweredLightTypeB", PointLight4), |  | ||||||
|                     new("PoweredLightTypeB (1)", PointLight4), |  | ||||||
|                 ]), |  | ||||||
|                 // < v70 |                 // < v70 | ||||||
|                 new("ManorStartRoom", [ |                 new("ManorStartRoom", [ | ||||||
|                     new("ManorStartRoom/Chandelier/PoweredLightTypeB (1)", PointLight4), |                     new("ManorStartRoom/Chandelier/PoweredLightTypeB (1)", PointLight4), | ||||||
|  | @ -98,10 +72,6 @@ namespace MuzikaGromche | ||||||
|                     new("ManorStartRoomMesh/Chandelier/PoweredLightTypeB (1)", PointLight4), |                     new("ManorStartRoomMesh/Chandelier/PoweredLightTypeB (1)", PointLight4), | ||||||
|                     new("ManorStartRoomMesh/Chandelier2/PoweredLightTypeB", PointLight4), |                     new("ManorStartRoomMesh/Chandelier2/PoweredLightTypeB", PointLight4), | ||||||
|                 ]), |                 ]), | ||||||
|                 new("NarrowHallwayTile2x2", [ |  | ||||||
|                     new("MineshaftSpotlight (1)", MineshaftSpotlight), |  | ||||||
|                     new("MineshaftSpotlight (2)", MineshaftSpotlight), |  | ||||||
|                 ]), |  | ||||||
|                 new("BirthdayRoomTile", [ |                 new("BirthdayRoomTile", [ | ||||||
|                     new("Lights/MineshaftSpotlight", MineshaftSpotlight), |                     new("Lights/MineshaftSpotlight", MineshaftSpotlight), | ||||||
|                 ]), |                 ]), | ||||||
|  | @ -113,21 +83,6 @@ namespace MuzikaGromche | ||||||
|                     new("CeilingFanAnimContainer", CeilingFan), |                     new("CeilingFanAnimContainer", CeilingFan), | ||||||
|                     new("MineshaftSpotlight (1)", MineshaftSpotlight), |                     new("MineshaftSpotlight (1)", MineshaftSpotlight), | ||||||
|                 ]), |                 ]), | ||||||
|                 new("GarageTile", [ |  | ||||||
|                     new("HangingLEDBarLight (3)", LEDHangingLight), |  | ||||||
|                     new("HangingLEDBarLight (4)", LEDHangingLight, |  | ||||||
|                         // This HangingLEDBarLight's IndirectLight is wrongly named, so animator couldn't find it |  | ||||||
|                         ManualPatch: RenameGameObjectPatch("IndirectLight (1)", "IndirectLight")), |  | ||||||
|                 ]), |  | ||||||
|                 new("PoolTile", [ |  | ||||||
|                     new("PoolLights/HangingLEDBarLight", LEDHangingLight), |  | ||||||
|                     new("PoolLights/HangingLEDBarLight (4)", LEDHangingLight), |  | ||||||
|                     new("PoolLights/HangingLEDBarLight (5)", LEDHangingLight), |  | ||||||
|                 ]), |  | ||||||
|                 new("MineshaftStartTile", [ |  | ||||||
|                     new("Cylinder.001 (1)", MineshaftStartTileSpotlight, AddTagAndAnimator: true), |  | ||||||
|                     new("Cylinder.001 (2)", MineshaftStartTileSpotlight, AddTagAndAnimator: true), |  | ||||||
|                 ]), |  | ||||||
|             ]; |             ]; | ||||||
| 
 | 
 | ||||||
|             Patches = descriptors |             Patches = descriptors | ||||||
|  | @ -157,41 +112,7 @@ namespace MuzikaGromche | ||||||
| #pragma warning restore CS0162 // Unreachable code detected | #pragma warning restore CS0162 // Unreachable code detected | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     GameObject animationContainer = animationContainerTransform.gameObject; |                     var animator = animationContainerTransform.gameObject.GetComponent<Animator>(); | ||||||
|                     Animator animator = animationContainer.GetComponent<Animator>(); |  | ||||||
| 
 |  | ||||||
|                     if (patch.AddTagAndAnimator) |  | ||||||
|                     { |  | ||||||
|                         animationContainer.tag = PoweredLightTag; |  | ||||||
|                         if (animator == null) |  | ||||||
|                         { |  | ||||||
|                             animator = animationContainer.AddComponent<Animator>(); |  | ||||||
|                         } |  | ||||||
|                         if (!animationContainer.TryGetComponent<PlayAudioAnimationEvent>(out var audioScript)) |  | ||||||
|                         { |  | ||||||
|                             audioScript = animationContainer.AddComponent<PlayAudioAnimationEvent>(); |  | ||||||
|                             audioScript.audioClip = AudioClipOn; |  | ||||||
|                             audioScript.audioClip2 = AudioClipOff; |  | ||||||
|                             audioScript.audioClip3 = AudioClipFlicker; |  | ||||||
|                         } |  | ||||||
|                         if (!animationContainer.TryGetComponent<AudioSource>(out var audioSource)) |  | ||||||
|                         { |  | ||||||
|                             // Copy from an existing AudioSource of another light animator |  | ||||||
|                             var otherSource = tile.gameObject.GetComponentInChildren<AudioSource>(); |  | ||||||
|                             if (otherSource != null) |  | ||||||
|                             { |  | ||||||
|                                 audioSource = animationContainer.AddComponent<AudioSource>(); |  | ||||||
|                                 audioSource.spatialBlend = 1; |  | ||||||
|                                 audioSource.playOnAwake = false; |  | ||||||
|                                 audioSource.outputAudioMixerGroup = otherSource.outputAudioMixerGroup; |  | ||||||
|                                 audioSource.spread = otherSource.spread; |  | ||||||
|                                 audioSource.rolloffMode = otherSource.rolloffMode; |  | ||||||
|                                 audioSource.maxDistance = otherSource.maxDistance; |  | ||||||
|                                 audioSource.SetCustomCurve(AudioSourceCurveType.CustomRolloff, otherSource.GetCustomCurve(AudioSourceCurveType.CustomRolloff)); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     if (animator == null) |                     if (animator == null) | ||||||
|                     { |                     { | ||||||
| #if DEBUG | #if DEBUG | ||||||
|  | @ -202,89 +123,21 @@ namespace MuzikaGromche | ||||||
| #pragma warning restore CS0162 // Unreachable code detected | #pragma warning restore CS0162 // Unreachable code detected | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|                     patch.ManualPatch?.Invoke(animationContainer); |  | ||||||
| 
 |  | ||||||
|                     animator.runtimeAnimatorController = patch.AnimatorController; |                     animator.runtimeAnimatorController = patch.AnimatorController; | ||||||
|                     Debug.Log($"{nameof(MuzikaGromche)} {nameof(PoweredLightsAnimatorsPatch)} {tilePatch.TileName}/{patch.AnimatorContainerPath}: Replaced animator controller"); |                     Debug.Log($"{nameof(MuzikaGromche)} {nameof(PoweredLightsAnimatorsPatch)} {tilePatch.TileName}/{patch.AnimatorContainerPath}: Replaced animator controller"); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         private static ManualPatch RenameGameObjectPatch(string relativePath, string newName) => animatorContainer => |  | ||||||
|         { |  | ||||||
|             var targetObject = animatorContainer.transform.Find(relativePath)?.gameObject; |  | ||||||
|             if (targetObject == null) |  | ||||||
|             { |  | ||||||
| #if DEBUG |  | ||||||
|                 throw new NullReferenceException($"{animatorContainer.name}/{relativePath}: GameObject not found!"); |  | ||||||
| #endif |  | ||||||
| #pragma warning disable CS0162 // Unreachable code detected |  | ||||||
|                 return; |  | ||||||
| #pragma warning restore CS0162 // Unreachable code detected |  | ||||||
|             } |  | ||||||
|             targetObject.name = newName; |  | ||||||
|             Debug.Log($"{nameof(MuzikaGromche)} {nameof(PoweredLightsAnimatorsPatch)} {animatorContainer.name}/{relativePath}: Renamed GameObject"); |  | ||||||
|         }; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     [HarmonyPatch(typeof(Tile))] |     [HarmonyPatch(typeof(Tile))] | ||||||
|     static class PoweredLightsAnimatorsPatch |     internal class PoweredLightsAnimatorsPatch | ||||||
|     { |     { | ||||||
|         [HarmonyPatch(nameof(Tile.AddTriggerVolume))] |         [HarmonyPatch("AddTriggerVolume")] | ||||||
|         [HarmonyPostfix] |         [HarmonyPostfix] | ||||||
|         static void OnAddTriggerVolume(Tile __instance) |         public static void OnAddTriggerVolume(Tile __instance) | ||||||
|         { |         { | ||||||
|             PoweredLightsAnimators.Patch(__instance); |             PoweredLightsAnimators.Patch(__instance); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     [HarmonyPatch(typeof(RoundManager))] |  | ||||||
|     static class AllPoweredLightsPatch |  | ||||||
|     { |  | ||||||
|         // Vanilla method assumes that GameObjects with tag "PoweredLight" only contain a single Light component each. |  | ||||||
|         // This is, however, not true for certains double-light setups, such as: |  | ||||||
|         // - double PointLight (even though one of them is 'Point' another is 'Spot') inside CeilingFanAnimContainer in BedroomTile/BedroomTileB; |  | ||||||
|         // - MineshaftSpotlight when it has not only `Point Light` but also `IndirectLight` in BirthdayRoomTile; |  | ||||||
|         // - (maybe more?) |  | ||||||
|         // In order to fix that, replace singular GetComponentInChildren<Light> with plural GetComponentsInChildren<Light> version. |  | ||||||
|         [HarmonyPatch(nameof(RoundManager.RefreshLightsList))] |  | ||||||
|         [HarmonyPrefix] |  | ||||||
|         static bool OnRefreshLightsList(RoundManager __instance) |  | ||||||
|         { |  | ||||||
|             RefreshLightsListPatched(__instance); |  | ||||||
|             // Skip the original method |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         static void RefreshLightsListPatched(RoundManager self) |  | ||||||
|         { |  | ||||||
|             // Reusable list to reduce allocations |  | ||||||
|             List<Light> lights = []; |  | ||||||
| 
 |  | ||||||
|             self.allPoweredLights.Clear(); |  | ||||||
|             self.allPoweredLightsAnimators.Clear(); |  | ||||||
| 
 |  | ||||||
|             GameObject[] gameObjects = GameObject.FindGameObjectsWithTag("PoweredLight"); |  | ||||||
|             if (gameObjects == null) |  | ||||||
|             { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             foreach (var gameObject in gameObjects) |  | ||||||
|             { |  | ||||||
|                 Animator animator = gameObject.GetComponentInChildren<Animator>(); |  | ||||||
|                 if (!(animator == null)) |  | ||||||
|                 { |  | ||||||
|                     self.allPoweredLightsAnimators.Add(animator); |  | ||||||
|                     // Patched section: Use list instead of singular GetComponentInChildren<Light> |  | ||||||
|                     gameObject.GetComponentsInChildren(includeInactive: true, lights); |  | ||||||
|                     self.allPoweredLights.AddRange(lights); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             foreach (var animator in self.allPoweredLightsAnimators) |  | ||||||
|             { |  | ||||||
|                 animator.SetFloat("flickerSpeed", UnityEngine.Random.Range(0.6f, 1.4f)); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,69 +0,0 @@ | ||||||
| using HarmonyLib; |  | ||||||
| using System; |  | ||||||
| using UnityEngine; |  | ||||||
| 
 |  | ||||||
| namespace MuzikaGromche |  | ||||||
| { |  | ||||||
|     [HarmonyPatch(typeof(RoundManager))] |  | ||||||
|     static class SpawnRatePatch |  | ||||||
|     { |  | ||||||
|         const string JesterEnemyName = "Jester"; |  | ||||||
| 
 |  | ||||||
|         // If set to null, do not override spawn chances. Otherwise, it is an index of Jester in RoundManager.currentLevel.Enemies |  | ||||||
|         static int? JesterEnemyIndex = null; |  | ||||||
|         static float? SpawnTime = null; |  | ||||||
| 
 |  | ||||||
|         [HarmonyPatch(nameof(RoundManager.AssignRandomEnemyToVent))] |  | ||||||
|         [HarmonyPrefix] |  | ||||||
|         static void AssignRandomEnemyToVentPrefix(RoundManager __instance, EnemyVent vent, float spawnTime) |  | ||||||
|         { |  | ||||||
|             if (!Config.OverrideSpawnRates.Value) |  | ||||||
|             { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             var index = __instance.currentLevel.Enemies.FindIndex(enemy => enemy.enemyType.enemyName == JesterEnemyName); |  | ||||||
|             if (index == -1) |  | ||||||
|             { |  | ||||||
|                 return; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             JesterEnemyIndex = index; |  | ||||||
|             SpawnTime = spawnTime; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         [HarmonyPatch(nameof(RoundManager.AssignRandomEnemyToVent))] |  | ||||||
|         [HarmonyPostfix] |  | ||||||
|         static void AssignRandomEnemyToVentPostfix(RoundManager __instance, EnemyVent vent, float spawnTime) |  | ||||||
|         { |  | ||||||
|             JesterEnemyIndex = null; |  | ||||||
|             SpawnTime = null; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         [HarmonyPatch(nameof(RoundManager.GetRandomWeightedIndex))] |  | ||||||
|         [HarmonyPostfix] |  | ||||||
|         static void GetRandomWeightedIndexPostfix(RoundManager __instance, ref int[] weights, ref System.Random randomSeed) |  | ||||||
|         { |  | ||||||
|             if (JesterEnemyIndex is int index && SpawnTime is float spawnTime) |  | ||||||
|             { |  | ||||||
|                 if (__instance.EnemyCannotBeSpawned(index)) |  | ||||||
|                 { |  | ||||||
|                     return; |  | ||||||
|                 } |  | ||||||
| 
 |  | ||||||
|                 var minMultiplierTime = 3 * __instance.timeScript.lengthOfHours; |  | ||||||
|                 var maxMultiplierTime = 7 * __instance.timeScript.lengthOfHours; |  | ||||||
|                 var normalizedMultiplierTime = Math.Clamp((spawnTime - minMultiplierTime) / (maxMultiplierTime - minMultiplierTime), 0f, 1f); |  | ||||||
| 
 |  | ||||||
|                 // TODO: Try Expo function instead of Lerp? |  | ||||||
|                 var minMultiplier = Mathf.Max(1, __instance.minEnemiesToSpawn); |  | ||||||
|                 var maxMultiplier = 9 + __instance.minEnemiesToSpawn; |  | ||||||
|                 var multiplier = Mathf.Lerp(minMultiplier, maxMultiplier, normalizedMultiplierTime); |  | ||||||
| 
 |  | ||||||
|                 var newWeight = (int)(weights[index] * multiplier); |  | ||||||
|                 Debug.Log($"{nameof(MuzikaGromche)} Overriding Jester spawn weight {weights[index]} * {multiplier} => {newWeight}"); |  | ||||||
|                 weights[index] = newWeight; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
										
											Binary file not shown.
										
									
								
							
		Loading…
	
		Reference in New Issue