using System.Collections; using System.Collections.Generic; using FishNet.Connection; using FishNet.Object; using UnityEngine; using Ashwild.Harvesting; using Ashwild.Inventory; namespace Ashwild.Network { /// /// Registry for Harvestables. On top of the shared WorldObjectRegistry plumbing (depleted ids, /// hide/show, catch-up) it owns the authoritative, server-side health per object, applies hits, /// grants the rolled loot to the hitting player, broadcasts the hit feedback to everyone, and /// schedules respawns. One per scene. Damage is computed client-side (client-authoritative, co-op). /// public class HarvestableRegistry : WorldObjectRegistry { #region Singleton /// /// The harvestable registry for the active game scene. /// public static HarvestableRegistry Instance { get; private set; } #endregion #region State /// /// Authoritative current health per harvestable id (server only, lazy-initialised to MaxHealth). /// private readonly Dictionary health = new Dictionary(); #endregion #region Lifecycle protected override void Awake() { base.Awake(); if (Instance != null && Instance != this) { Destroy(this); return; } Instance = this; } protected override void OnDestroy() { base.OnDestroy(); if (Instance == this) Instance = null; } #endregion #region Public API /// /// Owner-side entry: tells the server the local player hit a harvestable. The server applies /// the (client-computed) damage, grants loot, broadcasts feedback, and handles depletion. /// [ServerRpc(RequireOwnership = false)] public void RequestHitServerRpc(int id, float damage, ushort toolItemId, Vector3 hitDirection, NetworkConnection conn = null) { if (IsInactive(id)) return; if (!TryGetObject(id, out WorldObject obj) || obj is not Harvestable harvestable) return; if (!health.TryGetValue(id, out float hp)) hp = harvestable.MaxHealth; hp -= damage; health[id] = hp; // Grant the rolled loot to the hitting player. ItemData tool = ItemDatabase.Instance != null ? ItemDatabase.Instance.GetItem(toolItemId) : null; PlayerInventory inventory = ResolveInventory(conn); if (inventory != null) { foreach ((ItemData item, int quantity) in harvestable.RollDrops(tool)) inventory.GrantItemFromServer(item, quantity); } // Feedback for everyone except the hitter (who already played it locally for responsiveness). int hitterClientId = conn != null ? conn.ClientId : -1; PlayHitObserversRpc(id, hitDirection, hitterClientId); if (hp <= 0f) { health.Remove(id); MarkInactive(id); if (harvestable.CanRespawn) StartCoroutine(RespawnRoutine(id, harvestable.RespawnDelay)); } } #endregion #region Network Feedback /// /// Plays the hit feedback on every client except the one who threw the hit. /// [ObserversRpc] private void PlayHitObserversRpc(int id, Vector3 hitDirection, int hitterClientId) { if (LocalConnection != null && LocalConnection.ClientId == hitterClientId) return; if (TryGetObject(id, out WorldObject obj) && obj is Harvestable harvestable) harvestable.PlayHitEffect(hitDirection); } #endregion #region Internal Helpers /// /// Brings a depleted harvestable back after its respawn delay (server-side). /// private IEnumerator RespawnRoutine(int id, float delay) { yield return new WaitForSeconds(delay); MarkActive(id); } #endregion } }