using System.Collections.Generic; using FishNet.Connection; using FishNet.Object; using FishNet.Object.Synchronizing; using UnityEngine; using Ashwild.Inventory; namespace Ashwild.Network { /// /// Base registry that makes many local WorldObjects multiplayer-safe without any of them being a /// NetworkObject. It owns the shared plumbing: the synced set of "inactive" ids (claimed pickups, /// depleted harvestables), the local id → object lookup, the catch-up on join, and hide/show. /// Concrete registries (PickableRegistry, HarvestableRegistry) inherit this and add their own /// interaction RPCs and state. One instance per type per scene. /// [RequireComponent(typeof(NetworkObject))] public abstract class WorldObjectRegistry : NetworkBehaviour { #region State /// /// Ids of objects currently removed from the world (claimed / depleted), synced to all clients. /// private readonly SyncHashSet inactiveIds = new SyncHashSet(); /// /// Local lookup of registered objects by id. /// private readonly Dictionary registered = new Dictionary(); #endregion #region Unity Lifecycle /// /// Hook for subclasses to set their typed singleton; base does nothing. /// protected virtual void Awake() { } /// /// Hook for subclasses to clear their typed singleton; base does nothing. /// protected virtual void OnDestroy() { } #endregion #region Network Lifecycle public override void OnStartNetwork() { base.OnStartNetwork(); inactiveIds.OnChange += OnInactiveChanged; } public override void OnStartClient() { base.OnStartClient(); // Catch up on objects already inactive before we joined (silent — no effects). foreach (int id in inactiveIds.Collection) HideById(id, false); } public override void OnStopNetwork() { base.OnStopNetwork(); inactiveIds.OnChange -= OnInactiveChanged; } #endregion #region Public API /// /// Registers a world object, hiding it immediately if it is already inactive. /// public void RegisterObject(WorldObject obj) { if (obj == null) return; registered[obj.Id] = obj; if (inactiveIds.Contains(obj.Id)) obj.HideAsInactive(false); } /// /// Returns whether the object with this id has been removed from the world. /// public bool IsInactive(int id) => inactiveIds.Contains(id); #endregion #region Protected Helpers /// /// Looks up a registered object by id. /// protected bool TryGetObject(int id, out WorldObject obj) => registered.TryGetValue(id, out obj); /// /// Server-side: marks an object inactive (replicates → all clients hide it). /// protected void MarkInactive(int id) { if (!inactiveIds.Contains(id)) inactiveIds.Add(id); } /// /// Server-side: marks an object active again (replicates → all clients show it). /// protected void MarkActive(int id) { if (inactiveIds.Contains(id)) inactiveIds.Remove(id); } /// /// Returns the PlayerInventory on the player object owned by the given connection. /// protected PlayerInventory ResolveInventory(NetworkConnection conn) { NetworkObject playerObject = conn != null ? conn.FirstObject : null; return playerObject != null ? playerObject.GetComponent() : null; } #endregion #region Internal Helpers /// /// Hides or shows an object when its id enters or leaves the inactive set. /// private void OnInactiveChanged(SyncHashSetOperation op, int id, bool asServer) { if (op == SyncHashSetOperation.Add) HideById(id, true); else if (op == SyncHashSetOperation.Remove) ShowById(id); } private void HideById(int id, bool fresh) { if (registered.TryGetValue(id, out WorldObject obj) && obj != null) obj.HideAsInactive(fresh); } private void ShowById(int id) { if (registered.TryGetValue(id, out WorldObject obj) && obj != null) obj.ShowActive(); } #endregion } }