using System.Collections.Generic; using UnityEngine; using Ashwild.Network; using Ashwild.Player; namespace Ashwild.UI { /// /// The in-game feed that spawns a "X a rejoint / a quitté la partie" line whenever a remote player /// joins or leaves the session. Listens to the network layer's SessionPresenceEvents (raised on /// every client, host or not) and instantiates a PresenceFeedEntry under a holder whose layout group /// stacks the lines. Drop this on the holder (or any object referencing it) in the game scene. /// /// While the local player is tearing the session down (returning to the menu, or the host stopping) /// every remote copy despawns at once, which would spam "left" lines; this feed goes quiet on those /// bus events so that storm never reaches the screen. /// [DisallowMultipleComponent] public class PlayerPresenceFeedUI : MonoBehaviour { #region Serialized Fields [Header("References")] [Tooltip("Holder the entries are parented to — give it a VerticalLayoutGroup so lines stack.")] [SerializeField] private RectTransform holder; [Tooltip("Prefab with a PresenceFeedEntry (CanvasGroup + label) instantiated per notification.")] [SerializeField] private PresenceFeedEntry entryPrefab; [Header("Messages")] [Tooltip("Join line format — {0} is the player name.")] [SerializeField] private string joinFormat = "{0} a rejoint la partie"; [Tooltip("Leave line format — {0} is the player name.")] [SerializeField] private string leaveFormat = "{0} a quitté la partie"; [Header("Colours")] [SerializeField] private Color joinColor = new Color(0.45f, 1f, 0.45f); [SerializeField] private Color leaveColor = new Color(1f, 0.55f, 0.45f); [Header("Timing")] [Tooltip("Seconds a line stays fully visible before fading out.")] [SerializeField] private float holdDuration = 4f; [SerializeField] private float fadeInDuration = 0.3f; [SerializeField] private float fadeOutDuration = 0.5f; [Header("Limits")] [Tooltip("Oldest lines are removed once this many are on screen at once.")] [SerializeField] private int maxEntries = 5; #endregion #region State /// /// Currently displayed lines, oldest first — trimmed from the front when over the cap. /// private readonly List activeEntries = new List(); /// /// True once the session is tearing down, so the despawn-driven "left" storm is ignored. /// private bool suppressed; #endregion #region Unity Lifecycle /// /// Subscribes to presence notifications and the teardown signals that mute the feed. /// private void OnEnable() { SessionPresenceEvents.PlayerJoined += HandlePlayerJoined; SessionPresenceEvents.PlayerLeft += HandlePlayerLeft; PlayerEvents.ReturningToMenu += HandleTeardown; PlayerEvents.SessionStopped += HandleTeardown; } /// /// Unsubscribes — mirrors OnEnable exactly. /// private void OnDisable() { SessionPresenceEvents.PlayerJoined -= HandlePlayerJoined; SessionPresenceEvents.PlayerLeft -= HandlePlayerLeft; PlayerEvents.ReturningToMenu -= HandleTeardown; PlayerEvents.SessionStopped -= HandleTeardown; } #endregion #region Event Handlers /// /// Shows a green arrival line for the named player. /// private void HandlePlayerJoined(string playerName) => Push(string.Format(joinFormat, playerName), joinColor); /// /// Shows an orange departure line for the named player. /// private void HandlePlayerLeft(string playerName) => Push(string.Format(leaveFormat, playerName), leaveColor); /// /// Mutes the feed for the rest of its lifetime once the session is being torn down. /// private void HandleTeardown() => suppressed = true; #endregion #region Internal Helpers /// /// Instantiates a feed line, trimming the oldest ones past the cap. Ignored while muted or when /// a reference is missing (logged once so a mis-wired holder/prefab is obvious). /// private void Push(string message, Color color) { if (suppressed) return; if (holder == null || entryPrefab == null) { Debug.LogError("[PresenceFeed] Holder or entry prefab not assigned — cannot show notifications.", this); return; } activeEntries.RemoveAll(e => e == null); while (activeEntries.Count >= maxEntries) { PresenceFeedEntry oldest = activeEntries[0]; activeEntries.RemoveAt(0); if (oldest != null) Destroy(oldest.gameObject); } PresenceFeedEntry entry = Instantiate(entryPrefab, holder); entry.Play(message, color, holdDuration, fadeInDuration, fadeOutDuration); activeEntries.Add(entry); } #endregion } }