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;
}
}