From f20af1f91cd8e6dd3ee24bd843c136f73e6b5d9d Mon Sep 17 00:00:00 2001 From: James Armstrong Date: Wed, 24 Dec 2025 19:04:47 -0800 Subject: [PATCH 1/5] Initialize TimeService and implement MoTD timer --- Nuclei/Events/ServerEvents.cs | 4 +++- Nuclei/Features/MissionService.cs | 2 +- Nuclei/Features/TimeService.cs | 11 ++++++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Nuclei/Events/ServerEvents.cs b/Nuclei/Events/ServerEvents.cs index 7ff1cd1..bc7d6fb 100644 --- a/Nuclei/Events/ServerEvents.cs +++ b/Nuclei/Events/ServerEvents.cs @@ -1,4 +1,5 @@ using System; +using Nuclei.Features; namespace Nuclei.Events; @@ -15,6 +16,7 @@ public static class ServerEvents internal static void OnServerStarted() { ServerStarted?.Invoke(); + TimeService.Initialize(); } /// @@ -26,4 +28,4 @@ internal static void OnServerStopped() { ServerStopped?.Invoke(); } -} \ No newline at end of file +} diff --git a/Nuclei/Features/MissionService.cs b/Nuclei/Features/MissionService.cs index 49ea733..336d2e6 100644 --- a/Nuclei/Features/MissionService.cs +++ b/Nuclei/Features/MissionService.cs @@ -47,7 +47,7 @@ public static class MissionService /// /// The current mission time. /// - public static float CurrentMissionTime => Globals.MissionManagerInstance.missionTime; + public static float CurrentMissionTime => Globals.MissionManagerInstance.MissionTime; /// /// Gets all Mission Keys as an IEnumerable. diff --git a/Nuclei/Features/TimeService.cs b/Nuclei/Features/TimeService.cs index 885c8f3..d0e3c42 100644 --- a/Nuclei/Features/TimeService.cs +++ b/Nuclei/Features/TimeService.cs @@ -54,6 +54,15 @@ private void FixedUpdate() _lastTime = currentTime; TimeEvents.OnEverySecond(); + + + // MoTD + var motdFreq = NucleiConfig.MotDFrequency!.Value; + if (currentTime % motdFreq == 0) + { + ChatService.SendMotD(); + } + if (currentTime % 3600 == 0) { TimeEvents.OnEveryHour(); @@ -85,4 +94,4 @@ private void FixedUpdate() TimeEvents.OnEvery30Seconds(); } } -} \ No newline at end of file +} From b4e3bd4194408f5c6ba8846c0864661b902c0735 Mon Sep 17 00:00:00 2001 From: James Armstrong Date: Sun, 4 Jan 2026 00:40:46 -0800 Subject: [PATCH 2/5] Add weather randomizer --- Nuclei/Features/WeatherRandomizerService.cs | 37 +++++++++++++++++++++ Nuclei/Nuclei.csproj | 1 + Nuclei/Patches/MissionRotationPatches.cs | 23 +++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 Nuclei/Features/WeatherRandomizerService.cs create mode 100644 Nuclei/Patches/MissionRotationPatches.cs diff --git a/Nuclei/Features/WeatherRandomizerService.cs b/Nuclei/Features/WeatherRandomizerService.cs new file mode 100644 index 0000000..2235ff1 --- /dev/null +++ b/Nuclei/Features/WeatherRandomizerService.cs @@ -0,0 +1,37 @@ +using System; +using System.IO; +using System.Linq; +using System.Text.Json; + + namespace Nuclei.Features; + + public class WeatherRandomizerService + { + public const string MissionFolderSource = "NuclearOption-Missions"; + public static void RandomizeWeather(string missionName) + { + var currentMissionDir = Directory.GetDirectories(MissionFolderSource).First(x => x.Contains(missionName)); + Nuclei.Logger?.LogInfo($"currentMissionDir: {currentMissionDir} "); + var currentMissionFile = $"{currentMissionDir}/{missionName}.json"; + Nuclei.Logger?.LogInfo($"currentMissionFile: {currentMissionFile} "); + + string json = File.ReadAllText(currentMissionFile); + var parsedJson = System.Text.Json.Nodes.JsonNode.Parse(json); + + var writerOptions = new JsonSerializerOptions() + { + WriteIndented = true + }; + + Random rnd = new Random(); + parsedJson["environment"]["timeOfDay"] = rnd.Next(3,13); + parsedJson["timeFactor"] = 2.0; + parsedJson["weatherIntensity"] = rnd.NextDouble() * 0.8; + parsedJson["cloudAltitude"] = 500 + rnd.NextDouble() * 1000; + parsedJson["cloudAltitude"] = 500 + rnd.NextDouble() * 1000; + parsedJson["windSpeed"] = rnd.NextDouble() * 4; + parsedJson["windTurbulence"] = rnd.NextDouble() * 1; + parsedJson["windHeading"] = rnd.Next(0, 360); + File.WriteAllText(currentMissionFile, parsedJson.ToJsonString(writerOptions)); + } + } \ No newline at end of file diff --git a/Nuclei/Nuclei.csproj b/Nuclei/Nuclei.csproj index 7b3033f..4f71a60 100644 --- a/Nuclei/Nuclei.csproj +++ b/Nuclei/Nuclei.csproj @@ -52,6 +52,7 @@ + diff --git a/Nuclei/Patches/MissionRotationPatches.cs b/Nuclei/Patches/MissionRotationPatches.cs new file mode 100644 index 0000000..0ac528d --- /dev/null +++ b/Nuclei/Patches/MissionRotationPatches.cs @@ -0,0 +1,23 @@ +using HarmonyLib; +using Mirage; +using NuclearOption.Chat; +using NuclearOption.DedicatedServer; +using Nuclei.Features; +using Nuclei.Features.Commands; +using Nuclei.Helpers; + +namespace Nuclei.Patches; + +[HarmonyPatch(typeof(MissionRotation))] +[HarmonyPriority(Priority.First)] +[HarmonyWrapSafe] +internal static class MissionRotationPatches +{ + [HarmonyPrefix] + [HarmonyPatch(nameof(MissionRotation.GetNext))] + private static void GetNextPrefix() + { + MissionOptions nextMission = Globals.DedicatedServerManagerInstance.missionRotation.PeakNext(); + WeatherRandomizerService.RandomizeWeather(nextMission.Key.Name); + } +} \ No newline at end of file From 7230aed8bdef494bc8a4fc8d638adaf4b9c405e6 Mon Sep 17 00:00:00 2001 From: jmarmstrong1207 Date: Tue, 6 Jan 2026 15:22:36 -0800 Subject: [PATCH 3/5] Make Weather Randomizer configurable --- Nuclei/Features/NucleiConfig.cs | 31 +++++++++++++++++++++ Nuclei/Features/WeatherRandomizerService.cs | 4 +-- Nuclei/Patches/MissionRotationPatches.cs | 1 + 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Nuclei/Features/NucleiConfig.cs b/Nuclei/Features/NucleiConfig.cs index 6acfb49..0beef18 100644 --- a/Nuclei/Features/NucleiConfig.cs +++ b/Nuclei/Features/NucleiConfig.cs @@ -1,5 +1,8 @@ +using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text.Json.Nodes; using BepInEx.Configuration; using Nuclei.Enums; using Nuclei.Helpers; @@ -62,6 +65,9 @@ public static class NucleiConfig internal static ConfigEntry? RefreshServerNamePeriodically; internal const bool DefaultRefreshServerNamePeriodically = true; + + internal static ConfigEntry? RandomizeWeather; + internal const bool DefaultRandomizeWeather = false; internal static ConfigEntry? TargetFrameRate; internal const short DefaultTargetFrameRate = 120; @@ -164,6 +170,31 @@ internal static void InitSettings(ConfigFile config) CommandPrefix = config.Bind(GeneralSection, "CommandPrefix", DefaultCommandPrefix, "What to use as the command prefix (the character at the start of a command)."); Nuclei.Logger?.LogDebug($"CommandPrefix: {CommandPrefix.Value}"); + + RandomizeWeather = config.Bind(GeneralSection, "RandomizeWeather", DefaultRandomizeWeather, + "Randomize weather by modifying the .json mission file directly. This requires the missions to be in " + + "the mission folder you assigned in DedicatedServerConfig.json, meaning all missions' MissionGroup must be User, not BuiltIn"); + + if (RandomizeWeather.Value) + { + try + { + var json = File.ReadAllText("DedicatedServerConfig.json"); + var parsedJson = JsonNode.Parse(json)!; + + if (parsedJson["MissionDirectory"] == null) + throw new Exception("MissionDirectory in DedicatedServerConfig.json does not exist. Please set it up " + + "before enabling Randomize Weather"); + WeatherRandomizerService.MissionDir = (string)parsedJson["MissionDirectory"]!; + } + catch (FileNotFoundException e) + { + Nuclei.Logger?.LogError(e); + throw; + } + } + + Nuclei.Logger?.LogDebug($"CommandPrefix: {CommandPrefix.Value}"); Nuclei.Logger?.LogDebug("Loaded settings!"); } diff --git a/Nuclei/Features/WeatherRandomizerService.cs b/Nuclei/Features/WeatherRandomizerService.cs index 2235ff1..f874f75 100644 --- a/Nuclei/Features/WeatherRandomizerService.cs +++ b/Nuclei/Features/WeatherRandomizerService.cs @@ -7,10 +7,10 @@ namespace Nuclei.Features; public class WeatherRandomizerService { - public const string MissionFolderSource = "NuclearOption-Missions"; + internal static string MissionDir = null!; public static void RandomizeWeather(string missionName) { - var currentMissionDir = Directory.GetDirectories(MissionFolderSource).First(x => x.Contains(missionName)); + var currentMissionDir = Directory.GetDirectories(MissionDir).First(x => x.Contains(missionName)); Nuclei.Logger?.LogInfo($"currentMissionDir: {currentMissionDir} "); var currentMissionFile = $"{currentMissionDir}/{missionName}.json"; Nuclei.Logger?.LogInfo($"currentMissionFile: {currentMissionFile} "); diff --git a/Nuclei/Patches/MissionRotationPatches.cs b/Nuclei/Patches/MissionRotationPatches.cs index 0ac528d..3823d3d 100644 --- a/Nuclei/Patches/MissionRotationPatches.cs +++ b/Nuclei/Patches/MissionRotationPatches.cs @@ -17,6 +17,7 @@ internal static class MissionRotationPatches [HarmonyPatch(nameof(MissionRotation.GetNext))] private static void GetNextPrefix() { + if (!NucleiConfig.RandomizeWeather!.Value) return; MissionOptions nextMission = Globals.DedicatedServerManagerInstance.missionRotation.PeakNext(); WeatherRandomizerService.RandomizeWeather(nextMission.Key.Name); } From 8dae16ae2f22d377aa17c71a938178ca4fe0fc4b Mon Sep 17 00:00:00 2001 From: James Armstrong Date: Sun, 11 Jan 2026 13:24:51 -0800 Subject: [PATCH 4/5] Fix randomizer not changing other weather properties --- Nuclei/Features/WeatherRandomizerService.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Nuclei/Features/WeatherRandomizerService.cs b/Nuclei/Features/WeatherRandomizerService.cs index f874f75..2299cbc 100644 --- a/Nuclei/Features/WeatherRandomizerService.cs +++ b/Nuclei/Features/WeatherRandomizerService.cs @@ -16,7 +16,7 @@ public static void RandomizeWeather(string missionName) Nuclei.Logger?.LogInfo($"currentMissionFile: {currentMissionFile} "); string json = File.ReadAllText(currentMissionFile); - var parsedJson = System.Text.Json.Nodes.JsonNode.Parse(json); + var parsedJson = System.Text.Json.Nodes.JsonNode.Parse(json)!; var writerOptions = new JsonSerializerOptions() { @@ -24,14 +24,15 @@ public static void RandomizeWeather(string missionName) }; Random rnd = new Random(); - parsedJson["environment"]["timeOfDay"] = rnd.Next(3,13); - parsedJson["timeFactor"] = 2.0; - parsedJson["weatherIntensity"] = rnd.NextDouble() * 0.8; - parsedJson["cloudAltitude"] = 500 + rnd.NextDouble() * 1000; - parsedJson["cloudAltitude"] = 500 + rnd.NextDouble() * 1000; - parsedJson["windSpeed"] = rnd.NextDouble() * 4; - parsedJson["windTurbulence"] = rnd.NextDouble() * 1; - parsedJson["windHeading"] = rnd.Next(0, 360); + parsedJson["environment"]!["timeOfDay"] = rnd.Next(3,13); + parsedJson["environment"]!["timeFactor"] = 2.0; + parsedJson["environment"]!["weatherIntensity"] = rnd.NextDouble() * 0.9; + parsedJson["environment"]!["cloudAltitude"] = 500 + rnd.NextDouble() * 1000; + parsedJson["environment"]!["cloudAltitude"] = 500 + rnd.NextDouble() * 1000; + parsedJson["environment"]!["windSpeed"] = rnd.NextDouble() * 4; + parsedJson["environment"]!["windTurbulence"] = rnd.NextDouble() * 1; + parsedJson["environment"]!["windHeading"] = rnd.Next(0, 360); + parsedJson["environment"]!["windRandomHeading"] = rnd.Next(0, 91); File.WriteAllText(currentMissionFile, parsedJson.ToJsonString(writerOptions)); } } \ No newline at end of file From 681ec70afa0492f83b9752812424c891fba86c2c Mon Sep 17 00:00:00 2001 From: James Armstrong Date: Thu, 26 Feb 2026 15:10:47 -0800 Subject: [PATCH 5/5] Improve weather randomizer --- Nuclei/Features/NucleiConfig.cs | 19 ---------- Nuclei/Patches/MissionRotationPatches.cs | 24 ------------ Nuclei/Patches/MissionSaveLoadPatches.cs | 48 ++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 43 deletions(-) delete mode 100644 Nuclei/Patches/MissionRotationPatches.cs create mode 100644 Nuclei/Patches/MissionSaveLoadPatches.cs diff --git a/Nuclei/Features/NucleiConfig.cs b/Nuclei/Features/NucleiConfig.cs index 0beef18..ebc8dbb 100644 --- a/Nuclei/Features/NucleiConfig.cs +++ b/Nuclei/Features/NucleiConfig.cs @@ -175,25 +175,6 @@ internal static void InitSettings(ConfigFile config) "Randomize weather by modifying the .json mission file directly. This requires the missions to be in " + "the mission folder you assigned in DedicatedServerConfig.json, meaning all missions' MissionGroup must be User, not BuiltIn"); - if (RandomizeWeather.Value) - { - try - { - var json = File.ReadAllText("DedicatedServerConfig.json"); - var parsedJson = JsonNode.Parse(json)!; - - if (parsedJson["MissionDirectory"] == null) - throw new Exception("MissionDirectory in DedicatedServerConfig.json does not exist. Please set it up " + - "before enabling Randomize Weather"); - WeatherRandomizerService.MissionDir = (string)parsedJson["MissionDirectory"]!; - } - catch (FileNotFoundException e) - { - Nuclei.Logger?.LogError(e); - throw; - } - } - Nuclei.Logger?.LogDebug($"CommandPrefix: {CommandPrefix.Value}"); Nuclei.Logger?.LogDebug("Loaded settings!"); diff --git a/Nuclei/Patches/MissionRotationPatches.cs b/Nuclei/Patches/MissionRotationPatches.cs deleted file mode 100644 index 3823d3d..0000000 --- a/Nuclei/Patches/MissionRotationPatches.cs +++ /dev/null @@ -1,24 +0,0 @@ -using HarmonyLib; -using Mirage; -using NuclearOption.Chat; -using NuclearOption.DedicatedServer; -using Nuclei.Features; -using Nuclei.Features.Commands; -using Nuclei.Helpers; - -namespace Nuclei.Patches; - -[HarmonyPatch(typeof(MissionRotation))] -[HarmonyPriority(Priority.First)] -[HarmonyWrapSafe] -internal static class MissionRotationPatches -{ - [HarmonyPrefix] - [HarmonyPatch(nameof(MissionRotation.GetNext))] - private static void GetNextPrefix() - { - if (!NucleiConfig.RandomizeWeather!.Value) return; - MissionOptions nextMission = Globals.DedicatedServerManagerInstance.missionRotation.PeakNext(); - WeatherRandomizerService.RandomizeWeather(nextMission.Key.Name); - } -} \ No newline at end of file diff --git a/Nuclei/Patches/MissionSaveLoadPatches.cs b/Nuclei/Patches/MissionSaveLoadPatches.cs new file mode 100644 index 0000000..4d6eb68 --- /dev/null +++ b/Nuclei/Patches/MissionSaveLoadPatches.cs @@ -0,0 +1,48 @@ +using System; +using HarmonyLib; +using NuclearOption.SavedMission; +using Nuclei.Features; + +[HarmonyPriority(Priority.First)] +[HarmonyWrapSafe] +[HarmonyPatch(typeof(MissionSaveLoad))] +public class MissionSaveLoadPatches +{ + [HarmonyPostfix] + [HarmonyPatch(nameof(MissionSaveLoad.TryLoad))] + private static void Postfix( + MissionKey item, + ref Mission mission, + ref string error, + ref bool __result) + { + if (!__result || mission == null) return; + + RandomizeWeather(ref mission); + ModifyDifficulty(ref mission); + } + + private static void ModifyDifficulty(ref Mission mission) + { + // This is set to scale for larger player counts better + foreach (var f in mission.factions) + { + f.addAIPerEnemyPlayer = 0.80f; + f.AIAircraftLimit = 8; + } + } + + private static void RandomizeWeather(ref Mission mission) + { + if (!NucleiConfig.RandomizeWeather!.Value) return; + + var rnd = new Random(); + mission.environment.timeOfDay = rnd.Next(3, 18); + mission.environment.timeFactor = 8f; + mission.environment.weatherIntensity = (float)(rnd.NextDouble() * 0.9); + mission.environment.cloudAltitude = (float)(500 + rnd.NextDouble() * 1000); + mission.environment.windSpeed = (float)(rnd.NextDouble() * 4); + mission.environment.windTurbulence = (float)rnd.NextDouble(); + mission.environment.windHeading = rnd.Next(0, 360); + } +} \ No newline at end of file