using FishNet.Connection; using FishNet.Object; using Ashwild.Inventory; namespace Ashwild.Network { /// /// Server-side lookup of the player systems belonging to a connection. Every server-authoritative /// interaction (pickup, harvest, chest, cooking station, build refund) needs the requesting player's /// inventory, and they all used to resolve it through conn.FirstObject. /// /// That is unsafe: a connection owns more than one NetworkObject (the build ghost is spawned with the /// player as owner), FishNet stores them in an unordered HashSet, and it re-picks FirstObject /// from that set whenever the current one is despawned. FirstObject can therefore resolve to the ghost /// instead of the player — a null inventory, and a silently lost grant. Scanning the connection's /// objects for the component we actually want removes the guesswork. /// public static class NetworkPlayerLookup { #region Public API /// /// Returns the inventory of the player owned by this connection, or null when the connection has /// no player object (disconnecting, or not spawned yet). /// public static PlayerInventory ResolveInventory(NetworkConnection conn) => Resolve(conn); /// /// Returns the first component of the requested type found on any NetworkObject this connection /// owns. Checks FirstObject before scanning, since it is the right answer in the common case. /// public static T Resolve(NetworkConnection conn) where T : class { if (conn == null) return null; NetworkObject first = conn.FirstObject; if (first != null && first.TryGetComponent(out T fromFirst)) return fromFirst; foreach (NetworkObject nob in conn.Objects) { if (nob != null && nob.TryGetComponent(out T component)) return component; } return null; } #endregion } }