From 4d344ed11a47e89cbfb5dc6476e990afa4bb612c Mon Sep 17 00:00:00 2001 From: Dazzuh Date: Sat, 31 May 2025 14:31:33 +0100 Subject: [PATCH 1/3] Fika integration - Syncs bosses between host and clients. --- BossNotifier.csproj | 6 +- FikaIntegration.cs | 163 ++++++++++++++++++++++++++++++++++++++++++++ Plugin.cs | 38 ++++++----- 3 files changed, 188 insertions(+), 19 deletions(-) create mode 100644 FikaIntegration.cs diff --git a/BossNotifier.csproj b/BossNotifier.csproj index 867701e..80c1536 100644 --- a/BossNotifier.csproj +++ b/BossNotifier.csproj @@ -38,6 +38,7 @@ + @@ -67,6 +68,9 @@ References\UnityEngine.CoreModule.dll + + References\Fika.Core.dll + @@ -75,4 +79,4 @@ copy /Y "$(TargerDir)$(TargetFileName)" "C:\Games\SPT 3.10.3\BepInEx\plugins\$(T copy /Y "$(TargetDir)$(TargetFileName)" "$(TargetDir)BepInEx\plugins\$(TargetFileName)" powershell.exe -command Compress-Archive -Force -Path BepInEx $(TargetName).zip - \ No newline at end of file + diff --git a/FikaIntegration.cs b/FikaIntegration.cs new file mode 100644 index 0000000..da954d9 --- /dev/null +++ b/FikaIntegration.cs @@ -0,0 +1,163 @@ +using System.Collections.Generic; +using BepInEx.Logging; +using Comfort.Common; +using Fika.Core.Coop.Utils; +using Fika.Core.Networking; +using LiteNetLib; +using LiteNetLib.Utils; + +namespace BossNotifier +{ + // Packet for synchronizing boss list between host and clients + public class BossListPacket : INetSerializable + { + public readonly List BossNames = new List(); + public readonly List Locations = new List(); + + public void Serialize(NetDataWriter writer) + { + writer.Put(BossNames.Count); + for (int i = 0; i < BossNames.Count; i++) + { + writer.Put(BossNames[i]); + writer.Put(Locations[i]); + } + } + + public void Deserialize(NetDataReader reader) + { + BossNames.Clear(); + Locations.Clear(); + int count = reader.GetInt(); + for (int i = 0; i < count; i++) + { + BossNames.Add(reader.GetString()); + Locations.Add(reader.GetString()); + } + } + } + + // Packet for synchronizing vicinity notifications between host and clients + public class VicinityNotificationPacket : INetSerializable + { + public string Message; + + public void Serialize(NetDataWriter writer) + { + writer.Put(Message); + } + + public void Deserialize(NetDataReader reader) + { + Message = reader.GetString(); + } + } + + // Static class to handle all Fika-related functionality + public static class FikaIntegration + { + // Register Fika event handlers + public static void Initialize() + { + BossNotifierPlugin.Log(LogLevel.Debug, "FikaIntegration.Initialize called"); + // Register Fika packet handler using Fika's event system + Fika.Core.Modding.FikaEventDispatcher.SubscribeEvent(OnFikaNetworkManagerCreated); + BossNotifierPlugin.Log(LogLevel.Debug, "Subscribed to FikaNetworkManagerCreated event"); + } + + // Handler for Fika network manager creation event + private static void OnFikaNetworkManagerCreated(Fika.Core.Modding.Events.FikaNetworkManagerCreatedEvent evt) + { + BossNotifierPlugin.Log(LogLevel.Debug, "OnFikaNetworkManagerCreated event received"); + // Only register if this is a client + if (IsClient()) + { + BossNotifierPlugin.Log(LogLevel.Debug, "IsClient is true"); + var netMan = evt.Manager as FikaClient; + if (!netMan) return; + BossNotifierPlugin.Log(LogLevel.Debug, "FikaClient instance found, registering packet"); + netMan.RegisterPacket(OnBossListPacket); + BossNotifierPlugin.Log(LogLevel.Info, "Registered BossListPacket handler for Fika client via event."); + netMan.RegisterPacket(OnVicinityNotificationPacket); + BossNotifierPlugin.Log(LogLevel.Info, "Registered VicinityNotificationPacket handler for Fika client via event."); + } + } + + // Called when a BossListPacket is received from the host + private static void OnBossListPacket(BossListPacket packet) + { + BossNotifierPlugin.Log(LogLevel.Debug, $"OnBossListPacket called with {packet.BossNames.Count} bosses"); + BossNotifierPlugin.Log(LogLevel.Info, "Received BossListPacket from host."); + BossNotifierPlugin.Log(LogLevel.Info, $"Bosses in raid: {string.Join(", ", packet.BossNames)}"); + + BossLocationSpawnPatch.bossesInRaid.Clear(); + for (int i = 0; i < packet.BossNames.Count; i++) + { + BossNotifierPlugin.Log(LogLevel.Debug, $"Boss: {packet.BossNames[i]}, Location: {packet.Locations[i]}"); + BossLocationSpawnPatch.bossesInRaid[packet.BossNames[i]] = packet.Locations[i]; + } + + // Regenerate notifications if the mono instance exists + if (BossNotifierMono.Instance) + { + BossNotifierMono.Instance.GenerateBossNotifications(); + } + } + + // Called when a VicinityNotificationPacket is received from the host + private static void OnVicinityNotificationPacket(VicinityNotificationPacket packet) + { + BossNotifierPlugin.Log(LogLevel.Debug, $"OnVicinityNotificationPacket called with message: {packet.Message}"); + // Enqueue the message for display on the client + BotBossPatch.vicinityNotifications.Enqueue(packet.Message); + } + + // Send the boss list to all clients (called from host) + public static void SendBossListToClients(Dictionary bossesInRaid) + { + var netMan = Singleton.Instance; + if (netMan) + { + BossNotifierPlugin.Log(LogLevel.Debug, "FikaServer instance found, sending BossListPacket to all clients"); + var packet = new BossListPacket(); + foreach (var kvp in bossesInRaid) + { + packet.BossNames.Add(kvp.Key); + packet.Locations.Add(kvp.Value); + } + netMan.SendDataToAll(ref packet, DeliveryMethod.ReliableOrdered); + } + else + { + BossNotifierPlugin.Log(LogLevel.Debug, "FikaServer instance not found, skipping packet send"); + } + } + + // Send a vicinity notification to all clients (called from host) + public static void SendVicinityNotificationToClients(string message) + { + var netMan = Singleton.Instance; + if (netMan) + { + BossNotifierPlugin.Log(LogLevel.Debug, $"FikaServer instance found, sending VicinityNotificationPacket to all clients: {message}"); + var packet = new VicinityNotificationPacket { Message = message }; + netMan.SendDataToAll(ref packet, DeliveryMethod.ReliableOrdered); + } + else + { + BossNotifierPlugin.Log(LogLevel.Debug, "FikaServer instance not found, skipping vicinity notification packet send"); + } + } + + public static bool IsClient() + { + return FikaBackendUtils.IsClient; + } + + public static bool IsHost() + { + return FikaBackendUtils.IsServer; + } + } +} + diff --git a/Plugin.cs b/Plugin.cs index dc01172..cb6b873 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -10,8 +10,6 @@ using Comfort.Common; using BepInEx.Logging; using System.Text; -using System; -using HarmonyLib; #pragma warning disable IDE0051 // Remove unused private members @@ -19,9 +17,6 @@ namespace BossNotifier { [BepInPlugin("Mattdokn.BossNotifier", "BossNotifier", "1.6.0")] [BepInDependency("com.fika.core", BepInDependency.DependencyFlags.SoftDependency)] public class BossNotifierPlugin : BaseUnityPlugin { - public static FieldInfo FikaIsPlayerHost; - - // Configuration entries public static ConfigEntry showBossesKeyCode; public static ConfigEntry showNotificationsOnRaidStart; @@ -108,11 +103,6 @@ public static void Log(LogLevel level, string msg) { private void Awake() { logger = Logger; - Type FikaUtilExternalType = Type.GetType("Fika.Core.Coop.Utils.FikaBackendUtils, Fika.Core", false); - if (FikaUtilExternalType != null) { - FikaIsPlayerHost = AccessTools.Field(FikaUtilExternalType, "MatchingType"); - } - // Initialize configuration entries showBossesKeyCode = Config.Bind("General", "Keyboard Shortcut", new KeyboardShortcut(KeyCode.O), "Key to show boss notifications."); showNotificationsOnRaidStart = Config.Bind("General", "Show Bosses on Raid Start", true, "Show boss notifications on raid start."); @@ -141,6 +131,9 @@ private void Awake() { Config.SettingChanged += Config_SettingChanged; Logger.LogInfo($"Plugin BossNotifier is loaded!"); + + // Initialize Fika integration + FikaIntegration.Initialize(); } // Event handler for configuration changes @@ -205,11 +198,14 @@ private static void TryAddBoss(string boss, string location) { // Add the boss entry bossesInRaid.Add(boss, location); } + // After updating, send the new boss list to all clients if Fika is present and this is the host + FikaIntegration.SendBossListToClients(bossesInRaid); } // Handle boss location spawns [PatchPostfix] private static void PatchPostfix(BossLocationSpawn __instance) { + if (FikaIntegration.IsClient()) return; // If the boss will spawn if (__instance.ShallSpawn) { // Get it's name, if no name found then return. @@ -263,6 +259,12 @@ private static void PatchPostfix(BotBoss __instance) { vicinityNotifications.Enqueue($"{name} {(BossNotifierPlugin.pluralBosses.Contains(name) ? "have" : "has")} been detected in your vicinity."); + // Sync vicinity notification to clients if running as host + if (FikaIntegration.IsHost()) + { + FikaIntegration.SendVicinityNotificationToClients($"{name} {(BossNotifierPlugin.pluralBosses.Contains(name) ? "have" : "has")} been detected in your vicinity."); + } + //if (BossNotifierMono.Instance.intelCenterLevel >= BossNotifierPlugin.intelCenterDetectedUnlockLevel.Value) { // NotificationManagerClass.DisplayMessageNotification($"{name} {(BossNotifierPlugin.pluralBosses.Contains(name) ? "have" : "has")} been detected in your vicinity.", ENotificationDurationType.Long); // BossNotifierMono.Instance.GenerateBossNotifications(); @@ -291,7 +293,6 @@ class BossNotifierMono : MonoBehaviour { public int intelCenterLevel; private void SendBossNotifications() { - if (!ShouldFunction()) return; if (intelCenterLevel < BossNotifierPlugin.intelCenterUnlockLevel.Value) return; // If we have no notifications to display, send one saying there's no bosses located. @@ -346,18 +347,19 @@ public void OnDestroy() { BotBossPatch.spawnedBosses.Clear(); } - public bool ShouldFunction() { - if (BossNotifierPlugin.FikaIsPlayerHost == null) return true; - return (int)BossNotifierPlugin.FikaIsPlayerHost.GetValue(null) == 2; - } - public void GenerateBossNotifications() { // Clear out boss notification cache bossNotificationMessages = new List(); - // Check if it's daytime to prevent showing Cultist notif. + bool isDayTime; // This is the same method that DayTimeCultists patches so if that mod is installed then this always returns false - bool isDayTime = Singleton.Instance.BotsController.ZonesLeaveController.IsDay(); + if (FikaIntegration.IsClient()) { + // IBotGame.Instance is null as a Fika client, so we skip the isDayTime check, maybe there's an alternate way to check for day time? + BossNotifierPlugin.Log(LogLevel.Debug, "FikaIntegration.IsClient() is true, skipping isDayTime check"); + isDayTime = false; + } else { + isDayTime = Singleton.Instance.BotsController.ZonesLeaveController.IsDay(); + } // Get whether location is unlocked or not. bool isLocationUnlocked = intelCenterLevel >= BossNotifierPlugin.intelCenterLocationUnlockLevel.Value; From 5f62216b270592a4033c4b89dfa278175a2d5018 Mon Sep 17 00:00:00 2001 From: Dazzuh Date: Sat, 31 May 2025 17:40:14 +0100 Subject: [PATCH 2/3] Seperated packets into their own .dll, reworked FikaIntegration to prevent errors when Fika isn't installed. --- BossNotifier.csproj | 6 +++ FikaIntegration.cs | 67 +++++++------------------------- Plugin.cs | 35 +++++++++++++---- packets/BossListPacket.cs | 41 +++++++++++++++++++ packets/BossNotifier.fika.csproj | 14 +++++++ 5 files changed, 104 insertions(+), 59 deletions(-) create mode 100644 packets/BossListPacket.cs create mode 100644 packets/BossNotifier.fika.csproj diff --git a/BossNotifier.csproj b/BossNotifier.csproj index 80c1536..b246ec0 100644 --- a/BossNotifier.csproj +++ b/BossNotifier.csproj @@ -73,6 +73,12 @@ + + + + + + mkdir $(TargetDir)BepInEx\plugins\ copy /Y "$(TargerDir)$(TargetFileName)" "C:\Games\SPT 3.10.3\BepInEx\plugins\$(TargetFileName)" diff --git a/FikaIntegration.cs b/FikaIntegration.cs index da954d9..306c457 100644 --- a/FikaIntegration.cs +++ b/FikaIntegration.cs @@ -1,58 +1,16 @@ +using System; using System.Collections.Generic; using BepInEx.Logging; using Comfort.Common; using Fika.Core.Coop.Utils; +using Fika.Core.Modding; +using Fika.Core.Modding.Events; using Fika.Core.Networking; +using BossNotifier.Packets; using LiteNetLib; -using LiteNetLib.Utils; namespace BossNotifier { - // Packet for synchronizing boss list between host and clients - public class BossListPacket : INetSerializable - { - public readonly List BossNames = new List(); - public readonly List Locations = new List(); - - public void Serialize(NetDataWriter writer) - { - writer.Put(BossNames.Count); - for (int i = 0; i < BossNames.Count; i++) - { - writer.Put(BossNames[i]); - writer.Put(Locations[i]); - } - } - - public void Deserialize(NetDataReader reader) - { - BossNames.Clear(); - Locations.Clear(); - int count = reader.GetInt(); - for (int i = 0; i < count; i++) - { - BossNames.Add(reader.GetString()); - Locations.Add(reader.GetString()); - } - } - } - - // Packet for synchronizing vicinity notifications between host and clients - public class VicinityNotificationPacket : INetSerializable - { - public string Message; - - public void Serialize(NetDataWriter writer) - { - writer.Put(Message); - } - - public void Deserialize(NetDataReader reader) - { - Message = reader.GetString(); - } - } - // Static class to handle all Fika-related functionality public static class FikaIntegration { @@ -60,13 +18,14 @@ public static class FikaIntegration public static void Initialize() { BossNotifierPlugin.Log(LogLevel.Debug, "FikaIntegration.Initialize called"); - // Register Fika packet handler using Fika's event system - Fika.Core.Modding.FikaEventDispatcher.SubscribeEvent(OnFikaNetworkManagerCreated); + // Register Fika packet handler using Fika's event system by calling new Action to avoid errors when fika + // is not present. Thanks Tyfon for this tip. + FikaEventDispatcher.SubscribeEvent(new Action(OnFikaNetworkManagerCreated)); BossNotifierPlugin.Log(LogLevel.Debug, "Subscribed to FikaNetworkManagerCreated event"); } // Handler for Fika network manager creation event - private static void OnFikaNetworkManagerCreated(Fika.Core.Modding.Events.FikaNetworkManagerCreatedEvent evt) + private static void OnFikaNetworkManagerCreated(FikaNetworkManagerCreatedEvent evt) { BossNotifierPlugin.Log(LogLevel.Debug, "OnFikaNetworkManagerCreated event received"); // Only register if this is a client @@ -76,9 +35,9 @@ private static void OnFikaNetworkManagerCreated(Fika.Core.Modding.Events.FikaNet var netMan = evt.Manager as FikaClient; if (!netMan) return; BossNotifierPlugin.Log(LogLevel.Debug, "FikaClient instance found, registering packet"); - netMan.RegisterPacket(OnBossListPacket); + netMan.RegisterPacket(new Action(OnBossListPacket)); BossNotifierPlugin.Log(LogLevel.Info, "Registered BossListPacket handler for Fika client via event."); - netMan.RegisterPacket(OnVicinityNotificationPacket); + netMan.RegisterPacket(new Action(OnVicinityNotificationPacket)); BossNotifierPlugin.Log(LogLevel.Info, "Registered VicinityNotificationPacket handler for Fika client via event."); } } @@ -119,7 +78,11 @@ public static void SendBossListToClients(Dictionary bossesInRaid if (netMan) { BossNotifierPlugin.Log(LogLevel.Debug, "FikaServer instance found, sending BossListPacket to all clients"); - var packet = new BossListPacket(); + var packet = new BossListPacket + { + BossNames = new List(), + Locations = new List() + }; foreach (var kvp in bossesInRaid) { packet.BossNames.Add(kvp.Key); diff --git a/Plugin.cs b/Plugin.cs index cb6b873..aecefa4 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -1,4 +1,5 @@ -using BepInEx; +using System; +using BepInEx; using SPT.Reflection.Patching; using SPT.Reflection.Utils; using System.Reflection; @@ -10,6 +11,7 @@ using Comfort.Common; using BepInEx.Logging; using System.Text; +using BepInEx.Bootstrap; #pragma warning disable IDE0051 // Remove unused private members @@ -100,6 +102,8 @@ public static void Log(LogLevel level, string msg) { {"ZoneCard1", "Card 1" }, }; + public static bool IsFikaPresent; + private void Awake() { logger = Logger; @@ -133,7 +137,19 @@ private void Awake() { Logger.LogInfo($"Plugin BossNotifier is loaded!"); // Initialize Fika integration - FikaIntegration.Initialize(); + if (FikaPresent()) FikaIntegration.Initialize(); + } + + private static bool FikaPresent() + { + if (Chainloader.PluginInfos.TryGetValue("com.fika.core", out PluginInfo pluginInfo)) + { + IsFikaPresent = true; + return true; + } + + IsFikaPresent = false; + return false; } // Event handler for configuration changes @@ -199,13 +215,18 @@ private static void TryAddBoss(string boss, string location) { bossesInRaid.Add(boss, location); } // After updating, send the new boss list to all clients if Fika is present and this is the host - FikaIntegration.SendBossListToClients(bossesInRaid); + if (BossNotifierPlugin.IsFikaPresent) { + try { + FikaIntegration.SendBossListToClients(bossesInRaid); + } catch (Exception ex) { + Logger.LogError($"Error sending boss list to clients: {ex}"); + } + } } - // Handle boss location spawns [PatchPostfix] private static void PatchPostfix(BossLocationSpawn __instance) { - if (FikaIntegration.IsClient()) return; + if (BossNotifierPlugin.IsFikaPresent && FikaIntegration.IsClient()) return; // If the boss will spawn if (__instance.ShallSpawn) { // Get it's name, if no name found then return. @@ -260,7 +281,7 @@ private static void PatchPostfix(BotBoss __instance) { vicinityNotifications.Enqueue($"{name} {(BossNotifierPlugin.pluralBosses.Contains(name) ? "have" : "has")} been detected in your vicinity."); // Sync vicinity notification to clients if running as host - if (FikaIntegration.IsHost()) + if (BossNotifierPlugin.IsFikaPresent && FikaIntegration.IsHost()) { FikaIntegration.SendVicinityNotificationToClients($"{name} {(BossNotifierPlugin.pluralBosses.Contains(name) ? "have" : "has")} been detected in your vicinity."); } @@ -353,7 +374,7 @@ public void GenerateBossNotifications() { // Check if it's daytime to prevent showing Cultist notif. bool isDayTime; // This is the same method that DayTimeCultists patches so if that mod is installed then this always returns false - if (FikaIntegration.IsClient()) { + if (BossNotifierPlugin.IsFikaPresent && FikaIntegration.IsClient()) { // IBotGame.Instance is null as a Fika client, so we skip the isDayTime check, maybe there's an alternate way to check for day time? BossNotifierPlugin.Log(LogLevel.Debug, "FikaIntegration.IsClient() is true, skipping isDayTime check"); isDayTime = false; diff --git a/packets/BossListPacket.cs b/packets/BossListPacket.cs new file mode 100644 index 0000000..d2e5ec1 --- /dev/null +++ b/packets/BossListPacket.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using LiteNetLib; +using LiteNetLib.Utils; + +namespace BossNotifier.Packets +{ + // Packet for synchronizing boss list between host and clients + public struct BossListPacket : INetSerializable + { + public List BossNames; + public List Locations; + + public void Serialize(NetDataWriter writer) + { + writer.Put(BossNames?.Count ?? 0); + if (BossNames != null && Locations != null) + { + for (int i = 0; i < BossNames.Count; i++) + { + writer.Put(BossNames[i]); + writer.Put(Locations[i]); + } + } + } + + public void Deserialize(NetDataReader reader) + { + if (BossNames == null) BossNames = new List(); + if (Locations == null) Locations = new List(); + BossNames.Clear(); + Locations.Clear(); + int count = reader.GetInt(); + for (int i = 0; i < count; i++) + { + BossNames.Add(reader.GetString()); + Locations.Add(reader.GetString()); + } + } + } +} + diff --git a/packets/BossNotifier.fika.csproj b/packets/BossNotifier.fika.csproj new file mode 100644 index 0000000..63ea993 --- /dev/null +++ b/packets/BossNotifier.fika.csproj @@ -0,0 +1,14 @@ + + + net471 + BossNotifier.fika + BossNotifier.Packets + Library + + + + ..\References\Fika.Core.dll + + + + From 3c75be5e5e4c8d37fd8d9e5db45219a0c7f519db Mon Sep 17 00:00:00 2001 From: Dazzuh Date: Wed, 25 Jun 2025 20:39:40 +0100 Subject: [PATCH 3/3] =?UTF-8?q?Apparently=20I=20forgot=20to=20commit=20thi?= =?UTF-8?q?s=20file=3F=20Whoops=20=F0=9F=98=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packets/VicinityNotificationPacket.cs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 packets/VicinityNotificationPacket.cs diff --git a/packets/VicinityNotificationPacket.cs b/packets/VicinityNotificationPacket.cs new file mode 100644 index 0000000..572d825 --- /dev/null +++ b/packets/VicinityNotificationPacket.cs @@ -0,0 +1,22 @@ +using LiteNetLib; +using LiteNetLib.Utils; + +namespace BossNotifier.Packets +{ + // Packet for synchronizing vicinity notifications between host and clients + public struct VicinityNotificationPacket : INetSerializable + { + public string Message; + + public void Serialize(NetDataWriter writer) + { + writer.Put(Message); + } + + public void Deserialize(NetDataReader reader) + { + Message = reader.GetString(); + } + } +} +