diff --git a/CHANGELOG.md b/CHANGELOG.md index e3fa9a5..f507207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - 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 diff --git a/MuzikaGromche/DiscoBallManager.cs b/MuzikaGromche/DiscoBallManager.cs index 2068c1d..5b14068 100644 --- a/MuzikaGromche/DiscoBallManager.cs +++ b/MuzikaGromche/DiscoBallManager.cs @@ -1,4 +1,5 @@ using DunGen; +using HarmonyLib; using System; using System.Collections.Generic; using System.IO; @@ -11,53 +12,18 @@ namespace MuzikaGromche public static class DiscoBallManager { // A struct holding a disco ball container object and the name of a tile for which it was designed. - private readonly record struct Data(string TileName, GameObject DiscoBallContainer) + private readonly record struct TilePatch(string TileName, GameObject DiscoBallContainer) { // We are specifically looking for cloned tiles, not the original prototypes. public readonly string TileCloneName = $"{TileName}(Clone)"; } - private static readonly List Containers = []; - private static readonly List InstantiatedContainers = []; + private static TilePatch[] Patches = []; - public static void Initialize() - { - const string BundleFileName = "muzikagromche_discoball"; - string bundlePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), BundleFileName); - var assetBundle = AssetBundle.LoadFromFile(bundlePath) - ?? throw new NullReferenceException("Failed to load bundle"); + private static readonly List CachedDiscoBalls = []; + private static readonly List CachedDiscoBallAnimators = []; - 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 = assetBundle.LoadAsset(prefabPath); - Containers.Add(new(tileName, container)); - } - } - - public static void Enable() - { - // Just in case - Disable(); - - var query = from tile in Resources.FindObjectsOfTypeAll() - 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 = [ + private static readonly string[] AnimatorContainersNames = [ "DiscoBallProp/AnimContainer", "DiscoBallProp1/AnimContainer", "DiscoBallProp2/AnimContainer", @@ -66,29 +32,134 @@ namespace MuzikaGromche "DiscoBallProp5/AnimContainer", ]; - private static void Enable(Tile tile, Data container) + public static void Load() { - Debug.Log($"{nameof(MuzikaGromche)} {nameof(DiscoBallManager)} Enabling at '{tile.gameObject.name}'"); - var discoBall = UnityEngine.Object.Instantiate(container.DiscoBallContainer, tile.transform); - InstantiatedContainers.Add(discoBall); + const string BundleFileName = "muzikagromche_discoball"; + string bundlePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), BundleFileName); + var assetBundle = AssetBundle.LoadFromFile(bundlePath) + ?? throw new NullReferenceException("Failed to load bundle"); - foreach (var animatorName in animatorNames) + (string PrefabPath, string TileName)[] patchDescriptors = + [ + ("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(d.PrefabPath)) + )]; + } + + internal static void Patch(Tile tile) + { + var query = from patch in Patches + where tile.gameObject.name == patch.TileCloneName + select patch; + + // Should be just one, but FirstOrDefault() isn't usable with structs + foreach (var patch in query) { - if (discoBall.transform.Find(animatorName)?.gameObject is GameObject animator) - { - animator.GetComponent().SetBool("on", true); - } + Patch(tile, patch); } } + 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 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(); + 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() { - foreach (var discoBall in InstantiatedContainers) - { - Debug.Log($"{nameof(MuzikaGromche)} {nameof(DiscoBallManager)}: Disabling {discoBall.name}"); - UnityEngine.Object.Destroy(discoBall); - } - InstantiatedContainers.Clear(); + Toggle(false); + } + + internal static void Clear() + { + Debug.Log($"{nameof(MuzikaGromche)} {nameof(DiscoBallManager)} Clearing {CachedDiscoBalls.Count} disco balls & {CachedDiscoBallAnimators.Count} animators"); + CachedDiscoBallAnimators.Clear(); + CachedDiscoBalls.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(); } } } diff --git a/MuzikaGromche/Plugin.cs b/MuzikaGromche/Plugin.cs index 2e089b6..17b29ee 100644 --- a/MuzikaGromche/Plugin.cs +++ b/MuzikaGromche/Plugin.cs @@ -544,13 +544,15 @@ namespace MuzikaGromche track.LoadedLoop = DownloadHandlerAudioClip.GetContent(requests[i * 2 + 1]); } Config = new Config(base.Config); - DiscoBallManager.Initialize(); + DiscoBallManager.Load(); PoweredLightsAnimators.Load(); var harmony = new Harmony(PluginInfo.PLUGIN_NAME); harmony.PatchAll(typeof(JesterPatch)); harmony.PatchAll(typeof(EnemyAIPatch)); harmony.PatchAll(typeof(PoweredLightsAnimatorsPatch)); harmony.PatchAll(typeof(AllPoweredLightsPatch)); + harmony.PatchAll(typeof(DiscoBallTilePatch)); + harmony.PatchAll(typeof(DiscoBallDespawnPatch)); } else {