using System.Collections.Generic; namespace AcDream.App.Streaming; /// /// Decides which retained server-object spawns to re-hydrate (rebuild render /// entities for) when a landblock (re)loads. The pure selection half of the /// #138 fix; GameWindow owns the replay half (it alone can rebuild a /// mesh from a CreateObject). /// /// /// Why this exists (retail/ACE model). A real AC client keeps its /// weenie_object_table across a teleport and re-projects its rendered /// world from that table; the server does NOT re-broadcast objects it believes /// the client already knows (ACE's per-player KnownObjects set is never /// cleared on a normal teleport — ACE relies on the client retaining the /// table, cross-checked against references/holtburger + /// references/ACE/Source/ACE.Server/Physics/Common/ObjectMaint.cs). /// /// /// /// acdream's dungeon-collapse (and Near→Far demote) drops a landblock's /// RENDER entities for FPS but retains the parsed spawns /// (GameWindow._lastSpawnByGuid — the table is only pruned by an /// explicit server DeleteObject or a spawn de-dup). On reload, ACE will /// not re-send the objects it still thinks we have, so the render side stays /// empty — the #138 symptom. This selects the retained spawns whose render /// entity is currently absent so GameWindow can replay them, making /// re-delivery independent of the server. /// /// public static class LandblockEntityRehydrator { /// /// A retained spawn reduced to just the fields the selection needs. /// is true only when the spawn carries both /// a world position AND a Setup id — i.e. it would actually build a visible /// render entity (inventory items and setup-less spawns produce none and /// are skipped, matching OnLiveEntitySpawnedLocked's own guard). /// public readonly record struct RetainedSpawn(uint Guid, uint SpawnLandblockId, bool HasWorldMesh); /// /// Canonical landblock id (low 16 bits forced to 0xFFFF) — the same /// keying uses, so a /// cell-resolved spawn id (0xAAAA00CC) and a streamed landblock id /// (0xAAAAFFFF) compare equal when they name the same landblock. /// public static uint Canonicalize(uint landblockId) => (landblockId & 0xFFFF0000u) | 0xFFFFu; /// /// Select the guids whose retained spawn should be replayed for the /// just-loaded . /// /// The landblock that just (re)loaded. /// Snapshot of the retained world-object spawns. /// /// Server guids that already have a live render entity in /// . A guid here was either never dropped or was /// already re-delivered by the server, so replaying it would be redundant. /// /// /// The local player's guid — never re-hydrated here; it is owned by the /// persistent-entity rescue path ( /// + DrainRescued), which preserves the player's live pose/position /// rather than resetting it to the spawn snapshot. /// public static List SelectGuidsToRehydrate( uint loadedLandblockId, IReadOnlyCollection retainedSpawns, IReadOnlySet presentServerGuids, uint playerServerGuid) { uint loadedCanonical = Canonicalize(loadedLandblockId); var result = new List(); foreach (var s in retainedSpawns) { if (!s.HasWorldMesh) continue; // no visible entity to rebuild if (s.Guid == playerServerGuid) continue; // player: persistent-rescue path owns it if (Canonicalize(s.SpawnLandblockId) != loadedCanonical) continue; // different landblock if (presentServerGuids.Contains(s.Guid)) continue; // already rendered result.Add(s.Guid); } return result; } }