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:
@@ -11,9 +11,23 @@ namespace Ashwild.Crafting
|
||||
/// 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.
|
||||
///
|
||||
/// It also owns the crafting *context* — bare hands or a station. Opening the panel from a
|
||||
/// <see cref="CraftingStation"/> widens the visible catalog to that station's recipes for as long as
|
||||
/// the window stays open; closing it drops back to hand crafting.
|
||||
/// </summary>
|
||||
public class CraftingManager : MonoBehaviour
|
||||
{
|
||||
#region Singleton
|
||||
|
||||
/// <summary>
|
||||
/// The scene's crafting manager, so a world station can hand itself over without a
|
||||
/// FindObjectOfType. Mirrors BuildManager.
|
||||
/// </summary>
|
||||
public static CraftingManager Instance { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Serialized Fields
|
||||
|
||||
[Header("Data")]
|
||||
@@ -34,6 +48,12 @@ namespace Ashwild.Crafting
|
||||
private bool bound;
|
||||
private readonly HashSet<CraftingRecipe> unlockedRecipes = new HashSet<CraftingRecipe>();
|
||||
|
||||
/// <summary>
|
||||
/// The station the panel is currently open on, or None while the player crafts bare-handed
|
||||
/// (the inventory opened with the normal key). Reset whenever the window closes.
|
||||
/// </summary>
|
||||
private CraftingStationType activeStation = CraftingStationType.None;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public API
|
||||
@@ -43,37 +63,64 @@ namespace Ashwild.Crafting
|
||||
/// </summary>
|
||||
public CraftingRecipe[] Recipes => recipes;
|
||||
|
||||
/// <summary>
|
||||
/// Opens the crafting panel on the given station: its recipes join the catalog and the inventory
|
||||
/// window opens straight on the craft tab. The context lasts until the window closes.
|
||||
/// Called by <see cref="CraftingStation.Interact"/> on the interacting client only.
|
||||
/// </summary>
|
||||
public void OpenStation(CraftingStation station)
|
||||
{
|
||||
if (station == null) return;
|
||||
|
||||
if (InventoryUI.Instance == null)
|
||||
{
|
||||
Debug.LogError("[CraftingManager] No InventoryUI in the scene — cannot open the crafting panel.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
activeStation = station.StationType;
|
||||
InventoryUI.Instance.OpenAtCategory(InventoryCategory.Crafting);
|
||||
|
||||
if (craftingUI != null)
|
||||
craftingUI.Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the local player can currently craft the recipe the given number of times.
|
||||
/// A recipe is only craftable once every ingredient has been discovered, on top of the usual
|
||||
/// inventory check — an ingredient still shown as a mystery ("?") blocks the craft.
|
||||
/// A recipe is only craftable once every ingredient has been discovered and the right station is
|
||||
/// open, on top of the usual inventory check — an ingredient still shown as a mystery ("?")
|
||||
/// blocks the craft.
|
||||
/// </summary>
|
||||
public bool CanCraft(CraftingRecipe recipe, int count = 1)
|
||||
{
|
||||
return PlayerInventory.Instance != null
|
||||
&& IsStationAvailable(recipe)
|
||||
&& IsFullyDiscovered(recipe)
|
||||
&& recipe.CanCraft(PlayerInventory.Instance, count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Consumes the ingredients and produces the result if the local player can afford it and has
|
||||
/// discovered every ingredient. The result item is added through the inventory, which in turn
|
||||
/// discovers it — so crafting an intermediate result reveals the recipes further down the chain.
|
||||
/// Consumes the ingredients and produces the result if the local player can afford it, has
|
||||
/// discovered every ingredient, and is at the station the recipe requires. The result item is
|
||||
/// added through the inventory, which in turn discovers it — so crafting an intermediate result
|
||||
/// reveals the recipes further down the chain.
|
||||
///
|
||||
/// The station is re-checked here rather than trusted from the visible list: the panel could
|
||||
/// have been left open through a context change, and a bench recipe must never be craftable
|
||||
/// bare-handed.
|
||||
/// </summary>
|
||||
public bool TryCraft(CraftingRecipe recipe, int count = 1)
|
||||
{
|
||||
PlayerInventory inv = PlayerInventory.Instance;
|
||||
if (inv == null || !IsFullyDiscovered(recipe) || !recipe.CanCraft(inv, count))
|
||||
if (inv == null || !IsStationAvailable(recipe) || !IsFullyDiscovered(recipe) || !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);
|
||||
@@ -85,12 +132,22 @@ namespace Ashwild.Crafting
|
||||
#region Unity Lifecycle
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the local player spawn so we can bind to its inventory.
|
||||
/// Registers the singleton so world stations can reach the manager.
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Subscribes to the local player spawn so we can bind to its inventory, and to the window's
|
||||
/// open state so the station context is dropped when it closes.
|
||||
/// </summary>
|
||||
private void OnEnable()
|
||||
{
|
||||
PlayerEvents.LocalPlayerSpawned += HandleLocalPlayerSpawned;
|
||||
PlayerEvents.ItemDiscovered += HandleItemDiscovered;
|
||||
PlayerEvents.InventoryOpenChanged += HandleInventoryOpenChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -100,6 +157,7 @@ namespace Ashwild.Crafting
|
||||
{
|
||||
PlayerEvents.LocalPlayerSpawned -= HandleLocalPlayerSpawned;
|
||||
PlayerEvents.ItemDiscovered -= HandleItemDiscovered;
|
||||
PlayerEvents.InventoryOpenChanged -= HandleInventoryOpenChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -118,12 +176,14 @@ namespace Ashwild.Crafting
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops listening to the inventory when destroyed.
|
||||
/// Stops listening to the inventory and clears the singleton when destroyed.
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (inventory != null)
|
||||
inventory.onSlotChanged.RemoveListener(HandleInventoryChanged);
|
||||
|
||||
if (Instance == this) Instance = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -157,6 +217,21 @@ namespace Ashwild.Crafting
|
||||
craftingUI.Refresh();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drops the station context when the inventory window closes, so the next hand-opened panel is
|
||||
/// back to hand-only recipes. Tying this to the window rather than to a distance check keeps a
|
||||
/// single, unambiguous rule: the station lasts exactly as long as the panel it opened.
|
||||
/// </summary>
|
||||
private void HandleInventoryOpenChanged(bool open)
|
||||
{
|
||||
if (open || activeStation == CraftingStationType.None) return;
|
||||
|
||||
activeStation = CraftingStationType.None;
|
||||
|
||||
if (craftingUI != null)
|
||||
craftingUI.Refresh();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Helpers
|
||||
@@ -187,13 +262,17 @@ namespace Ashwild.Crafting
|
||||
private void SeedUnlockedRecipes()
|
||||
{
|
||||
for (int i = 0; i < recipes.Length; i++)
|
||||
if (IsRecipeVisible(recipes[i]))
|
||||
if (IsDiscoveryRevealed(recipes[i]))
|
||||
unlockedRecipes.Add(recipes[i]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises a "NEW" notification for every recipe that has just become visible (its first
|
||||
/// Raises a "NEW" notification for every recipe that has just been revealed (its first
|
||||
/// discovered ingredient), each announced once. Called after an item is discovered.
|
||||
///
|
||||
/// Deliberately keyed on discovery alone, not on the station filter: a bench recipe must still
|
||||
/// announce itself the moment its ingredient is found — that is the cue telling the player the
|
||||
/// bench is worth building — instead of silently waiting for the bench to be opened.
|
||||
/// </summary>
|
||||
private void NotifyNewlyUnlockedRecipes()
|
||||
{
|
||||
@@ -201,7 +280,7 @@ namespace Ashwild.Crafting
|
||||
{
|
||||
CraftingRecipe recipe = recipes[i];
|
||||
if (unlockedRecipes.Contains(recipe)) continue;
|
||||
if (!IsRecipeVisible(recipe)) continue;
|
||||
if (!IsDiscoveryRevealed(recipe)) continue;
|
||||
|
||||
unlockedRecipes.Add(recipe);
|
||||
PlayerEvents.RaiseRecipeDiscovered(recipe.Icon, recipe.RecipeName);
|
||||
@@ -225,10 +304,21 @@ namespace Ashwild.Crafting
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the recipe should appear at all: revealed as soon as at least one of its ingredients
|
||||
/// has been discovered. Undiscovered ingredients then show as a mystery until found.
|
||||
/// Whether the recipe should appear in the list right now: revealed by discovery AND allowed by
|
||||
/// the station the panel is open on. The two criteria stay separate helpers on purpose —
|
||||
/// discovery is permanent player progress, the station is a transient context — and only their
|
||||
/// conjunction drives what the player sees.
|
||||
/// </summary>
|
||||
private bool IsRecipeVisible(CraftingRecipe recipe)
|
||||
{
|
||||
return IsStationAvailable(recipe) && IsDiscoveryRevealed(recipe);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the recipe has been revealed by discovery: true as soon as at least one of its
|
||||
/// ingredients has been found. Undiscovered ingredients then show as a mystery until found.
|
||||
/// </summary>
|
||||
private bool IsDiscoveryRevealed(CraftingRecipe recipe)
|
||||
{
|
||||
CraftingIngredient[] ingredients = recipe.Ingredients;
|
||||
for (int i = 0; i < ingredients.Length; i++)
|
||||
@@ -236,6 +326,17 @@ namespace Ashwild.Crafting
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the recipe's required station matches the current context: a recipe requiring none is
|
||||
/// craftable everywhere (bare hands and every station alike), any other one only on a station of
|
||||
/// its exact type.
|
||||
/// </summary>
|
||||
private bool IsStationAvailable(CraftingRecipe recipe)
|
||||
{
|
||||
return recipe.RequiredStation == CraftingStationType.None
|
||||
|| recipe.RequiredStation == activeStation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether every ingredient of the recipe has been discovered — the gate for actually crafting it.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user