using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// GAME 1303 Tier 8 — The cauldron. Compares what the witch gathered against the order
/// and grades the result. Commission Witch.
///
/// SETUP
/// 1. Cauldron object + Collider2D (trigger) + Tier 6 Interactable + this script.
/// Wire the Interactable's OnInteract -> RecipeChecker.Brew.
/// 2. Current Recipe: the active order. Set it from an order-board Interactable
/// (OnInteract -> RecipeChecker.SetRecipe with the Recipe asset dragged in).
/// 3. Ingredients come from Tier 1's CollectionTracker — the witch picks up Collectibles
/// out in the world and they count automatically.
/// 4. Consume On Brew spends them (so a failed brew costs you the ingredients — harsh but
/// it makes gathering matter). Turn it off while playtesting.
/// 5. Results feed the ending: each brew adds to counters "brews_perfect", "brews_passable"
/// and "brews_ruined", which Tier 6's EndingSelector reads with Min Counter Value.
/// That's how "different endings based on how many and which ingredients you collect"
/// actually resolves.
///
/// GRADES
/// Perfect – every required ingredient, no forbidden ones, no excess beyond the allowance
/// Passable – required ingredients present, but too many extras
/// Ruined – a forbidden ingredient went in
/// Failed – something required is missing
/// </summary>
public class RecipeChecker : MonoBehaviour
{
public enum Result { Perfect, Passable, Ruined, Failed }
[Header("Order")]
public Recipe currentRecipe;
public bool consumeOnBrew = true;
[Header("Feedback")]
public string brewSfx = "brew";
public string successSfx = "success";
public string failSfx = "fail";
public GameObject successEffect;
public GameObject failEffect;
[Header("Events")]
public UnityEvent<Recipe> OnRecipeSet;
public UnityEvent<Result> OnBrewResult;
public UnityEvent<string> OnFeedback; // a human-readable line for the UI
public UnityEvent OnPerfect;
public UnityEvent OnRuined;
public UnityEvent<int, int> OnIngredientProgress; // (have, needed) totals
public Result LastResult { get; private set; }
/// <summary>Wire an order-board Interactable here, with the Recipe asset in the slot.</summary>
public void SetRecipe(Recipe recipe)
{
currentRecipe = recipe;
OnRecipeSet?.Invoke(recipe);
ReportProgress();
}
void Update()
{
// Cheap enough at this scale, and it keeps a "3 / 5 gathered" readout live.
if (currentRecipe != null) ReportProgress();
}
void ReportProgress()
{
if (CollectionTracker.Instance == null || currentRecipe == null) return;
int have = 0, need = 0;
foreach (var ing in currentRecipe.required)
{
if (ing.item == null) continue;
need += ing.amount;
have += Mathf.Min(ing.amount, CollectionTracker.Instance.GetCount(ing.item));
}
OnIngredientProgress?.Invoke(have, need);
}
/// <summary>Wire the cauldron's Interactable OnInteract here.</summary>
public void Brew()
{
if (currentRecipe == null) { OnFeedback?.Invoke("No order to fill."); return; }
if (CollectionTracker.Instance == null) { Debug.LogWarning("RecipeChecker: no CollectionTracker in the scene."); return; }
if (!string.IsNullOrEmpty(brewSfx)) AudioManager.Instance?.PlaySFX(brewSfx);
var tracker = CollectionTracker.Instance;
// 1. Forbidden ingredients ruin it outright
foreach (var bad in currentRecipe.forbidden)
{
if (bad == null) continue;
if (tracker.GetCount(bad) > 0)
{
Finish(Result.Ruined, $"The {bad.displayName} turned it black. Ruined.");
return;
}
}
// 2. Everything required must be present
var missing = new List<string>();
foreach (var ing in currentRecipe.required)
{
if (ing.item == null) continue;
if (tracker.GetCount(ing.item) < ing.amount)
missing.Add($"{ing.amount - tracker.GetCount(ing.item)}x {ing.item.displayName}");
}
if (missing.Count > 0)
{
Finish(Result.Failed, "Still missing: " + string.Join(", ", missing));
return;
}
// 3. Count anything carried that the recipe doesn't call for
int extras = 0;
foreach (var kv in tracker.GetAllCounts())
{
int needed = 0;
foreach (var ing in currentRecipe.required)
if (ing.item == kv.Key) needed = ing.amount;
extras += Mathf.Max(0, kv.Value - needed);
}
if (consumeOnBrew)
foreach (var ing in currentRecipe.required)
if (ing.item != null) tracker.Spend(ing.item, ing.amount);
if (extras > currentRecipe.extrasAllowed)
Finish(Result.Passable, $"It works, but it's cloudy — {extras} stray ingredients.");
else
Finish(Result.Perfect, $"{currentRecipe.potionName}, exactly as ordered.");
}
void Finish(Result result, string message)
{
LastResult = result;
var flags = ChoiceFlagManager.Instance;
switch (result)
{
case Result.Perfect:
flags?.AddCount("brews_perfect", 1);
if (!string.IsNullOrEmpty(currentRecipe.setFlagOnSuccess)) flags?.SetFlag(currentRecipe.setFlagOnSuccess);
if (successEffect) Instantiate(successEffect, transform.position, Quaternion.identity);
if (!string.IsNullOrEmpty(successSfx)) AudioManager.Instance?.PlaySFX(successSfx);
OnPerfect?.Invoke();
break;
case Result.Passable:
flags?.AddCount("brews_passable", 1);
if (!string.IsNullOrEmpty(currentRecipe.setFlagOnSuccess)) flags?.SetFlag(currentRecipe.setFlagOnSuccess);
if (!string.IsNullOrEmpty(successSfx)) AudioManager.Instance?.PlaySFX(successSfx);
break;
case Result.Ruined:
flags?.AddCount("brews_ruined", 1);
if (!string.IsNullOrEmpty(currentRecipe.setFlagOnFailure)) flags?.SetFlag(currentRecipe.setFlagOnFailure);
if (failEffect) Instantiate(failEffect, transform.position, Quaternion.identity);
if (!string.IsNullOrEmpty(failSfx)) AudioManager.Instance?.PlaySFX(failSfx);
OnRuined?.Invoke();
break;
case Result.Failed:
if (!string.IsNullOrEmpty(failSfx)) AudioManager.Instance?.PlaySFX(failSfx);
break;
}
OnFeedback?.Invoke(message);
OnBrewResult?.Invoke(result);
}
}