using UnityEngine; using Ashwild.Interaction; using Ashwild.Inventory; using Ashwild.Player; namespace Ashwild.Network { /// /// The single component for every world pickup — scattered ground items, hand-placed items, and /// runtime drops alike. A WorldObject backed by the PickableRegistry: scene pickups self-register /// with a baked id (>= 0); drops are spawned by the registry with a negative id. /// [DisallowMultipleComponent] public class Pickable : WorldObject, IInteractable { #region Serialized Fields [Header("Item")] [SerializeField] private ItemData itemData; [SerializeField] private int quantity = 1; [Header("Interaction")] [SerializeField] private string interactionVerb = "Pick up"; #endregion #region Public API /// /// The item this pickup grants. /// public ItemData ItemData => itemData; /// /// How many of the item this pickup grants. /// public int Quantity => quantity; /// /// Crosshair label such as "Pick up Wood". /// public string InteractionPrompt => itemData != null ? $"{interactionVerb} {itemData.ItemName}" : interactionVerb; /// /// Configures a runtime drop instance (called by the registry on each client after spawn). /// public void InitializeAsDrop(int dropId, ItemData data, int qty) { SetId(dropId); itemData = data; quantity = qty; } /// /// IInteractable entry point — runs on the interacting (owner) client. /// public void Interact() => Pickup(); /// /// Asks the registry (server) to claim this pickup for the local player. Aborts while the player /// is building (the hammer's placement/demolition would otherwise let an interact press snatch an /// item mid-build), if already claimed, or if the local inventory is full. /// /// A full inventory is announced rather than ignored: silently doing nothing on a press is /// indistinguishable from a broken pickup, and that ambiguity is what hid the real bugs. A scene /// pickup the registry never accepted (missing or duplicate baked id) is refused here too, with /// the reason spelled out — the server would drop the request anyway. /// /// The grab animation fires here, once every refusal is behind us but before the server has /// answered: the hand must reach out on the press, not a round-trip later. A refused pickup /// therefore never animates, and a granted one animates immediately. /// public void Pickup() { if (PlayerEvents.IsBuilding) return; if (itemData == null) { Debug.LogError($"[Pickable] '{name}' has no ItemData assigned.", this); return; } if (PickableRegistry.Instance == null) { Debug.LogError("[Pickable] No PickableRegistry in the scene.", this); return; } if (PickableRegistry.Instance.IsClaimed(Id)) return; if (Id >= 0 && !PickableRegistry.Instance.IsRegistered(Id)) { Debug.LogError($"[Pickable] '{name}' (id {Id}) is not tracked by the registry — the server " + "would drop this pickup. Check the console for an id error at startup.", this); return; } // Client-side pre-check so we don't claim something we can't hold. if (PlayerInventory.Instance != null && !PlayerInventory.Instance.CanFit(itemData, quantity)) { PlayerEvents.RaiseInteractionRefused("Inventory full"); return; } PlayerEvents.RaiseGrabPerformed(); PickableRegistry.Instance.RequestPickupServerRpc(Id); } #endregion #region WorldObject /// /// Only scene pickups self-register; drops are registered by the registry on spawn. /// protected override bool ShouldAutoRegister => Id >= 0; /// /// This pickup belongs to the PickableRegistry. /// protected override WorldObjectRegistry GetRegistry() => PickableRegistry.Instance; #endregion } }