using UnityEngine; using Ashwild.Inventory; namespace Ashwild.Player { /// /// Held-item logic for the build hammer, placed on its hand prefab. It is the single arbiter of the /// right-click gesture in a build context, so the menu can never fight a placement over the same /// click. One right-click is a tap, resolved on release as a clean build-mode toggle: while /// placing a ghost it cancels the placement (back to idle, no menu), otherwise it opens/closes the /// construction menu. Holding right-click past a short threshold instead enters demolition /// mode for as long as it is held. The tap is gated only by hard UI locks (dead, inventory, chest, /// pause) — not by the build menu being open (so a tap closes it) nor by a placement being active /// (so a tap cancels it). The hammer stays a pure input source: it only raises bus requests; /// BuildManager owns the menu and the world-facing targeting/destroy, so this never reaches for either. /// [DisallowMultipleComponent] public class BuildHammerBehaviour : MonoBehaviour, IHeldItemBehaviour { #region Serialized Fields [Header("Gestures")] /// /// How long, in seconds, right-click must stay held before it counts as a demolition hold /// instead of a menu tap. A release before this opens/closes the menu. /// [SerializeField] private float holdThreshold = 0.2f; /// /// Minimum time, in seconds, between two menu toggles so a burst of taps does not flicker the /// menu open and shut. /// [SerializeField] private float toggleCooldown = 0.25f; #endregion #region State /// /// Earliest time, in seconds, the next menu toggle is allowed. /// private float nextToggleTime; /// /// True between an accepted press and its release. A press ignored at the gate (input locked /// or already placing) never sets this, so its release is ignored too. /// private bool secondaryDown; /// /// Time, in seconds, the current accepted press began — measured against . /// private float secondaryDownTime; /// /// True while the hold has crossed into demolition mode, so the release ends the mode rather /// than toggling the menu. /// private bool demoActive; #endregion #region Unity Lifecycle /// /// Subscribes to the right-click hold stream for as long as the hammer is held. /// private void OnEnable() { PlayerEvents.SecondaryUseHeld += HandleSecondaryUse; } /// /// Unsubscribes and leaves demolition mode cleanly when the hammer is put away (its prefab is /// destroyed mid-hold), so the mode never sticks on with no hammer to release it. /// private void OnDisable() { PlayerEvents.SecondaryUseHeld -= HandleSecondaryUse; if (demoActive) ExitDemolition(); secondaryDown = false; } /// /// Promotes a sustained press into demolition mode once it outlasts the tap threshold. Held /// out of Update (not a timer) so the mode drops instantly if input locks or a placement /// begins mid-hold. /// private void Update() { if (!secondaryDown || demoActive) return; if (PlayerEvents.InputLocked || PlayerEvents.IsPlacingBuild) return; if (Time.time - secondaryDownTime < holdThreshold) return; EnterDemolition(); } #endregion #region IHeldItemBehaviour /// /// Nothing to link: the hammer only raises bus requests. Present to satisfy the held-item /// contract; BuildManager holds the player/world refs the build UX needs. /// public void Setup(HeldItemContext context, ItemData item) { } #endregion #region Event Handlers /// /// The single right-click gesture, split by press and release. A press is tracked unless a hard /// UI lock is up; the build menu being open or a placement being active do NOT block it, because /// this gesture is what drives those states. The release resolves the gesture in priority order: /// a hold that engaged demolition leaves demolition; a tap while placing a ghost cancels it /// (the single arbiter — BuildManager no longer listens to the raw click, so the menu can't /// reopen on the same click); otherwise a tap toggles the construction menu. /// private void HandleSecondaryUse(bool held) { if (held) { if (HardLocked) return; secondaryDown = true; secondaryDownTime = Time.time; return; } if (!secondaryDown) return; secondaryDown = false; if (demoActive) { ExitDemolition(); return; } if (PlayerEvents.IsPlacingBuild) { PlayerEvents.RaiseBuildCancelRequested(); return; } ToggleMenu(); } #endregion #region Internal Helpers /// /// A hard UI lock (dead, inventory, chest, pause) where right-click must do nothing. Unlike the /// bus's InputLocked, it deliberately ignores the build menu being open and a placement being /// active — both are build states this gesture is meant to drive (close the menu, cancel the /// placement), not locks that should swallow the click. /// private static bool HardLocked => PlayerEvents.IsDead || PlayerEvents.IsInventoryOpen || PlayerEvents.IsChestOpen || PlayerEvents.IsPaused; /// /// Requests the construction menu to open/close, gated by the toggle cooldown. /// private void ToggleMenu() { if (Time.time < nextToggleTime) return; nextToggleTime = Time.time + toggleCooldown; PlayerEvents.RaiseBuildMenuToggleRequested(); } /// /// Enters demolition mode and announces it on the bus. /// private void EnterDemolition() { demoActive = true; PlayerEvents.RaiseDemolishModeChanged(true); } /// /// Leaves demolition mode and announces it on the bus. /// private void ExitDemolition() { demoActive = false; PlayerEvents.RaiseDemolishModeChanged(false); } #endregion } }