184 lines
6.9 KiB
C#
184 lines
6.9 KiB
C#
using UnityEngine;
|
|
using Ashwild.Inventory;
|
|
|
|
namespace Ashwild.Player
|
|
{
|
|
/// <summary>
|
|
/// 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 <b>tap</b>, 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. <b>Holding</b> 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.
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
public class BuildHammerBehaviour : MonoBehaviour, IHeldItemBehaviour
|
|
{
|
|
#region Serialized Fields
|
|
|
|
[Header("Gestures")]
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
[SerializeField] private float holdThreshold = 0.2f;
|
|
|
|
/// <summary>
|
|
/// Minimum time, in seconds, between two menu toggles so a burst of taps does not flicker the
|
|
/// menu open and shut.
|
|
/// </summary>
|
|
[SerializeField] private float toggleCooldown = 0.25f;
|
|
|
|
#endregion
|
|
|
|
#region State
|
|
|
|
/// <summary>
|
|
/// Earliest time, in seconds, the next menu toggle is allowed.
|
|
/// </summary>
|
|
private float nextToggleTime;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private bool secondaryDown;
|
|
|
|
/// <summary>
|
|
/// Time, in seconds, the current accepted press began — measured against <see cref="holdThreshold"/>.
|
|
/// </summary>
|
|
private float secondaryDownTime;
|
|
|
|
/// <summary>
|
|
/// True while the hold has crossed into demolition mode, so the release ends the mode rather
|
|
/// than toggling the menu.
|
|
/// </summary>
|
|
private bool demoActive;
|
|
|
|
#endregion
|
|
|
|
#region Unity Lifecycle
|
|
|
|
/// <summary>
|
|
/// Subscribes to the right-click hold stream for as long as the hammer is held.
|
|
/// </summary>
|
|
private void OnEnable()
|
|
{
|
|
PlayerEvents.SecondaryUseHeld += HandleSecondaryUse;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private void OnDisable()
|
|
{
|
|
PlayerEvents.SecondaryUseHeld -= HandleSecondaryUse;
|
|
if (demoActive) ExitDemolition();
|
|
secondaryDown = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private void Update()
|
|
{
|
|
if (!secondaryDown || demoActive) return;
|
|
if (PlayerEvents.InputLocked || PlayerEvents.IsPlacingBuild) return;
|
|
if (Time.time - secondaryDownTime < holdThreshold) return;
|
|
|
|
EnterDemolition();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IHeldItemBehaviour
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public void Setup(HeldItemContext context, ItemData item)
|
|
{
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event Handlers
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
private static bool HardLocked =>
|
|
PlayerEvents.IsDead || PlayerEvents.IsInventoryOpen || PlayerEvents.IsChestOpen || PlayerEvents.IsPaused;
|
|
|
|
/// <summary>
|
|
/// Requests the construction menu to open/close, gated by the toggle cooldown.
|
|
/// </summary>
|
|
private void ToggleMenu()
|
|
{
|
|
if (Time.time < nextToggleTime) return;
|
|
nextToggleTime = Time.time + toggleCooldown;
|
|
PlayerEvents.RaiseBuildMenuToggleRequested();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Enters demolition mode and announces it on the bus.
|
|
/// </summary>
|
|
private void EnterDemolition()
|
|
{
|
|
demoActive = true;
|
|
PlayerEvents.RaiseDemolishModeChanged(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Leaves demolition mode and announces it on the bus.
|
|
/// </summary>
|
|
private void ExitDemolition()
|
|
{
|
|
demoActive = false;
|
|
PlayerEvents.RaiseDemolishModeChanged(false);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|