From f82ae76a3ead5ae16a5ade091010c1d82dc8d774 Mon Sep 17 00:00:00 2001 From: devbeni Date: Sat, 13 Dec 2025 23:03:10 +0100 Subject: [PATCH] =?UTF-8?q?tal=C3=A1n=20fix=20geic=20v=C3=A9gre=20big-pick?= =?UTF-8?q?le?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LoadSaveOverrides/MultiplayerSaveContainer.cs | 236 ++++++++++++++++-- Main.cs | 6 +- Packets/Game/SetSpeed.cs | 52 +++- StateManagement/Sync/SyncManager.cs | 130 ++++++++-- 4 files changed, 386 insertions(+), 38 deletions(-) diff --git a/LoadSaveOverrides/MultiplayerSaveContainer.cs b/LoadSaveOverrides/MultiplayerSaveContainer.cs index 0a5e6de..7138052 100644 --- a/LoadSaveOverrides/MultiplayerSaveContainer.cs +++ b/LoadSaveOverrides/MultiplayerSaveContainer.cs @@ -1,4 +1,4 @@ -using Assets.Code; +using Assets.Code; using Riptide; using Riptide.Transports; using Steamworks; @@ -189,15 +189,20 @@ namespace KCM.LoadSaveOverrides Main.helper.Log("Finished unpacking player data"); - /* - * Not even going to bother fixing AI brains save data yet, not in short-term roadmap - */ - - /*bool flag2 = this.AIBrainsSaveData != null; + // Fix AI brains save/load system to restore villager AI state + bool flag2 = this.AIBrainsSaveData != null; if (flag2) { - this.AIBrainsSaveData.UnpackPrePlayer(AIBrainsContainer.inst); - }*/ + try + { + Main.helper.Log("Unpacking AI brains before player data"); + this.AIBrainsSaveData.UnpackPrePlayer(AIBrainsContainer.inst); + } + catch (Exception e) + { + Main.helper.Log("Error unpacking AI brains pre-player: " + e.Message); + } + } Main.helper.Log("Unpacking free resource manager"); this.FreeResourceManagerSaveData.Unpack(FreeResourceManager.inst); @@ -255,7 +260,55 @@ namespace KCM.LoadSaveOverrides bool flag10 = this.AIBrainsSaveData != null; if (flag10) { - this.AIBrainsSaveData.Unpack(AIBrainsContainer.inst); + try + { + this.AIBrainsSaveData.Unpack(AIBrainsContainer.inst); + Main.helper.Log("AI brains unpacked successfully"); + } + catch (Exception e) + { + Main.helper.Log("Error unpacking AI brains: " + e.Message); + Main.helper.Log("Attempting to reinitialize AI systems"); + try + { + AIBrainsContainer.inst.ClearAIs(); + // Force reinitialize AI for all villagers + for (int i = 0; i < Villager.villagers.Count; i++) + { + Villager v = Villager.villagers.data[i]; + if (v != null && v.brain != null) + { + v.brain.Restart(); + } + } + Main.helper.Log("AI systems reinitialized"); + } + catch (Exception ex) + { + Main.helper.Log("Failed to reinitialize AI systems: " + ex.Message); + } + } + } + else + { + Main.helper.Log("No AI brains save data found, initializing fresh AI"); + try + { + // Initialize AI for all villagers if no save data + for (int i = 0; i < Villager.villagers.Count; i++) + { + Villager v = Villager.villagers.data[i]; + if (v != null && v.brain != null) + { + v.brain.Restart(); + } + } + Main.helper.Log("Fresh AI initialization completed"); + } + catch (Exception e) + { + Main.helper.Log("Failed fresh AI initialization: " + e.Message); + } } Main.helper.Log("Unpacking custom save data"); bool flag11 = this.CustomSaveData != null; @@ -287,41 +340,190 @@ namespace KCM.LoadSaveOverrides Player.inst.Buildings.data[i].UpdateMaterialSelection(); } - // Player.inst.loadTickDelay = 1; + // Increase loadTickDelay values to ensure proper initialization Type playerType = typeof(Player); FieldInfo loadTickDelayField = playerType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic); if (loadTickDelayField != null) { - loadTickDelayField.SetValue(Player.inst, 1); + loadTickDelayField.SetValue(Player.inst, 3); } - // UnitSystem.inst.loadTickDelay = 1; + // UnitSystem.inst.loadTickDelay = 3; Type unitSystemType = typeof(UnitSystem); loadTickDelayField = unitSystemType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic); if (loadTickDelayField != null) { - loadTickDelayField.SetValue(UnitSystem.inst, 1); + loadTickDelayField.SetValue(UnitSystem.inst, 3); } - // JobSystem.inst.loadTickDelay = 1; + // JobSystem.inst.loadTickDelay = 3; Type jobSystemType = typeof(JobSystem); loadTickDelayField = jobSystemType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic); if (loadTickDelayField != null) { - loadTickDelayField.SetValue(JobSystem.inst, 1); + loadTickDelayField.SetValue(JobSystem.inst, 3); } - // VillagerSystem.inst.loadTickDelay = 1; + // VillagerSystem.inst.loadTickDelay = 3; Type villagerSystemType = typeof(VillagerSystem); loadTickDelayField = villagerSystemType.GetField("loadTickDelay", BindingFlags.Instance | BindingFlags.NonPublic); if (loadTickDelayField != null) { - loadTickDelayField.SetValue(VillagerSystem.inst, 1); + loadTickDelayField.SetValue(VillagerSystem.inst, 3); + } + + // Force AI system restart after load + try + { + Main.helper.Log("Forcing AI system restart after load"); + + // Restart all villager AI brains + for (int i = 0; i < Villager.villagers.Count; i++) + { + Villager v = Villager.villagers.data[i]; + if (v != null && v.brain != null) + { + try + { + v.brain.Restart(); + } + catch (Exception e) + { + Main.helper.Log($"Failed to restart villager AI: {e.Message}"); + } + } + } + + // Force job system refresh + if (JobSystem.inst != null) + { + try + { + // Use reflection to call any refresh/rebuild methods + var jobSystemType = typeof(JobSystem); + var refreshMethods = jobSystemType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) + .Where(m => m.Name.Contains("Refresh") || m.Name.Contains("Rebuild") || m.Name.Contains("Update")); + + foreach (var method in refreshMethods) + { + if (method.GetParameters().Length == 0) + { + try + { + method.Invoke(JobSystem.inst, null); + Main.helper.Log($"Called JobSystem.{method.Name}()"); + } + catch { } + } + } + } + catch (Exception e) + { + Main.helper.Log($"Error refreshing job system: {e.Message}"); + } + } + + Main.helper.Log("AI system restart completed"); + } + catch (Exception e) + { + Main.helper.Log($"Error during AI system restart: {e.Message}"); } Main.helper.Log($"Setting kingdom name to: {kingdomNames[Main.PlayerSteamID]}"); TownNameUI.inst.SetTownName(kingdomNames[Main.PlayerSteamID]); + // Perform villager state resync after loading completes + try + { + Main.helper.Log("Starting villager state resync after load"); + + // Wait a frame for all systems to initialize + System.Threading.Tasks.Task.Delay(100).ContinueWith(_ => + { + try + { + Main.helper.Log("Performing delayed villager resync"); + + // Resync all villager positions and states + for (int i = 0; i < Villager.villagers.Count; i++) + { + Villager v = Villager.villagers.data[i]; + if (v != null) + { + try + { + // Force position update + Vector3 currentPos = v.Pos; + v.TeleportTo(currentPos); + + // Restart AI brain if it exists + if (v.brain != null) + { + v.brain.Restart(); + } + + // Ensure villager is in correct system lists + if (v.workerJob != null && Player.inst != null) + { + if (!Player.inst.Workers.Contains(v)) + { + Player.inst.Workers.Add(v); + } + } + else if (v.workerJob == null && Player.inst != null) + { + if (!Player.inst.Homeless.Contains(v)) + { + Player.inst.Homeless.Add(v); + } + } + } + catch (Exception e) + { + Main.helper.Log($"Error resyncing villager {i}: {e.Message}"); + } + } + } + + // Force job system to re-evaluate all jobs + if (JobSystem.inst != null) + { + try + { + var jobSystemType = typeof(JobSystem); + var updateMethods = jobSystemType.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + .Where(m => (m.Name.Contains("Update") || m.Name.Contains("Refresh")) && m.GetParameters().Length == 0); + + foreach (var method in updateMethods) + { + try + { + method.Invoke(JobSystem.inst, null); + Main.helper.Log($"Called JobSystem.{method.Name} for resync"); + } + catch { } + } + } + catch (Exception e) + { + Main.helper.Log($"Error updating job system: {e.Message}"); + } + } + + Main.helper.Log("Villager state resync completed"); + } + catch (Exception e) + { + Main.helper.Log($"Error in delayed villager resync: {e.Message}"); + } + }); + } + catch (Exception e) + { + Main.helper.Log($"Error starting villager resync: {e.Message}"); + } + return obj; } } diff --git a/Main.cs b/Main.cs index 1e772a7..7022717 100644 --- a/Main.cs +++ b/Main.cs @@ -1,4 +1,4 @@ -using Assets.Code; +using Assets.Code; using Assets.Code.UI; using Assets.Interface; using Harmony; @@ -1160,9 +1160,11 @@ namespace KCM return; Main.helper.Log("SpeedControlUI.SetSpeed (local): " + idx); + bool isPaused = (idx == 0); new SetSpeed() { - speed = idx + speed = idx, + isPaused = isPaused }.Send(); lastTime = DateTimeOffset.Now.ToUnixTimeMilliseconds(); diff --git a/Packets/Game/SetSpeed.cs b/Packets/Game/SetSpeed.cs index 319eb71..58014bd 100644 --- a/Packets/Game/SetSpeed.cs +++ b/Packets/Game/SetSpeed.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -11,18 +11,64 @@ namespace KCM.Packets.Game public override ushort packetId => (int)Enums.Packets.SetSpeed; public int speed { get; set; } + public bool isPaused { get; set; } public override void HandlePacketClient() { if (clientId == KCClient.client.Id) // Prevent speed softlock return; - SpeedControlUI.inst.SetSpeed(speed); + try + { + // Apply speed setting + SpeedControlUI.inst.SetSpeed(speed); + + // Handle pause/unpause state + if (isPaused && Time.timeScale > 0) + { + // Game should be paused + Time.timeScale = 0f; + Main.helper.Log("Game paused via network sync"); + } + else if (!isPaused && Time.timeScale == 0) + { + // Game should be unpaused - restore speed + Time.timeScale = 1f; + SpeedControlUI.inst.SetSpeed(speed); + Main.helper.Log("Game unpaused via network sync"); + } + + // Force AI system update when speed changes + if (speed > 0) + { + try + { + // Restart villager AI to ensure they continue working + for (int i = 0; i < Villager.villagers.Count; i++) + { + Villager v = Villager.villagers.data[i]; + if (v != null && v.brain != null) + { + v.brain.Restart(); + } + } + Main.helper.Log($"AI systems restarted for speed change to {speed}"); + } + catch (Exception e) + { + Main.helper.Log("Error restarting AI on speed change: " + e.Message); + } + } + } + catch (Exception e) + { + Main.helper.Log("Error handling SetSpeed packet: " + e.Message); + } } public override void HandlePacketServer() { - //throw new NotImplementedException(); + // Server doesn't need to handle this packet } } } diff --git a/StateManagement/Sync/SyncManager.cs b/StateManagement/Sync/SyncManager.cs index 84d8f60..9623114 100644 --- a/StateManagement/Sync/SyncManager.cs +++ b/StateManagement/Sync/SyncManager.cs @@ -15,8 +15,10 @@ namespace KCM.StateManagement.Sync private const int ResourceBroadcastIntervalMs = 2000; private const int MaxBuildingSnapshotBytes = 30000; private const int MaxVillagerTeleportsPerResync = 400; + private const int VillagerValidationIntervalMs = 10000; // 10 seconds private static long lastResourceBroadcastMs; + private static long lastVillagerValidationMs; private static FieldInfo freeResourceAmountField; private static MethodInfo resourceAmountGetMethod; @@ -30,27 +32,36 @@ namespace KCM.StateManagement.Sync return; long now = DateTimeOffset.Now.ToUnixTimeMilliseconds(); - if ((now - lastResourceBroadcastMs) < ResourceBroadcastIntervalMs) - return; - - lastResourceBroadcastMs = now; - - try + + // Resource broadcast + if ((now - lastResourceBroadcastMs) >= ResourceBroadcastIntervalMs) { - ResourceSnapshotPacket snapshot = BuildResourceSnapshotPacket(); - if (snapshot == null) - return; + lastResourceBroadcastMs = now; - snapshot.clientId = KCClient.client != null ? KCClient.client.Id : (ushort)0; + try + { + ResourceSnapshotPacket snapshot = BuildResourceSnapshotPacket(); + if (snapshot == null) + return; - // Exclude host/local client from receiving its own snapshot. - ushort exceptId = KCClient.client != null ? KCClient.client.Id : (ushort)0; - snapshot.SendToAll(exceptId); + snapshot.clientId = KCClient.client != null ? KCClient.client.Id : (ushort)0; + + // Exclude host/local client from receiving its own snapshot. + ushort exceptId = KCClient.client != null ? KCClient.client.Id : (ushort)0; + snapshot.SendToAll(exceptId); + } + catch (Exception ex) + { + Main.helper.Log("Error broadcasting resource snapshot"); + Main.helper.Log(ex.ToString()); + } } - catch (Exception ex) + + // Villager state validation + if ((now - lastVillagerValidationMs) >= VillagerValidationIntervalMs) { - Main.helper.Log("Error broadcasting resource snapshot"); - Main.helper.Log(ex.ToString()); + lastVillagerValidationMs = now; + ValidateAndCorrectVillagerStates(); } } @@ -731,5 +742,92 @@ namespace KCM.StateManagement.Sync { } } + + private static void ValidateAndCorrectVillagerStates() + { + try + { + int stuckVillagers = 0; + int correctedVillagers = 0; + + for (int i = 0; i < Villager.villagers.Count; i++) + { + Villager v = Villager.villagers.data[i]; + if (v == null) + continue; + + try + { + bool needsCorrection = false; + + // Check if villager is stuck (not moving for too long while having a job) + if (v.workerJob != null && v.brain != null) + { + // Check if villager is in invalid state + if (v.brain.currentAction == null && v.workerJob.active) + { + needsCorrection = true; + stuckVillagers++; + } + + // Check if villager position is invalid + if (float.IsNaN(v.Pos.x) || float.IsNaN(v.Pos.y) || float.IsNaN(v.Pos.z)) + { + needsCorrection = true; + stuckVillagers++; + } + } + + if (needsCorrection) + { + // Correct villager state + try + { + // Restart AI brain + if (v.brain != null) + { + v.brain.Restart(); + } + + // Ensure valid position + if (float.IsNaN(v.Pos.x) || float.IsNaN(v.Pos.y) || float.IsNaN(v.Pos.z)) + { + // Teleport to a safe position near their workplace or home + Vector3 safePos = Vector3.zero; + if (v.workerJob != null && v.workerJob.employer != null) + { + safePos = v.workerJob.employer.transform.position; + } + else + { + safePos = new Vector3(World.inst.GridWidth / 2, 0, World.inst.GridHeight / 2); + } + v.TeleportTo(safePos); + } + + correctedVillagers++; + } + catch (Exception e) + { + Main.helper.Log($"Error correcting villager {i}: {e.Message}"); + } + } + } + catch (Exception e) + { + Main.helper.Log($"Error validating villager {i}: {e.Message}"); + } + } + + if (stuckVillagers > 0) + { + Main.helper.Log($"Villager validation: Found {stuckVillagers} stuck villagers, corrected {correctedVillagers}"); + } + } + catch (Exception e) + { + Main.helper.Log("Error in villager state validation: " + e.Message); + } + } } }