168 lines
5.2 KiB
C#
168 lines
5.2 KiB
C#
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
using Ashwild.Inventory;
|
|
using Ashwild.Player;
|
|
|
|
namespace Ashwild.Crafting
|
|
{
|
|
/// <summary>
|
|
/// Owns the crafting recipes and logic, and drives the (dumb) CraftingUI: it builds the view
|
|
/// from recipe data, hands it the player-dependent queries, and refreshes it whenever the
|
|
/// local player's inventory changes. The UI never reaches back into the player or this class.
|
|
/// Keep this and CraftingUI as stable scene objects so the reference can't break.
|
|
/// </summary>
|
|
public class CraftingManager : MonoBehaviour
|
|
{
|
|
#region Serialized Fields
|
|
|
|
[Header("Data")]
|
|
[SerializeField] private CraftingRecipe[] recipes;
|
|
|
|
[Header("View")]
|
|
[Tooltip("The crafting panel this manager drives. Both should be stable scene objects.")]
|
|
[SerializeField] private CraftingUI craftingUI;
|
|
|
|
[Header("Events")]
|
|
public UnityEvent<CraftingRecipe> onCraftSuccess;
|
|
|
|
#endregion
|
|
|
|
#region State
|
|
|
|
private PlayerInventory inventory;
|
|
private bool bound;
|
|
|
|
#endregion
|
|
|
|
#region Public API
|
|
|
|
/// <summary>
|
|
/// All authored recipes (read-only data source).
|
|
/// </summary>
|
|
public CraftingRecipe[] Recipes => recipes;
|
|
|
|
/// <summary>
|
|
/// Returns whether the local player can currently craft the recipe the given number of times.
|
|
/// </summary>
|
|
public bool CanCraft(CraftingRecipe recipe, int count = 1)
|
|
{
|
|
return PlayerInventory.Instance != null && recipe.CanCraft(PlayerInventory.Instance, count);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Consumes the ingredients and produces the result if the local player can afford it.
|
|
/// </summary>
|
|
public bool TryCraft(CraftingRecipe recipe, int count = 1)
|
|
{
|
|
PlayerInventory inv = PlayerInventory.Instance;
|
|
if (inv == null || !recipe.CanCraft(inv, count))
|
|
return false;
|
|
|
|
// Consume ingredients
|
|
for (int i = 0; i < recipe.Ingredients.Length; i++)
|
|
{
|
|
CraftingIngredient ingredient = recipe.Ingredients[i];
|
|
inv.RemoveItemByData(ingredient.item, ingredient.quantity * count);
|
|
}
|
|
|
|
// Produce result
|
|
inv.AddItem(recipe.ResultItem, recipe.ResultQuantity * count);
|
|
|
|
onCraftSuccess?.Invoke(recipe);
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Unity Lifecycle
|
|
|
|
/// <summary>
|
|
/// Subscribes to the local player spawn so we can bind to its inventory.
|
|
/// </summary>
|
|
private void OnEnable()
|
|
{
|
|
PlayerEvents.LocalPlayerSpawned += HandleLocalPlayerSpawned;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribes — mirrors OnEnable exactly.
|
|
/// </summary>
|
|
private void OnDisable()
|
|
{
|
|
PlayerEvents.LocalPlayerSpawned -= HandleLocalPlayerSpawned;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds the view from static recipe data, then binds to the player if it already exists.
|
|
/// </summary>
|
|
private void Start()
|
|
{
|
|
if (craftingUI != null)
|
|
craftingUI.Build(recipes, CanCraft, CountItem, TryCraft);
|
|
else
|
|
Debug.LogError("[CraftingManager] No CraftingUI assigned — the crafting panel will not populate.", this);
|
|
|
|
// The player may already exist (late init); otherwise we wait for the spawn event.
|
|
if (PlayerInventory.Instance != null)
|
|
BindToInventory();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops listening to the inventory when destroyed.
|
|
/// </summary>
|
|
private void OnDestroy()
|
|
{
|
|
if (inventory != null)
|
|
inventory.onSlotChanged.RemoveListener(HandleInventoryChanged);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event Handlers
|
|
|
|
/// <summary>
|
|
/// Binds to the inventory when the local player spawns on the network.
|
|
/// </summary>
|
|
private void HandleLocalPlayerSpawned() => BindToInventory();
|
|
|
|
/// <summary>
|
|
/// Refreshes the view's craftability whenever any inventory slot changes.
|
|
/// </summary>
|
|
private void HandleInventoryChanged(int slotIndex)
|
|
{
|
|
if (craftingUI != null)
|
|
craftingUI.Refresh();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Internal Helpers
|
|
|
|
/// <summary>
|
|
/// Caches the local player's inventory and starts reacting to its changes; runs once.
|
|
/// </summary>
|
|
private void BindToInventory()
|
|
{
|
|
if (bound) return;
|
|
inventory = PlayerInventory.Instance;
|
|
if (inventory == null) return;
|
|
bound = true;
|
|
|
|
inventory.onSlotChanged.AddListener(HandleInventoryChanged);
|
|
|
|
if (craftingUI != null)
|
|
craftingUI.Refresh();
|
|
}
|
|
|
|
/// <summary>
|
|
/// How many of an item the local player currently holds (0 when offline).
|
|
/// </summary>
|
|
private int CountItem(ItemData item)
|
|
{
|
|
return PlayerInventory.Instance != null ? PlayerInventory.Instance.CountItem(item) : 0;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|