From dd17030e561cbfafb53b0a88ab975b8e70852702 Mon Sep 17 00:00:00 2001 From: devbeni Date: Sun, 14 Dec 2025 10:58:37 +0100 Subject: [PATCH] Fix: Add periodic villager position sync from server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Server syncs villager positions every ~3 seconds to clients - Only syncs villagers that moved more than 0.5 units (bandwidth optimization) - Maintains position cache to detect movement - Clears cache on lobby leave to prevent stale data 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- Main.cs | 86 ++++++++++++++++++--------- RiptideSteamTransport/LobbyManager.cs | 1 + 2 files changed, 60 insertions(+), 27 deletions(-) diff --git a/Main.cs b/Main.cs index 4125245..1c28bb0 100644 --- a/Main.cs +++ b/Main.cs @@ -182,43 +182,75 @@ namespace KCM #endregion public static int FixedUpdateInterval = 0; + private static Dictionary lastVillagerPositions = new Dictionary(); + + public static void ClearVillagerPositionCache() + { + lastVillagerPositions.Clear(); + } private void FixedUpdate() { - // send batched building placement info - /*if (PlaceHook.QueuedBuildings.Count > 0 && (FixedUpdateInterval % 25 == 0)) + // Periodic villager position sync from server to clients + // Sync every 150 frames (~3 seconds at 50 FPS) to reduce bandwidth + if (KCServer.IsRunning && KCClient.client.IsConnected && FixedUpdateInterval % 150 == 0) { - foreach (Building building in PlaceHook.QueuedBuildings) + try { - new WorldPlace() - { - uniqueName = building.UniqueName, - customName = building.customName, - guid = building.guid, - rotation = building.transform.GetChild(0).rotation, - globalPosition = building.transform.position, - localPosition = building.transform.GetChild(0).localPosition, - built = building.IsBuilt(), - placed = building.IsPlaced(), - open = building.Open, - doBuildAnimation = building.doBuildAnimation, - constructionPaused = building.constructionPaused, - constructionProgress = building.constructionProgress, - life = building.Life, - ModifiedMaxLife = building.ModifiedMaxLife, - //CollectForBuild = CollectForBuild, - yearBuilt = building.YearBuilt, - decayProtection = building.decayProtection, - seenByPlayer = building.seenByPlayer - }.Send(); + SyncVillagerPositions(); } - - PlaceHook.QueuedBuildings.Clear(); - }*/ + catch (Exception e) + { + helper.Log($"Error in villager sync: {e.Message}"); + } + } FixedUpdateInterval++; } + private void SyncVillagerPositions() + { + // Only sync villagers that have moved significantly + const float movementThreshold = 0.5f; + + foreach (var kcPlayer in kCPlayers.Values) + { + if (kcPlayer.inst == null) continue; + + for (int i = 0; i < kcPlayer.inst.Workers.Count; i++) + { + var villager = kcPlayer.inst.Workers.data[i]; + if (villager == null) continue; + + Vector3 currentPos = villager.Pos; + Vector3 lastPos; + + bool shouldSync = false; + if (!lastVillagerPositions.TryGetValue(villager.guid, out lastPos)) + { + // First time seeing this villager + lastVillagerPositions[villager.guid] = currentPos; + shouldSync = true; + } + else if (Vector3.Distance(currentPos, lastPos) > movementThreshold) + { + // Villager has moved significantly + lastVillagerPositions[villager.guid] = currentPos; + shouldSync = true; + } + + if (shouldSync) + { + new Packets.Game.GameVillager.VillagerTeleportTo() + { + guid = villager.guid, + pos = currentPos + }.SendToAll(KCClient.client.Id); + } + } + } + } + #region "TransitionTo" public static void TransitionTo(MenuState state) { diff --git a/RiptideSteamTransport/LobbyManager.cs b/RiptideSteamTransport/LobbyManager.cs index 9fa21dd..53a1812 100644 --- a/RiptideSteamTransport/LobbyManager.cs +++ b/RiptideSteamTransport/LobbyManager.cs @@ -160,6 +160,7 @@ namespace Riptide.Demos.Steam.PlayerHosted Main.helper.Log("clear players"); Main.kCPlayers.Clear(); Main.clientSteamIds.Clear(); // Clear client-to-steam ID mapping + Main.ClearVillagerPositionCache(); // Clear villager sync cache LobbyHandler.ClearPlayerList(); LobbyHandler.ClearChatEntries(); Main.helper.Log("end clear players");