using UnityEngine; using UnityEngine.UI; using TMPro; using DG.Tweening; using System; using System.Collections.Generic; using Ashwild.Inventory; namespace Ashwild.Crafting { /// /// Pure crafting view: shows the recipe list, the detail panel and the craft button. /// It knows nothing about the player or the crafting logic — CraftingManager drives it and /// passes in the player-dependent queries (can-craft, item-count) and the craft action as /// delegates. The UI never reaches back into the player or the manager. /// public class CraftingUI : MonoBehaviour { #region Serialized Fields [Header("Recipe List (Left)")] [SerializeField] private Transform recipeListContainer; [SerializeField] private GameObject recipeButtonPrefab; [Header("Detail Panel (Right)")] [SerializeField] private GameObject detailPanel; [SerializeField] private Image resultIcon; [SerializeField] private TextMeshProUGUI resultNameText; [SerializeField] private TextMeshProUGUI resultQuantityText; [SerializeField] private Transform ingredientContainer; [SerializeField] private GameObject ingredientSlotPrefab; [SerializeField] private Button craftButton; [Header("Craft Feedback")] [SerializeField] private float punchScale = 0.15f; [SerializeField] private float punchDuration = 0.3f; #endregion #region State private Func canCraftQuery; private Func countItemQuery; private Func craftRequest; private readonly List recipeButtons = new List(); private readonly List ingredientInstances = new List(); private RecipeButtonUI selectedButton; private Tweener punchTween; private bool built; #endregion #region Public API /// /// Builds the recipe buttons (once) and wires the manager-owned queries/action. Safe to /// call again later to re-point the delegates; it then simply refreshes the display. /// public void Build(CraftingRecipe[] recipes, Func canCraft, Func countItem, Func craft) { canCraftQuery = canCraft; countItemQuery = countItem; craftRequest = craft; if (!built) { for (int i = 0; i < recipes.Length; i++) { GameObject btnGO = Instantiate(recipeButtonPrefab, recipeListContainer); RecipeButtonUI btn = btnGO.GetComponent(); btn.Initialize(recipes[i], OnRecipeSelected, OnCountChanged); recipeButtons.Add(btn); } craftButton.onClick.AddListener(OnCraftClicked); detailPanel.SetActive(false); built = true; } Refresh(); } /// /// Recomputes craftability for every recipe button and refreshes the open detail. /// Called by the manager whenever the local player's inventory changes. /// public void Refresh() { if (!built) return; for (int i = 0; i < recipeButtons.Count; i++) { RecipeButtonUI btn = recipeButtons[i]; bool canCraft = canCraftQuery != null && canCraftQuery(btn.Recipe, btn.CraftCount); btn.UpdateCraftability(canCraft); } if (selectedButton != null) RefreshDetail(); } #endregion #region Unity Lifecycle /// /// Kills the feedback tween so it never targets a destroyed button. /// private void OnDestroy() { punchTween?.Kill(); } #endregion #region Event Handlers /// /// Selects a recipe and opens its detail panel. /// private void OnRecipeSelected(RecipeButtonUI button) { if (selectedButton == button) return; if (selectedButton != null) selectedButton.SetSelected(false); selectedButton = button; selectedButton.SetSelected(true); detailPanel.SetActive(true); RefreshDetail(); } /// /// Refreshes the detail panel when the selected recipe's craft count changes. /// private void OnCountChanged(RecipeButtonUI button) { if (button == selectedButton) RefreshDetail(); } /// /// Asks the manager to craft the selected recipe and plays feedback on success. /// private void OnCraftClicked() { if (selectedButton == null || craftRequest == null) return; if (craftRequest(selectedButton.Recipe, selectedButton.CraftCount)) { punchTween?.Kill(); punchTween = craftButton.transform.DOPunchScale(Vector3.one * punchScale, punchDuration, 5, 0.5f) .SetUpdate(true); selectedButton.ResetCount(); } } #endregion #region Internal Helpers /// /// Renders the detail panel (result + ingredient slots) for the selected recipe, /// using the manager-provided item-count query to show what the player owns. /// private void RefreshDetail() { CraftingRecipe recipe = selectedButton.Recipe; int count = selectedButton.CraftCount; // Result info resultIcon.sprite = recipe.Icon; resultNameText.text = recipe.RecipeName; int totalResult = recipe.ResultQuantity * count; bool showQty = totalResult > 1; resultQuantityText.gameObject.SetActive(showQty); if (showQty) resultQuantityText.text = "x" + totalResult; // Clear old ingredients foreach (GameObject go in ingredientInstances) Destroy(go); ingredientInstances.Clear(); // Create ingredient slots bool canCraft = true; CraftingIngredient[] ingredients = recipe.Ingredients; for (int i = 0; i < ingredients.Length; i++) { GameObject slotGO = Instantiate(ingredientSlotPrefab, ingredientContainer); IngredientSlotUI slotUI = slotGO.GetComponent(); int required = ingredients[i].quantity * count; int playerHas = countItemQuery != null ? countItemQuery(ingredients[i].item) : 0; slotUI.Setup(ingredients[i].item, required, playerHas); if (playerHas < required) canCraft = false; ingredientInstances.Add(slotGO); } craftButton.interactable = canCraft; } #endregion } }