using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
/// <summary>
/// GAME 1303 Tier 7 — The money. Cash on hand, a day counter, ordering stock, and the
/// running totals. Store Simulator.
///
/// SETUP
/// 1. One empty GameObject "StoreEconomy" with this script.
/// 2. Starting Cash. Ordering a box costs the ProductType's Wholesale Price × Units Per
/// Box; a sale earns the Retail Price. That gap is the whole game.
/// 3. Day cycle: Day Length Seconds, then EndDay() runs automatically (or call it from a
/// "close up" button). Rent is deducted each night — that's the pressure that stops
/// the game being a sandbox.
/// 4. Wire OnCashChanged to a HUD text, OnDayEnded to a summary panel showing the day's
/// sales, and OnWentBroke to a game-over.
/// 5. Delivery: OrderBox spawns the product's box prefab at the Delivery Point after a
/// short delay, so the player has to go fetch it.
///
/// A note for Sanad: the reference games all work because money is TIGHT at the start.
/// Set Starting Cash low enough that the first delivery is a real decision.
/// </summary>
public class StoreEconomy : MonoBehaviour
{
public static StoreEconomy Instance { get; private set; }
[Header("Money")]
public float startingCash = 200f;
public float dailyRent = 50f;
[Header("Day cycle")]
public bool useDayCycle = true;
public float dayLengthSeconds = 300f;
[Header("Delivery")]
public Transform deliveryPoint;
public float deliveryDelay = 5f;
[Header("Events")]
public UnityEvent<float> OnCashChanged;
public UnityEvent<int> OnDayChanged;
public UnityEvent<float> OnDayTimeChanged; // 0-1 through the day
public UnityEvent<float, float> OnDayEnded; // (revenue, expenses) for the day
public UnityEvent OnWentBroke;
public UnityEvent<ProductType> OnDelivered;
public float Cash { get; private set; }
public int Day { get; private set; } = 1;
public float DayRevenue { get; private set; }
public float DayExpenses { get; private set; }
public float TotalRevenue { get; private set; }
private float dayTimer;
void Awake()
{
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
Instance = this;
Cash = startingCash;
}
void Start()
{
dayTimer = dayLengthSeconds;
OnCashChanged?.Invoke(Cash);
OnDayChanged?.Invoke(Day);
}
void Update()
{
if (!useDayCycle || dayLengthSeconds <= 0f) return;
dayTimer -= Time.deltaTime;
OnDayTimeChanged?.Invoke(1f - Mathf.Clamp01(dayTimer / dayLengthSeconds));
if (dayTimer <= 0f) EndDay();
}
// ---- Money ----
public void Earn(float amount)
{
Cash += amount;
DayRevenue += amount;
TotalRevenue += amount;
OnCashChanged?.Invoke(Cash);
}
public bool Spend(float amount)
{
if (amount > Cash) return false;
Cash -= amount;
DayExpenses += amount;
OnCashChanged?.Invoke(Cash);
return true;
}
// ---- Ordering stock ----
public float BoxCost(ProductType product) =>
product == null ? 0f : product.wholesalePrice * product.unitsPerBox;
/// <summary>Buy a box. Wire a shop UI button here.</summary>
public bool OrderBox(ProductType product)
{
if (product == null || product.worldPrefab == null) return false;
if (!Spend(BoxCost(product))) return false;
StartCoroutine(DeliverAfterDelay(product));
return true;
}
System.Collections.IEnumerator DeliverAfterDelay(ProductType product)
{
yield return new WaitForSeconds(deliveryDelay);
Vector3 where = deliveryPoint != null ? deliveryPoint.position : transform.position;
var box = Instantiate(product.worldPrefab, where + Vector3.up * 0.5f, Quaternion.identity);
// Make sure the delivered object is actually carryable and holds the stock
var carryable = box.GetComponent<Carryable>();
if (carryable == null) carryable = box.AddComponent<Carryable>();
carryable.product = product;
carryable.units = product.unitsPerBox;
OnDelivered?.Invoke(product);
}
// ---- Days ----
public void EndDay()
{
Cash -= dailyRent;
DayExpenses += dailyRent;
OnCashChanged?.Invoke(Cash);
OnDayEnded?.Invoke(DayRevenue, DayExpenses);
if (Cash < 0f) { OnWentBroke?.Invoke(); return; }
Day++;
DayRevenue = 0f;
DayExpenses = 0f;
dayTimer = dayLengthSeconds;
OnDayChanged?.Invoke(Day);
}
}