Files
MedievalShopkeeper/Assets/Scripts/GameManager.cs

341 lines
11 KiB
C#

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<int, int> OnTimeChanged; // Hour, Minute
public event Action<int> OnMoneyChanged; // New Balance
public event Action<bool> OnShopStateChanged; // IsOpen
public event Action<int> OnCustomerCountChanged; // Current Customers
public event Action<GameEvent> 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;
if (autoManageShopHours && CurrentHour >= shopOpenHour && CurrentHour < shopCloseHour)
{
IsShopOpen = true;
}
else
{
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);
CustomerNPC npc = newNPC.GetComponent<CustomerNPC>();
if (npc != null)
{
npc.Initialize(NPCIntention.Shopper, new System.Collections.Generic.List<ItemData>());
}
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<CustomerNPC>(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($"<color=#FF5555><b>[GameManager] RANDOM EVENT TRIGGERED: {triggeredEvent}</b></color>");
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
}
}