Merge remote-tracking branch 'origin/feat/inport-props' into feat/craft-discovery
# Conflicts: # Assets/External/Animated PBR Chest Demo/Materials/WoodChest.mat # Packages/com.distantlands.cozy.core/Content/Integration/Import for BiRP.unitypackage.meta # Packages/com.distantlands.cozy.core/Content/Integration/Import for HDRP.unitypackage.meta # Packages/com.distantlands.cozy.core/Content/Integration/Import for URP.unitypackage.meta
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
using UnityEngine;
|
||||
using Ashwild.Harvesting;
|
||||
using Ashwild.Inventory;
|
||||
using Ashwild.Network;
|
||||
|
||||
namespace Ashwild.Player
|
||||
{
|
||||
@@ -44,6 +43,13 @@ namespace Ashwild.Player
|
||||
/// </summary>
|
||||
[SerializeField] private float swingCooldown = 0.5f;
|
||||
|
||||
[Header("Impact")]
|
||||
/// <summary>
|
||||
/// Delay after which a swing resolves on its own when the animation never reports its contact
|
||||
/// frame. Safety net for an item whose attack clip carries no AnimAttackImpact event.
|
||||
/// </summary>
|
||||
[SerializeField] private float impactFallbackDelay = 0.2f;
|
||||
|
||||
#endregion
|
||||
|
||||
#region State
|
||||
@@ -63,16 +69,27 @@ namespace Ashwild.Player
|
||||
/// </summary>
|
||||
private float nextSwingTime;
|
||||
|
||||
/// <summary>
|
||||
/// True between a swing starting and its hit being resolved.
|
||||
/// </summary>
|
||||
private bool swingPending;
|
||||
|
||||
/// <summary>
|
||||
/// Time at which a pending swing resolves itself without an impact event.
|
||||
/// </summary>
|
||||
private float impactDeadline;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unity Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the use input for as long as this item is held.
|
||||
/// Subscribes to the use input and to the animation's contact frame for as long as this item is held.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
PlayerEvents.AttackPressed += HandleAttackPressed;
|
||||
PlayerEvents.AttackImpact += HandleAttackImpact;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,6 +98,19 @@ namespace Ashwild.Player
|
||||
private void OnDisable()
|
||||
{
|
||||
PlayerEvents.AttackPressed -= HandleAttackPressed;
|
||||
PlayerEvents.AttackImpact -= HandleAttackImpact;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a swing whose animation never announced its contact frame, so an item with no impact
|
||||
/// event still harvests instead of swinging forever without effect. Runs only while a swing is in
|
||||
/// flight.
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (!swingPending) return;
|
||||
if (Time.time < impactDeadline) return;
|
||||
ResolveSwing();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -101,9 +131,10 @@ namespace Ashwild.Player
|
||||
#region Event Handlers
|
||||
|
||||
/// <summary>
|
||||
/// Swings on use input (gated by lock, setup and cooldown) and harvests the
|
||||
/// aimed target. A depleted but non-destroyed tool (a worn-out repairable axe) is
|
||||
/// unusable: the swing is blocked until it is repaired.
|
||||
/// Starts a swing on use input (gated by lock, setup and cooldown). The hit is deliberately not
|
||||
/// resolved here: it waits for the animation's contact frame, so the tree only loses a chunk once
|
||||
/// the blade has actually reached it. A depleted but non-destroyed tool (a worn-out repairable
|
||||
/// axe) is unusable: the swing is blocked until it is repaired.
|
||||
/// </summary>
|
||||
private void HandleAttackPressed()
|
||||
{
|
||||
@@ -113,8 +144,33 @@ namespace Ashwild.Player
|
||||
if (Time.time < nextSwingTime) return;
|
||||
|
||||
nextSwingTime = Time.time + swingCooldown;
|
||||
swingPending = true;
|
||||
impactDeadline = Time.time + impactFallbackDelay;
|
||||
|
||||
PlayerEvents.RaiseAttackSwung();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The swing animation reached its contact frame. Ignored when no swing of ours is in flight, so
|
||||
/// an impact event belonging to a previous item cannot make a freshly drawn tool hit for free.
|
||||
/// </summary>
|
||||
private void HandleAttackImpact()
|
||||
{
|
||||
if (!swingPending) return;
|
||||
ResolveSwing();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Swing Resolution
|
||||
|
||||
/// <summary>
|
||||
/// Closes an in-flight swing and applies its hit. Clearing the pending flag first makes the swing
|
||||
/// resolve exactly once whether the impact event or the fallback timer got here first.
|
||||
/// </summary>
|
||||
private void ResolveSwing()
|
||||
{
|
||||
swingPending = false;
|
||||
TryHarvest();
|
||||
}
|
||||
|
||||
@@ -124,47 +180,31 @@ namespace Ashwild.Player
|
||||
|
||||
/// <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.
|
||||
/// "clunk" when this tool deals no damage to that target type. The hit itself goes through
|
||||
/// Harvestable.ApplyHit (the shared path with bare-hand gathering); the tool is only worn when
|
||||
/// that hit actually connects, never on a swing into the air or a blocked clunk. Triggers are
|
||||
/// included so walkable harvestables (e.g. bushes with a trigger collider) can still be hit.
|
||||
/// </summary>
|
||||
private void TryHarvest()
|
||||
{
|
||||
if (!Physics.Raycast(raycastOrigin.position, raycastOrigin.forward, out RaycastHit hit, harvestRange, harvestMask))
|
||||
if (!Physics.Raycast(raycastOrigin.position, raycastOrigin.forward, out RaycastHit hit,
|
||||
harvestRange, harvestMask, QueryTriggerInteraction.Collide))
|
||||
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);
|
||||
harvestable.PlayBlockedFeedback();
|
||||
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);
|
||||
|
||||
// Wear the tool only on a real connecting hit, never on a swing into the air or a blocked clunk.
|
||||
if (PlayerInventory.Instance != null)
|
||||
if (harvestable.ApplyHit(item, damage) && PlayerInventory.Instance != null)
|
||||
PlayerInventory.Instance.ConsumeSelectedToolUse(1);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user