(Feat) Item Dqta
This commit is contained in:
@@ -104,7 +104,7 @@ MonoBehaviour:
|
|||||||
m_OnCullStateChanged:
|
m_OnCullStateChanged:
|
||||||
m_PersistentCalls:
|
m_PersistentCalls:
|
||||||
m_Calls: []
|
m_Calls: []
|
||||||
m_Sprite: {fileID: 0}
|
m_Sprite: {fileID: 21300000, guid: 2237e4ae38354ff459a9b61781694de6, type: 3}
|
||||||
m_Type: 0
|
m_Type: 0
|
||||||
m_PreserveAspect: 0
|
m_PreserveAspect: 0
|
||||||
m_FillCenter: 1
|
m_FillCenter: 1
|
||||||
@@ -114,6 +114,171 @@ MonoBehaviour:
|
|||||||
m_FillOrigin: 0
|
m_FillOrigin: 0
|
||||||
m_UseSpriteMesh: 0
|
m_UseSpriteMesh: 0
|
||||||
m_PixelsPerUnitMultiplier: 1
|
m_PixelsPerUnitMultiplier: 1
|
||||||
|
--- !u!1 &5573310796372301174
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 6703316971934351753}
|
||||||
|
- component: {fileID: 4480407019944074660}
|
||||||
|
- component: {fileID: 2465922160796796964}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: Image (1)
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!224 &6703316971934351753
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 5573310796372301174}
|
||||||
|
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 5909651071786952382}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0.5, y: 0}
|
||||||
|
m_AnchorMax: {x: 0.5, y: 0}
|
||||||
|
m_AnchoredPosition: {x: 0, y: -0.00010586}
|
||||||
|
m_SizeDelta: {x: 80.3081, y: 8.2002}
|
||||||
|
m_Pivot: {x: 0.5, y: 0}
|
||||||
|
--- !u!222 &4480407019944074660
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 5573310796372301174}
|
||||||
|
m_CullTransparentMesh: 1
|
||||||
|
--- !u!114 &2465922160796796964
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 5573310796372301174}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 0.4862745, g: 0.9882354, b: 0, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
m_Maskable: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_Sprite: {fileID: 21300000, guid: 89cc97401eb588a408315f5fa0148929, type: 3}
|
||||||
|
m_Type: 3
|
||||||
|
m_PreserveAspect: 0
|
||||||
|
m_FillCenter: 1
|
||||||
|
m_FillMethod: 0
|
||||||
|
m_FillAmount: 0.76
|
||||||
|
m_FillClockwise: 1
|
||||||
|
m_FillOrigin: 0
|
||||||
|
m_UseSpriteMesh: 0
|
||||||
|
m_PixelsPerUnitMultiplier: 2.8
|
||||||
|
--- !u!1 &5983078061427925415
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 5909651071786952382}
|
||||||
|
- component: {fileID: 4758672237807855567}
|
||||||
|
- component: {fileID: 6423672678987582984}
|
||||||
|
- component: {fileID: 5188521778652969812}
|
||||||
|
m_Layer: 5
|
||||||
|
m_Name: Mask Durability
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 0
|
||||||
|
--- !u!224 &5909651071786952382
|
||||||
|
RectTransform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 5983078061427925415}
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children:
|
||||||
|
- {fileID: 6703316971934351753}
|
||||||
|
m_Father: {fileID: 4729973786354734962}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
m_AnchorMin: {x: 0.5, y: 0}
|
||||||
|
m_AnchorMax: {x: 0.5, y: 0}
|
||||||
|
m_AnchoredPosition: {x: 0, y: 9.200001}
|
||||||
|
m_SizeDelta: {x: 80.3081, y: 8.2001}
|
||||||
|
m_Pivot: {x: 0.5, y: 0}
|
||||||
|
--- !u!222 &4758672237807855567
|
||||||
|
CanvasRenderer:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 5983078061427925415}
|
||||||
|
m_CullTransparentMesh: 1
|
||||||
|
--- !u!114 &6423672678987582984
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 5983078061427925415}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
|
||||||
|
m_Material: {fileID: 0}
|
||||||
|
m_Color: {r: 0.3056603, g: 0.3056603, b: 0.3056603, a: 1}
|
||||||
|
m_RaycastTarget: 1
|
||||||
|
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||||
|
m_Maskable: 1
|
||||||
|
m_OnCullStateChanged:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
m_Sprite: {fileID: 21300000, guid: b3f0d976f6d6802479d6465d11b3aa68, type: 3}
|
||||||
|
m_Type: 1
|
||||||
|
m_PreserveAspect: 0
|
||||||
|
m_FillCenter: 1
|
||||||
|
m_FillMethod: 4
|
||||||
|
m_FillAmount: 1
|
||||||
|
m_FillClockwise: 1
|
||||||
|
m_FillOrigin: 0
|
||||||
|
m_UseSpriteMesh: 0
|
||||||
|
m_PixelsPerUnitMultiplier: 7.79
|
||||||
|
--- !u!114 &5188521778652969812
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 5983078061427925415}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Mask
|
||||||
|
m_ShowMaskGraphic: 1
|
||||||
--- !u!1 &6747954976634286854
|
--- !u!1 &6747954976634286854
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -219,6 +384,7 @@ RectTransform:
|
|||||||
m_ConstrainProportionsScale: 0
|
m_ConstrainProportionsScale: 0
|
||||||
m_Children:
|
m_Children:
|
||||||
- {fileID: 850401354156706093}
|
- {fileID: 850401354156706093}
|
||||||
|
- {fileID: 5909651071786952382}
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
m_AnchorMin: {x: 0, y: 0}
|
m_AnchorMin: {x: 0, y: 0}
|
||||||
@@ -241,6 +407,10 @@ MonoBehaviour:
|
|||||||
iconImage: {fileID: 6241670606430176389}
|
iconImage: {fileID: 6241670606430176389}
|
||||||
quantityText: {fileID: 2578968024406161774}
|
quantityText: {fileID: 2578968024406161774}
|
||||||
highlightImage: {fileID: 9078470808336705615}
|
highlightImage: {fileID: 9078470808336705615}
|
||||||
|
usageBarRoot: {fileID: 5983078061427925415}
|
||||||
|
usageBarFill: {fileID: 2465922160796796964}
|
||||||
|
normalTint: {r: 0.4862745, g: 0.9882354, b: 0, a: 1}
|
||||||
|
depletedTint: {r: 1, g: 0, b: 0, a: 1}
|
||||||
--- !u!1 &8681194172915985056
|
--- !u!1 &8681194172915985056
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
@@ -1,31 +1,63 @@
|
|||||||
|
|
||||||
namespace Ashwild.Inventory
|
namespace Ashwild.Inventory
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A single inventory cell: the item it holds, how many, and — for items that track uses
|
||||||
|
/// (tool durability, multi-bite food) — the remaining uses of this specific instance. Items
|
||||||
|
/// with uses never stack, so a slot's uses always describe one concrete instance.
|
||||||
|
/// </summary>
|
||||||
[System.Serializable]
|
[System.Serializable]
|
||||||
public class InventorySlot
|
public class InventorySlot
|
||||||
{
|
{
|
||||||
private ItemData itemData;
|
private ItemData itemData;
|
||||||
private int quantity;
|
private int quantity;
|
||||||
|
private int currentUses;
|
||||||
|
|
||||||
public ItemData ItemData => itemData;
|
public ItemData ItemData => itemData;
|
||||||
public int Quantity => quantity;
|
public int Quantity => quantity;
|
||||||
|
public int CurrentUses => currentUses;
|
||||||
public bool IsEmpty => itemData == null;
|
public bool IsEmpty => itemData == null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this slot holds a uses-tracked item whose uses have run out — kept in the
|
||||||
|
/// inventory (shown red and unusable) when the item is not destroyed on depletion.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDepleted => itemData != null && itemData.HasUses && currentUses <= 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Places an item in the slot, resetting its uses to full for uses-tracked items.
|
||||||
|
/// </summary>
|
||||||
public void Set(ItemData data, int qty)
|
public void Set(ItemData data, int qty)
|
||||||
{
|
{
|
||||||
itemData = data;
|
itemData = data;
|
||||||
quantity = qty;
|
quantity = qty;
|
||||||
|
currentUses = (data != null && data.HasUses) ? data.MaxUses : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Places an item with an explicit remaining-uses value, used when restoring a partially
|
||||||
|
/// used instance (e.g. picking a dropped tool back up). A negative value means "full".
|
||||||
|
/// </summary>
|
||||||
|
public void Set(ItemData data, int qty, int uses)
|
||||||
|
{
|
||||||
|
itemData = data;
|
||||||
|
quantity = qty;
|
||||||
|
if (data != null && data.HasUses)
|
||||||
|
currentUses = uses < 0 ? data.MaxUses : uses;
|
||||||
|
else
|
||||||
|
currentUses = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
itemData = null;
|
itemData = null;
|
||||||
quantity = 0;
|
quantity = 0;
|
||||||
|
currentUses = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int AddQuantity(int amount)
|
public int AddQuantity(int amount)
|
||||||
{
|
{
|
||||||
if (itemData == null || !itemData.IsStackable)
|
if (itemData == null || !itemData.IsStackable || itemData.HasUses)
|
||||||
return amount;
|
return amount;
|
||||||
|
|
||||||
int space = itemData.MaxStackSize - quantity;
|
int space = itemData.MaxStackSize - quantity;
|
||||||
@@ -41,9 +73,25 @@ namespace Ashwild.Inventory
|
|||||||
Clear();
|
Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spends uses on this instance (a tool hit, a food bite), clamped at 0. Has no effect on
|
||||||
|
/// items that do not track uses.
|
||||||
|
/// </summary>
|
||||||
|
public void ConsumeUse(int amount = 1)
|
||||||
|
{
|
||||||
|
if (itemData == null || !itemData.HasUses) return;
|
||||||
|
currentUses -= amount;
|
||||||
|
if (currentUses < 0) currentUses = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uses-tracked items are non-stackable, so they never merge into an existing slot —
|
||||||
|
/// every instance keeps its own uses bar.
|
||||||
|
/// </summary>
|
||||||
public bool CanAccept(ItemData data)
|
public bool CanAccept(ItemData data)
|
||||||
{
|
{
|
||||||
if (IsEmpty) return true;
|
if (IsEmpty) return true;
|
||||||
|
if (data != null && data.HasUses) return false;
|
||||||
return itemData == data && itemData.IsStackable && quantity < itemData.MaxStackSize;
|
return itemData == data && itemData.IsStackable && quantity < itemData.MaxStackSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ namespace Ashwild.Inventory
|
|||||||
[Header("Type")]
|
[Header("Type")]
|
||||||
[SerializeField] private ItemType itemType;
|
[SerializeField] private ItemType itemType;
|
||||||
|
|
||||||
|
[Header("Uses / Durability")]
|
||||||
|
[Tooltip("How many times this item can be used before it is depleted (a tool's hits, a food's bites). 0 = no usage tracking — a normal item without a uses bar.")]
|
||||||
|
[SerializeField] private int maxUses = 0;
|
||||||
|
[Tooltip("When the uses reach 0: checked = the item stays in the inventory, shown red and unusable (repairable tool, refillable container); unchecked = it is destroyed.")]
|
||||||
|
[SerializeField] private bool keepWhenDepleted = false;
|
||||||
|
|
||||||
[Header("Tool")]
|
[Header("Tool")]
|
||||||
[SerializeField] private HarvestType harvestType = HarvestType.None;
|
[SerializeField] private HarvestType harvestType = HarvestType.None;
|
||||||
[SerializeField] private float toolPower = 1f;
|
[SerializeField] private float toolPower = 1f;
|
||||||
@@ -59,6 +65,21 @@ namespace Ashwild.Inventory
|
|||||||
public bool IsStackable => isStackable;
|
public bool IsStackable => isStackable;
|
||||||
public int MaxStackSize => maxStackSize;
|
public int MaxStackSize => maxStackSize;
|
||||||
public ItemType ItemType => itemType;
|
public ItemType ItemType => itemType;
|
||||||
|
|
||||||
|
public int MaxUses => maxUses;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When uses run out: true keeps the item (shown red, unusable until repaired/refilled),
|
||||||
|
/// false destroys it. Repairable tools and refillable containers set this true.
|
||||||
|
/// </summary>
|
||||||
|
public bool KeepWhenDepleted => keepWhenDepleted;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether this item tracks per-instance uses (a tool's durability, a food's bites). Items
|
||||||
|
/// with uses get a slot bar and are treated as non-stackable so each instance keeps its own value.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasUses => maxUses > 0;
|
||||||
|
|
||||||
public HarvestType HarvestType => harvestType;
|
public HarvestType HarvestType => harvestType;
|
||||||
public float ToolPower => toolPower;
|
public float ToolPower => toolPower;
|
||||||
public float HealthRestore => healthRestore;
|
public float HealthRestore => healthRestore;
|
||||||
|
|||||||
@@ -131,7 +131,13 @@ namespace Ashwild.Inventory
|
|||||||
/// Server-side: sends a granted item to this inventory's owning client, where it is added.
|
/// Server-side: sends a granted item to this inventory's owning client, where it is added.
|
||||||
/// Called after the server authorises a pickup.
|
/// Called after the server authorises a pickup.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void GrantItemFromServer(ItemData item, int quantity)
|
public void GrantItemFromServer(ItemData item, int quantity) => GrantItemFromServer(item, quantity, -1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server-side grant that also restores a specific remaining-uses value, used when a partially
|
||||||
|
/// used instance is picked back up off the ground. A negative value means "grant at full uses".
|
||||||
|
/// </summary>
|
||||||
|
public void GrantItemFromServer(ItemData item, int quantity, int uses)
|
||||||
{
|
{
|
||||||
if (item == null) return;
|
if (item == null) return;
|
||||||
|
|
||||||
@@ -142,14 +148,15 @@ namespace Ashwild.Inventory
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TargetGrantItem(Owner, id, quantity);
|
TargetGrantItem(Owner, id, quantity, uses);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs on the owning client: resolves the granted item and adds it locally.
|
/// Runs on the owning client: resolves the granted item and adds it locally, restoring its
|
||||||
|
/// remaining uses when one was carried across the drop.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TargetRpc]
|
[TargetRpc]
|
||||||
private void TargetGrantItem(NetworkConnection conn, ushort itemId, int quantity)
|
private void TargetGrantItem(NetworkConnection conn, ushort itemId, int quantity, int uses)
|
||||||
{
|
{
|
||||||
ItemData item = ItemDatabase.Instance != null ? ItemDatabase.Instance.GetItem(itemId) : null;
|
ItemData item = ItemDatabase.Instance != null ? ItemDatabase.Instance.GetItem(itemId) : null;
|
||||||
if (item == null)
|
if (item == null)
|
||||||
@@ -158,7 +165,7 @@ namespace Ashwild.Inventory
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddItem(item, quantity);
|
AddItem(item, quantity, uses);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -173,7 +180,12 @@ namespace Ashwild.Inventory
|
|||||||
|
|
||||||
public InventorySlot GetSelectedSlot() => slots[selectedHotbarIndex];
|
public InventorySlot GetSelectedSlot() => slots[selectedHotbarIndex];
|
||||||
|
|
||||||
public bool AddItem(ItemData item, int quantity = 1)
|
/// <summary>
|
||||||
|
/// Adds an item, stacking into existing slots first. Uses-tracked items never stack: each unit
|
||||||
|
/// takes its own slot with its own uses bar. The optional uses value restores a partially used
|
||||||
|
/// instance (negative = full); it only applies to uses-tracked items.
|
||||||
|
/// </summary>
|
||||||
|
public bool AddItem(ItemData item, int quantity = 1, int uses = -1)
|
||||||
{
|
{
|
||||||
if (item == null)
|
if (item == null)
|
||||||
{
|
{
|
||||||
@@ -183,6 +195,8 @@ namespace Ashwild.Inventory
|
|||||||
|
|
||||||
int remaining = quantity;
|
int remaining = quantity;
|
||||||
|
|
||||||
|
if (!item.HasUses)
|
||||||
|
{
|
||||||
for (int i = 0; i < inventorySize && remaining > 0; i++)
|
for (int i = 0; i < inventorySize && remaining > 0; i++)
|
||||||
{
|
{
|
||||||
if (!slots[i].IsEmpty && slots[i].ItemData == item && slots[i].CanAccept(item))
|
if (!slots[i].IsEmpty && slots[i].ItemData == item && slots[i].CanAccept(item))
|
||||||
@@ -191,13 +205,22 @@ namespace Ashwild.Inventory
|
|||||||
NotifySlotChanged(i);
|
NotifySlotChanged(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for (int i = 0; i < inventorySize && remaining > 0; i++)
|
for (int i = 0; i < inventorySize && remaining > 0; i++)
|
||||||
{
|
{
|
||||||
if (slots[i].IsEmpty)
|
if (slots[i].IsEmpty)
|
||||||
|
{
|
||||||
|
if (item.HasUses)
|
||||||
|
{
|
||||||
|
slots[i].Set(item, 1, uses);
|
||||||
|
remaining -= 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
int toPlace = (item.IsStackable && remaining > item.MaxStackSize) ? item.MaxStackSize : remaining;
|
int toPlace = (item.IsStackable && remaining > item.MaxStackSize) ? item.MaxStackSize : remaining;
|
||||||
slots[i].Set(item, toPlace);
|
slots[i].Set(item, toPlace);
|
||||||
remaining -= toPlace;
|
remaining -= toPlace;
|
||||||
|
}
|
||||||
NotifySlotChanged(i);
|
NotifySlotChanged(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,6 +280,12 @@ namespace Ashwild.Inventory
|
|||||||
NotifySlotChanged(indexB);
|
NotifySlotChanged(indexB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Consumes one use of a consumable: applies its restores once (per bite, for multi-use food)
|
||||||
|
/// then either spends a use or removes one unit. A multi-use item is kept at 0 uses (red,
|
||||||
|
/// unusable) when KeepWhenDepleted is set, otherwise it is destroyed. Ignores an already
|
||||||
|
/// depleted item.
|
||||||
|
/// </summary>
|
||||||
public void UseItem(int index)
|
public void UseItem(int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= inventorySize) return;
|
if (index < 0 || index >= inventorySize) return;
|
||||||
@@ -264,6 +293,7 @@ namespace Ashwild.Inventory
|
|||||||
|
|
||||||
ItemData item = slots[index].ItemData;
|
ItemData item = slots[index].ItemData;
|
||||||
if (item.ItemType != ItemType.Consumable) return;
|
if (item.ItemType != ItemType.Consumable) return;
|
||||||
|
if (slots[index].IsDepleted) return;
|
||||||
|
|
||||||
if (PlayerStats.Instance != null)
|
if (PlayerStats.Instance != null)
|
||||||
{
|
{
|
||||||
@@ -273,7 +303,39 @@ namespace Ashwild.Inventory
|
|||||||
}
|
}
|
||||||
PlayerEvents.RaiseItemConsumed(item);
|
PlayerEvents.RaiseItemConsumed(item);
|
||||||
|
|
||||||
|
if (item.HasUses)
|
||||||
|
{
|
||||||
|
slots[index].ConsumeUse(1);
|
||||||
|
if (slots[index].IsDepleted && !item.KeepWhenDepleted)
|
||||||
RemoveItem(index, 1);
|
RemoveItem(index, 1);
|
||||||
|
else
|
||||||
|
NotifySlotChanged(index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RemoveItem(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Spends durability on the currently selected hotbar item after a successful tool hit. The
|
||||||
|
/// tool is kept at 0 uses (red, unusable until repaired) when KeepWhenDepleted is set, otherwise
|
||||||
|
/// it is destroyed. Returns false when the selected slot holds a depleted repairable tool, so
|
||||||
|
/// the caller can block the swing; true otherwise (including non-uses items).
|
||||||
|
/// </summary>
|
||||||
|
public bool ConsumeSelectedToolUse(int amount = 1)
|
||||||
|
{
|
||||||
|
InventorySlot slot = slots[selectedHotbarIndex];
|
||||||
|
if (slot.IsEmpty || !slot.ItemData.HasUses) return true;
|
||||||
|
if (slot.IsDepleted) return false;
|
||||||
|
|
||||||
|
ItemData item = slot.ItemData;
|
||||||
|
slot.ConsumeUse(amount);
|
||||||
|
if (slot.IsDepleted && !item.KeepWhenDepleted)
|
||||||
|
RemoveItem(selectedHotbarIndex, 1);
|
||||||
|
else
|
||||||
|
NotifySlotChanged(selectedHotbarIndex);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DropItem(int index, int quantity = 1)
|
public void DropItem(int index, int quantity = 1)
|
||||||
@@ -282,13 +344,14 @@ namespace Ashwild.Inventory
|
|||||||
if (slots[index].IsEmpty) return;
|
if (slots[index].IsEmpty) return;
|
||||||
|
|
||||||
ItemData item = slots[index].ItemData;
|
ItemData item = slots[index].ItemData;
|
||||||
|
int uses = item.HasUses ? slots[index].CurrentUses : -1;
|
||||||
Transform origin = dropOrigin != null ? dropOrigin : transform;
|
Transform origin = dropOrigin != null ? dropOrigin : transform;
|
||||||
if (item.WorldPrefab != null && origin != null && PickableRegistry.Instance != null)
|
if (item.WorldPrefab != null && origin != null && PickableRegistry.Instance != null)
|
||||||
{
|
{
|
||||||
Vector3 dropPos = origin.position + origin.forward * dropForwardDistance;
|
Vector3 dropPos = origin.position + origin.forward * dropForwardDistance;
|
||||||
ushort id = ItemDatabase.Instance != null ? ItemDatabase.Instance.GetId(item) : (ushort)0;
|
ushort id = ItemDatabase.Instance != null ? ItemDatabase.Instance.GetId(item) : (ushort)0;
|
||||||
if (id != 0)
|
if (id != 0)
|
||||||
PickableRegistry.Instance.RequestDropServerRpc(id, quantity, dropPos, origin.rotation);
|
PickableRegistry.Instance.RequestDropServerRpc(id, quantity, uses, dropPos, origin.rotation);
|
||||||
else
|
else
|
||||||
Debug.LogWarning($"[PlayerInventory] '{item.ItemName}' is not in the ItemDatabase — drop not spawned. Run Rebuild Item Database.", this);
|
Debug.LogWarning($"[PlayerInventory] '{item.ItemName}' is not in the ItemDatabase — drop not spawned. Run Rebuild Item Database.", this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,16 @@ namespace Ashwild.Inventory
|
|||||||
[SerializeField] private TextMeshProUGUI quantityText;
|
[SerializeField] private TextMeshProUGUI quantityText;
|
||||||
[SerializeField] private Image highlightImage;
|
[SerializeField] private Image highlightImage;
|
||||||
|
|
||||||
|
[Header("Uses Bar")]
|
||||||
|
[Tooltip("Root object of the uses/durability bar — shown only for items that track uses.")]
|
||||||
|
[SerializeField] private GameObject usageBarRoot;
|
||||||
|
[Tooltip("Filled image whose fillAmount maps to the remaining uses (Image Type = Filled).")]
|
||||||
|
[SerializeField] private Image usageBarFill;
|
||||||
|
[Tooltip("Icon tint applied when the item still has uses left.")]
|
||||||
|
[SerializeField] private Color normalTint = Color.white;
|
||||||
|
[Tooltip("Icon (and bar) tint applied when the item is depleted — a broken repairable tool / empty container.")]
|
||||||
|
[SerializeField] private Color depletedTint = Color.red;
|
||||||
|
|
||||||
private int slotIndex;
|
private int slotIndex;
|
||||||
private Action<int, int> onSwapRequested;
|
private Action<int, int> onSwapRequested;
|
||||||
private Action<int, bool> onClicked;
|
private Action<int, bool> onClicked;
|
||||||
@@ -40,12 +50,17 @@ namespace Ashwild.Inventory
|
|||||||
onClicked = clickCallback;
|
onClicked = clickCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Redraws the slot: icon, quantity, and the uses bar. Uses-tracked items (tools, multi-bite
|
||||||
|
/// food) show a fill bar for their remaining uses and turn red once depleted.
|
||||||
|
/// </summary>
|
||||||
public void UpdateVisual(InventorySlot slot)
|
public void UpdateVisual(InventorySlot slot)
|
||||||
{
|
{
|
||||||
if (slot == null || slot.IsEmpty)
|
if (slot == null || slot.IsEmpty)
|
||||||
{
|
{
|
||||||
iconImage.gameObject.SetActive(false);
|
iconImage.gameObject.SetActive(false);
|
||||||
quantityText.gameObject.SetActive(false);
|
quantityText.gameObject.SetActive(false);
|
||||||
|
if (usageBarRoot != null) usageBarRoot.SetActive(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -55,9 +70,30 @@ namespace Ashwild.Inventory
|
|||||||
quantityText.gameObject.SetActive(showQty);
|
quantityText.gameObject.SetActive(showQty);
|
||||||
if (showQty)
|
if (showQty)
|
||||||
quantityText.text = slot.Quantity.ToString();
|
quantityText.text = slot.Quantity.ToString();
|
||||||
|
|
||||||
|
UpdateUsageBar(slot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Shows the uses bar and tints the icon red when the item is depleted; hides the bar entirely
|
||||||
|
/// for items that do not track uses. Preserves the current icon alpha so a mid-drag fade stays.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateUsageBar(InventorySlot slot)
|
||||||
|
{
|
||||||
|
bool hasUses = slot.ItemData.HasUses;
|
||||||
|
|
||||||
|
if (usageBarRoot != null)
|
||||||
|
usageBarRoot.SetActive(hasUses);
|
||||||
|
|
||||||
|
Color tint = (hasUses && slot.IsDepleted) ? depletedTint : normalTint;
|
||||||
|
tint.a = iconImage.color.a;
|
||||||
|
iconImage.color = tint;
|
||||||
|
|
||||||
|
if (hasUses && usageBarFill != null)
|
||||||
|
usageBarFill.fillAmount = (float)slot.CurrentUses / slot.ItemData.MaxUses;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetSelected(bool selected)
|
public void SetSelected(bool selected)
|
||||||
{
|
{
|
||||||
if (highlightImage != null)
|
if (highlightImage != null)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace Ashwild.Network
|
|||||||
{
|
{
|
||||||
public ushort itemId;
|
public ushort itemId;
|
||||||
public int quantity;
|
public int quantity;
|
||||||
|
public int uses;
|
||||||
public Vector3 position;
|
public Vector3 position;
|
||||||
public Quaternion rotation;
|
public Quaternion rotation;
|
||||||
}
|
}
|
||||||
@@ -135,7 +136,7 @@ namespace Ashwild.Network
|
|||||||
ItemData item = ItemDatabase.Instance != null ? ItemDatabase.Instance.GetItem(record.itemId) : null;
|
ItemData item = ItemDatabase.Instance != null ? ItemDatabase.Instance.GetItem(record.itemId) : null;
|
||||||
|
|
||||||
activeDrops.Remove(id);
|
activeDrops.Remove(id);
|
||||||
if (item != null) inventory.GrantItemFromServer(item, record.quantity);
|
if (item != null) inventory.GrantItemFromServer(item, record.quantity, record.uses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +144,7 @@ namespace Ashwild.Network
|
|||||||
/// Owner-side entry: asks the server to spawn a dropped item into the world.
|
/// Owner-side entry: asks the server to spawn a dropped item into the world.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ServerRpc(RequireOwnership = false)]
|
[ServerRpc(RequireOwnership = false)]
|
||||||
public void RequestDropServerRpc(ushort itemId, int quantity, Vector3 position, Quaternion rotation, NetworkConnection conn = null)
|
public void RequestDropServerRpc(ushort itemId, int quantity, int uses, Vector3 position, Quaternion rotation, NetworkConnection conn = null)
|
||||||
{
|
{
|
||||||
if (ItemDatabase.Instance == null || ItemDatabase.Instance.GetItem(itemId) == null) return;
|
if (ItemDatabase.Instance == null || ItemDatabase.Instance.GetItem(itemId) == null) return;
|
||||||
|
|
||||||
@@ -152,6 +153,7 @@ namespace Ashwild.Network
|
|||||||
{
|
{
|
||||||
itemId = itemId,
|
itemId = itemId,
|
||||||
quantity = quantity,
|
quantity = quantity,
|
||||||
|
uses = uses,
|
||||||
position = position,
|
position = position,
|
||||||
rotation = rotation
|
rotation = rotation
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -102,12 +102,14 @@ namespace Ashwild.Player
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Swings on use input (gated by lock, setup and cooldown) and harvests the
|
/// Swings on use input (gated by lock, setup and cooldown) and harvests the
|
||||||
/// aimed target.
|
/// aimed target. A depleted but non-destroyed tool (a worn-out repairable axe) is
|
||||||
|
/// unusable: the swing is blocked until it is repaired.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void HandleAttackPressed()
|
private void HandleAttackPressed()
|
||||||
{
|
{
|
||||||
if (PlayerEvents.InputLocked) return;
|
if (PlayerEvents.InputLocked) return;
|
||||||
if (raycastOrigin == null) return;
|
if (raycastOrigin == null) return;
|
||||||
|
if (PlayerInventory.Instance != null && PlayerInventory.Instance.GetSelectedSlot().IsDepleted) return;
|
||||||
if (Time.time < nextSwingTime) return;
|
if (Time.time < nextSwingTime) return;
|
||||||
|
|
||||||
nextSwingTime = Time.time + swingCooldown;
|
nextSwingTime = Time.time + swingCooldown;
|
||||||
@@ -160,6 +162,10 @@ namespace Ashwild.Player
|
|||||||
HarvestableRegistry.Instance.RequestHitServerRpc(harvestable.Id, damage, toolId, hitDirection);
|
HarvestableRegistry.Instance.RequestHitServerRpc(harvestable.Id, damage, toolId, hitDirection);
|
||||||
|
|
||||||
PlayerEvents.RaiseHarvestableHit(harvestable, damage);
|
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)
|
||||||
|
PlayerInventory.Instance.ConsumeSelectedToolUse(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ MonoBehaviour:
|
|||||||
isStackable: 0
|
isStackable: 0
|
||||||
maxStackSize: 64
|
maxStackSize: 64
|
||||||
itemType: 1
|
itemType: 1
|
||||||
|
maxUses: 60
|
||||||
|
keepWhenDepleted: 1
|
||||||
harvestType: 0
|
harvestType: 0
|
||||||
toolPower: 1
|
toolPower: 1
|
||||||
healthRestore: 0
|
healthRestore: 0
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ MonoBehaviour:
|
|||||||
isStackable: 0
|
isStackable: 0
|
||||||
maxStackSize: 64
|
maxStackSize: 64
|
||||||
itemType: 1
|
itemType: 1
|
||||||
|
maxUses: 100
|
||||||
|
keepWhenDepleted: 1
|
||||||
harvestType: 2
|
harvestType: 2
|
||||||
toolPower: 1
|
toolPower: 1
|
||||||
healthRestore: 0
|
healthRestore: 0
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ MonoBehaviour:
|
|||||||
isStackable: 0
|
isStackable: 0
|
||||||
maxStackSize: 64
|
maxStackSize: 64
|
||||||
itemType: 1
|
itemType: 1
|
||||||
|
maxUses: 80
|
||||||
|
keepWhenDepleted: 1
|
||||||
harvestType: 1
|
harvestType: 1
|
||||||
toolPower: 1
|
toolPower: 1
|
||||||
healthRestore: 0
|
healthRestore: 0
|
||||||
|
|||||||
Reference in New Issue
Block a user