Files
Emberwild/Assets/GAME/Script/Player/HeldItems/ToolBehaviour.cs
T
2026-06-22 16:18:34 +02:00

182 lines
6.0 KiB
C#

using UnityEngine;
using Ashwild.Harvesting;
using Ashwild.Inventory;
using Ashwild.Network;
namespace Ashwild.Player
{
/// <summary>
/// Held-item logic for tools, placed on the tool's hand prefab (axe, pickaxe, ...).
/// While the prefab is equipped it listens to the use input and harvests whatever
/// the player aims at, scaled by the tool's own ItemData (ToolPower, HarvestType).
/// No animation here — the FPS arm model plays its own; this is pure logic.
/// </summary>
[DisallowMultipleComponent]
public class ToolBehaviour : MonoBehaviour, IHeldItemBehaviour
{
#region Serialized Fields
[Header("Harvesting")]
/// <summary>
/// Maximum distance, in metres, at which a harvestable can be hit.
/// </summary>
[SerializeField] private float harvestRange = 3f;
/// <summary>
/// Layers the harvest ray is allowed to hit.
/// </summary>
[SerializeField] private LayerMask harvestMask = ~0;
[Header("Damage")]
/// <summary>
/// Base damage before the tool's ToolPower multiplier is applied.
/// </summary>
[SerializeField] private float baseDamage = 10f;
/// <summary>
/// Damage multiplier when the tool does not match the target type (0 = strict).
/// </summary>
[SerializeField, Range(0f, 1f)] private float wrongToolMultiplier = 0.25f;
[Header("Cooldown")]
/// <summary>
/// Minimum time, in seconds, between two swings.
/// </summary>
[SerializeField] private float swingCooldown = 0.5f;
#endregion
#region State
/// <summary>
/// Aim ray injected by the player when this item is drawn.
/// </summary>
private Transform raycastOrigin;
/// <summary>
/// The ItemData this held prefab represents, used for damage scaling.
/// </summary>
private ItemData item;
/// <summary>
/// Earliest time, in seconds, the next swing is allowed.
/// </summary>
private float nextSwingTime;
#endregion
#region Unity Lifecycle
/// <summary>
/// Subscribes to the use input for as long as this item is held.
/// </summary>
private void OnEnable()
{
PlayerEvents.AttackPressed += HandleAttackPressed;
}
/// <summary>
/// Unsubscribes when the item is put away (the prefab is destroyed).
/// </summary>
private void OnDisable()
{
PlayerEvents.AttackPressed -= HandleAttackPressed;
}
#endregion
#region IHeldItemBehaviour
/// <summary>
/// Links this tool to the player: stores the aim ray and the tool's ItemData.
/// </summary>
public void Setup(HeldItemContext context, ItemData item)
{
raycastOrigin = context != null ? context.RaycastOrigin : null;
this.item = item;
}
#endregion
#region Event Handlers
/// <summary>
/// Swings on use input (gated by lock, setup and cooldown) and harvests the
/// aimed target.
/// </summary>
private void HandleAttackPressed()
{
if (PlayerEvents.InputLocked) return;
if (raycastOrigin == null) return;
if (Time.time < nextSwingTime) return;
nextSwingTime = Time.time + swingCooldown;
PlayerEvents.RaiseAttackSwung();
TryHarvest();
}
#endregion
#region Harvesting
/// <summary>
/// Raycasts forward and applies a hit to the harvestable found, or a blocked
/// "clunk" when this tool deals no damage to that target type.
/// </summary>
private void TryHarvest()
{
if (!Physics.Raycast(raycastOrigin.position, raycastOrigin.forward, out RaycastHit hit, harvestRange, harvestMask))
return;
Harvestable harvestable = hit.collider.GetComponentInParent<Harvestable>();
if (harvestable == null) return;
Vector3 hitDirection = raycastOrigin.forward;
float damage = CalculateDamage(harvestable.HarvestType);
// No proper tool match: the hit is blocked — no resources, no damage,
// just a small "clunk" so it reads as "you need a tool for this".
if (damage <= 0f)
{
harvestable.PlayBlockedFeedback(hitDirection);
return;
}
if (HarvestableRegistry.Instance == null)
{
Debug.LogError("[ToolBehaviour] No HarvestableRegistry in the scene — cannot harvest.", this);
return;
}
// Already depleted (e.g. someone else just felled it): ignore.
if (HarvestableRegistry.Instance.IsInactive(harvestable.Id)) return;
// Play the hit feedback locally for instant response; the server replays it to others.
harvestable.PlayHitEffect(hitDirection);
// Server applies the damage authoritatively, grants loot, and handles depletion/respawn.
ushort toolId = ItemDatabase.Instance != null ? ItemDatabase.Instance.GetId(item) : (ushort)0;
HarvestableRegistry.Instance.RequestHitServerRpc(harvestable.Id, damage, toolId, hitDirection);
PlayerEvents.RaiseHarvestableHit(harvestable, damage);
}
/// <summary>
/// Damage against a target type: full when the tool matches, reduced (or zero)
/// otherwise. Zero when this prefab has no ItemData.
/// </summary>
private float CalculateDamage(HarvestType targetType)
{
if (item == null) return 0f;
if (item.HarvestType == targetType) return baseDamage * item.ToolPower;
// Wrong tool type: reduced damage. Set wrongToolMultiplier to 0 for a strict "right tool only" rule.
return baseDamage * item.ToolPower * wrongToolMultiplier;
}
#endregion
}
}