using System; using UnityEngine; using Random = UnityEngine.Random; namespace MedievalShopkeeper { public enum GameEvent { None, GoblinRobber, ShopRaid, Plague, MarketBoom, DeliveryArrived } public class GameManager : MonoBehaviour { public static GameManager Instance { get; private set; } [Header("Time Settings")] [Tooltip("How many in-game minutes pass per real-life second? (Default: 1 real second = 1 game minute)")] [SerializeField] private float inGameMinutesPerRealSecond = 1f; [SerializeField] private int startHour = 7; [SerializeField] private int startMinute = 0; [Header("Shop Hours")] [SerializeField] private bool autoManageShopHours = true; [SerializeField] private int shopOpenHour = 8; [SerializeField] private int shopCloseHour = 18; [Header("Economy")] [SerializeField] private int startingMoney = 100; [Header("Random Events")] [Tooltip("Percentage chance to trigger a random event every in-game hour.")] [Range(0f, 100f)] [SerializeField] private float hourlyEventChance = 5f; // State properties public int CurrentHour { get; private set; } public int CurrentMinute { get; private set; } public int CurrentMoney { get; private set; } public int CurrentCustomersInShop { get; private set; } public bool IsShopOpen { get; private set; } // Events public event Action OnTimeChanged; // Hour, Minute public event Action OnMoneyChanged; // New Balance public event Action OnShopStateChanged; // IsOpen public event Action OnCustomerCountChanged; // Current Customers public event Action OnRandomEventTriggered; // The Event private float timeAccumulator = 0f; private int lastEvaluatedHour = -1; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; // Initialize state CurrentHour = startHour; CurrentMinute = startMinute; CurrentMoney = startingMoney; CurrentCustomersInShop = 0; IsShopOpen = false; } private void Start() { // Initial broadcasts OnTimeChanged?.Invoke(CurrentHour, CurrentMinute); OnMoneyChanged?.Invoke(CurrentMoney); OnShopStateChanged?.Invoke(IsShopOpen); OnCustomerCountChanged?.Invoke(CurrentCustomersInShop); } private void Update() { UpdateTime(); } #region Time Management public bool IsNightTime => CurrentHour >= shopCloseHour || CurrentHour < shopOpenHour; private void UpdateTime() { // Night time is 10x faster so the player doesn't have to wait a full hour real-time with no customers float timeMultiplier = (IsNightTime && !IsShopOpen) ? 10f : 1f; // Calculate time progression timeAccumulator += Time.deltaTime * (inGameMinutesPerRealSecond * timeMultiplier); if (timeAccumulator >= 1f) { int minutesPassed = Mathf.FloorToInt(timeAccumulator); timeAccumulator -= minutesPassed; AdvanceTime(minutesPassed); } } private void AdvanceTime(int minutes) { CurrentMinute += minutes; while (CurrentMinute >= 60) { CurrentMinute -= 60; CurrentHour++; if (CurrentHour >= 24) { CurrentHour = 0; OnDayPassed(); } OnHourPassed(); } OnTimeChanged?.Invoke(CurrentHour, CurrentMinute); } private void OnHourPassed() { // Handle automatic opening and closing if (autoManageShopHours) { if (!IsShopOpen && CurrentHour == shopOpenHour) { OpenShop(); } else if (IsShopOpen && CurrentHour == shopCloseHour) { CloseShop(); } } // Roll for a random event each hour if (CurrentHour != lastEvaluatedHour) { lastEvaluatedHour = CurrentHour; RollForRandomEvent(); } } private void OnDayPassed() { Debug.Log("[GameManager] A new day has started!"); // Future logic: Reset daily stats, pay taxes, etc. } #endregion #region Shop Management public void OpenShop() { if (IsShopOpen) return; IsShopOpen = true; if (IsNightTime) { Debug.LogWarning("[GameManager] Shop opened outside of regular hours! High risk of robberies."); } else { Debug.Log("[GameManager] The shop is now OPEN."); } OnShopStateChanged?.Invoke(IsShopOpen); } [Header("Testing & Debug")] [Tooltip("Prefab of the CustomerNPC to spawn for testing.")] [SerializeField] private GameObject testNPCPrefab; [Tooltip("Where to spawn the test NPC. If left empty, will try to spawn at the ShopDoor.")] [SerializeField] private Transform testNPCSpawnPoint; public void SpawnTestNPC() { if (testNPCPrefab == null) { Debug.LogWarning("[GameManager] Test NPC Prefab is not assigned!"); return; } Vector3 spawnPos = Vector3.zero; if (testNPCSpawnPoint != null) { spawnPos = testNPCSpawnPoint.position; } else if (ShopDoor.Instance != null) { spawnPos = ShopDoor.Instance.transform.position; } else { Debug.LogWarning("[GameManager] No spawn point or ShopDoor found. Spawning at 0,0,0."); } GameObject newNPC = Instantiate(testNPCPrefab, spawnPos, Quaternion.identity); Debug.Log($"[GameManager] Spawned test NPC: {newNPC.name}"); } public void CloseShop() { if (!IsShopOpen) return; IsShopOpen = false; Debug.Log("[GameManager] The shop is now CLOSED."); // Force all NPCs to leave and put items back CustomerNPC[] allNPCs = FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None); foreach (var npc in allNPCs) { npc.KickOut(); } OnShopStateChanged?.Invoke(IsShopOpen); } public void CustomerEntered() { CurrentCustomersInShop++; OnCustomerCountChanged?.Invoke(CurrentCustomersInShop); } public void CustomerLeft() { CurrentCustomersInShop = Mathf.Max(0, CurrentCustomersInShop - 1); OnCustomerCountChanged?.Invoke(CurrentCustomersInShop); } #endregion #region Economy Management public void AddMoney(int amount) { if (amount <= 0) return; CurrentMoney += amount; Debug.Log($"[GameManager] Earned {amount} coin(s). Balance: {CurrentMoney}"); OnMoneyChanged?.Invoke(CurrentMoney); } public bool TrySpendMoney(int amount) { if (amount <= 0) return true; if (CurrentMoney >= amount) { CurrentMoney -= amount; Debug.Log($"[GameManager] Spent {amount} coin(s). Balance: {CurrentMoney}"); OnMoneyChanged?.Invoke(CurrentMoney); return true; } Debug.LogWarning($"[GameManager] Not enough money! Needed: {amount}, Balance: {CurrentMoney}"); return false; } #endregion #region Random Events private void RollForRandomEvent() { float currentEventChance = hourlyEventChance; // If shop is open at night, triple the chance of random events (robberies) if (IsShopOpen && IsNightTime) { currentEventChance *= 3f; } if (Random.Range(0f, 100f) <= currentEventChance) { TriggerRandomEvent(); } } [ContextMenu("Force Trigger Random Event")] public void TriggerRandomEvent() { // Pick a random event that isn't 'None' int minEvent = 1; // skip None int maxEvent = Enum.GetValues(typeof(GameEvent)).Length; GameEvent triggeredEvent = (GameEvent)Random.Range(minEvent, maxEvent); Debug.Log($"[GameManager] RANDOM EVENT TRIGGERED: {triggeredEvent}"); OnRandomEventTriggered?.Invoke(triggeredEvent); // Basic immediate effects switch (triggeredEvent) { case GameEvent.Plague: Debug.Log(">> Plague strikes! Customers might avoid the shop today."); break; case GameEvent.GoblinRobber: Debug.Log(">> A Goblin Robber is trying to steal stock!"); break; case GameEvent.ShopRaid: Debug.Log(">> A Shop Raid! Lock the doors!"); break; case GameEvent.MarketBoom: Debug.Log(">> Market Boom! Selling prices are temporarily higher."); break; case GameEvent.DeliveryArrived: Debug.Log(">> An unexpected extra delivery arrived."); break; } } #endregion } }