diff --git a/CHANGELOG.md b/CHANGELOG.md index f507207..120f437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,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. +- Added an opt-in config option to increase certain spawn rate to experience content of this mod more often. ## MuzikaGromche 13.37.1337 - Photosensitivity Warning Edition diff --git a/MuzikaGromche/Plugin.cs b/MuzikaGromche/Plugin.cs index 17b29ee..6f4adbf 100644 --- a/MuzikaGromche/Plugin.cs +++ b/MuzikaGromche/Plugin.cs @@ -553,6 +553,7 @@ namespace MuzikaGromche harmony.PatchAll(typeof(AllPoweredLightsPatch)); harmony.PatchAll(typeof(DiscoBallTilePatch)); harmony.PatchAll(typeof(DiscoBallDespawnPatch)); + harmony.PatchAll(typeof(SpawnRatePatch)); } else { @@ -1427,6 +1428,8 @@ namespace MuzikaGromche public static ConfigEntry SkipExplicitTracks { get; private set; } = null!; + public static SyncedEntry OverrideSpawnRates { get; private set; } = null!; + public static bool ShouldSkipWindingPhase { get; private set; } = false; public static Palette? PaletteOverride { get; private set; } = null; @@ -1455,6 +1458,11 @@ namespace MuzikaGromche new ConfigDescription("When choosing tracks at random, skip the ones with Explicit Content/Lyrics.")); 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 SetupEntriesToSkipWinding(configFile); SetupEntriesForPaletteOverride(configFile); diff --git a/MuzikaGromche/SpawnRateManager.cs b/MuzikaGromche/SpawnRateManager.cs new file mode 100644 index 0000000..66a1591 --- /dev/null +++ b/MuzikaGromche/SpawnRateManager.cs @@ -0,0 +1,88 @@ +using HarmonyLib; +using System; +using UnityEngine; + +namespace MuzikaGromche +{ + [HarmonyPatch(typeof(RoundManager))] + static class SpawnRatePatch + { + const string JesterEnemyName = "Jester"; + + // GetRandomWeightedIndex is not only called from AssignRandomEnemyToVent, + // so in order to differentiate it from other calls, prefix assigns these + // global variables, and postfix cleans them up. + + // If set to null, do not override spawn chances. + // Otherwise, it is an index of Jester in RoundManager.currentLevel.Enemies + static int? EnemyIndex = null; + static float SpawnTime = 0f; + + [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; + } + + EnemyIndex = index; + SpawnTime = spawnTime; + } + + [HarmonyPatch(nameof(RoundManager.AssignRandomEnemyToVent))] + [HarmonyPostfix] + static void AssignRandomEnemyToVentPostfix(RoundManager __instance, EnemyVent vent, float spawnTime) + { + EnemyIndex = null; + SpawnTime = 0f; + } + + [HarmonyPatch(nameof(RoundManager.GetRandomWeightedIndex))] + [HarmonyPrefix] + static void GetRandomWeightedIndexPostfix(RoundManager __instance, int[] weights, System.Random randomSeed) + { + if (EnemyIndex is int index) + { + if (__instance.EnemyCannotBeSpawned(index)) + { + return; + } + + // 0 == 6:00 AM + // 60 == 7:00 AM + // 100 == 7:40 AM (Cycle #1) + // 120 == 8:00 AM + // 180 == 9:00 AM (Cycle #2) + // 300 == 11:00 AM (Cycle #3) + // 420 == 1:00 PM (Cycle #4) + // 540 == 3:00 PM (Cycle #5) + // 660 ~= 5:00 PM + // 780 ~= 7:00 PM + // 900 ~= 9:00 PM + // 1020 ~= 11:00 PM + // 1080 == 12:00 AM + const float minMultiplierTime = 200f; // 9:20 AM + const float maxMultiplierTime = 500f; // 2:00 PM + var normalizedMultiplierTime = Mathf.Clamp((SpawnTime - minMultiplierTime) / (maxMultiplierTime - minMultiplierTime), 0f, 1f); + // Start slowly, then escalate it quickly + normalizedMultiplierTime = Easing.InCubic.Eval(normalizedMultiplierTime); + + const float minMultiplier = 1f; + const float maxMultiplier = 15f; + var multiplier = Mathf.Lerp(minMultiplier, maxMultiplier, normalizedMultiplierTime); + + var newWeight = Mathf.FloorToInt(weights[index] * multiplier); + Debug.Log($"{nameof(MuzikaGromche)} {nameof(SpawnRatePatch)} Overriding spawn weight[{index}] {weights[index]} * {multiplier} => {newWeight} for t={SpawnTime}"); + weights[index] = newWeight; + } + } + } +} diff --git a/README.md b/README.md index 9a2e658..1618a6c 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Speaking of dependencies, [`V70PoweredLights_Fix`] is not strictly required, but Configuration integrates with [`LethalConfig`] mod. +If you are just trying out this mod for the first time, or want to experience it more often, consider toggling ON the "Override Spawn Rates" config entry. Otherwise it might take a frustratingly long time to find out what you need to find out. + Track selection options are only configurable by host player and only while orbiting. Any player can change their personal preferences locally.