using FishNet.Object; using FishNet.Object.Synchronizing; using Steamworks; using UnityEngine; namespace Ashwild.Network { /// /// Announces this player's arrival and departure to every other client's presence feed. Lives on /// the networked player prefab (like PlayerNameTag) and is NOT owner-gated — remote copies must run /// it so they can report their own despawn as a "left". /// /// Join is server-driven: the owner submits its Steam name, the server stores it and fires a /// one-shot ObserversRpc to whoever is watching right now. Because the RPC isn't buffered, a client /// joining a busy room never replays the joins that happened before it arrived — no startup burst. /// Leave is client-detected: when a player's copy despawns (disconnect), each remaining client reads /// the replicated name and reports it. The feed UI itself suppresses the departure storm that fires /// when the local player is the one leaving the session. /// [DisallowMultipleComponent] public class SessionPresenceNotifier : NetworkBehaviour { #region Networked State /// /// The owner's display name, written by the server so every client can label this player's /// "left" message once the copy despawns and the owner is already gone. /// private readonly SyncVar displayName = new SyncVar(); #endregion #region Network Lifecycle /// /// On the owning client, publishes the local Steam persona name so the server can announce the join. /// public override void OnStartClient() { base.OnStartClient(); if (!base.IsOwner) return; if (!SteamManager.Initialized) { Debug.LogError($"[SessionPresence] '{name}' — Steam not initialized, cannot announce join.", this); return; } SubmitPresenceServerRpc(SteamFriends.GetPersonaName()); } /// /// A player's copy is despawning on this client. On non-owner copies that means a remote player /// left the session, so report it; the owner never reports its own departure. Gated on the local /// client still running so that OUR own disconnect — which despawns every copy at once — doesn't /// announce the whole room leaving. /// public override void OnStopClient() { base.OnStopClient(); if (base.IsOwner || !base.IsClientStarted) return; string playerName = displayName.Value; if (!string.IsNullOrEmpty(playerName)) SessionPresenceEvents.RaisePlayerLeft(playerName); } #endregion #region Replication /// /// Server-side: records the owner's name and announces the join once to the current observers. /// [ServerRpc] private void SubmitPresenceServerRpc(string persona) { displayName.Value = persona; AnnounceJoinObserversRpc(persona); } /// /// Fires on every client observing this player at join time; each reports it to its feed, /// except the owner who shouldn't be told they joined their own session. /// [ObserversRpc] private void AnnounceJoinObserversRpc(string persona) { if (base.IsOwner) return; SessionPresenceEvents.RaisePlayerJoined(persona); } #endregion } }