Files
MVP1/Assets/Scripts/Modules/Economy/ResourceManager.cs
T

221 lines
8.9 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using UnityEngine;
// МОДУЛЬ ЭКОНОМИКИ. Центральное хранилище всех ресурсов игрока.
// Другие модули работают с ним только через контракт IEconomy (GameServices.Get<IEconomy>()),
// а изменения узнают из события ResourceChangedEvent. Документ: Docs/Modules/Economy.md
//
// Старый доступ ResourceManager.Instance оставлен для интерфейса (панели, HUD) -- модулям им пользоваться нельзя.
public class ResourceManager : MonoBehaviour, IEconomy, ISaveParticipant
{
public static ResourceManager Instance { get; private set; }
// Событие для интерфейса (HUD). Модули слушают ResourceChangedEvent через EventBus.
public event Action<ResourceType, float> OnResourceChanged;
[Header("Стартовые значения (для теста/баланса)")]
public float startWood = 0f;
public float startStone = 0f;
public float startIron = 0f;
public float startFood = 0f;
public float startGold = 0f;
public float startGems = 0f;
public float startPower = 0f;
public float startPopulation = 0f;
private Dictionary<ResourceType, float> resources = new Dictionary<ResourceType, float>();
// Лимиты склада (этап 1). Нет записи -- лимита нет. Задаёт слой приложения (App/CitySync) по Складам и Амбарам,
// запоминаются в сохранении: на карте мира город не открыт, а лимиты действуют.
private Dictionary<ResourceType, float> capacities = new Dictionary<ResourceType, float>();
// Лимит изменился (для интерфейса: "запас / лимит")
public event Action<ResourceType, float> OnCapacityChanged;
void Awake()
{
// Классический паттерн Singleton: если уже есть экземпляр -- уничтожаем дубликат
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject); // менеджер ресурсов должен жить между сценами (меню -> игра)
SetStartValues(notify: false);
GameServices.Register<IEconomy>(this);
SaveRegistry.Register(this);
}
void OnDestroy()
{
if (Instance != this) return;
GameServices.Unregister<IEconomy>(this);
SaveRegistry.Unregister(this);
}
// Добавить ресурс (можно и отрицательное число -- для трат).
// Прибавка не поднимает запас выше лимита склада -- лишнее пропадает. Если запас уже выше лимита
// (старое сохранение, лимит уменьшился), он не урезается, просто больше не растёт.
public void AddResource(ResourceType type, float amount)
{
if (!resources.ContainsKey(type))
resources[type] = 0f;
float current = resources[type];
float next = current + amount;
if (amount > 0f && capacities.TryGetValue(type, out float cap))
next = Mathf.Min(next, Mathf.Max(current, cap));
if (next < 0f) next = 0f; // ресурс не уходит в минус
if (next == current) return; // ничего не изменилось (например, склад полон) -- не будим интерфейс
resources[type] = next;
NotifyChanged(type);
}
// Проверить, хватает ли ресурса (для трат/построек в будущем)
public bool HasEnough(ResourceType type, float amount)
{
return GetResource(type) >= amount;
}
// Попробовать потратить -- вернёт false, если не хватило (и ничего не спишет)
public bool TrySpend(ResourceType type, float amount)
{
if (!HasEnough(type, amount)) return false;
AddResource(type, -amount);
return true;
}
public float GetResource(ResourceType type)
{
return resources.TryGetValue(type, out float value) ? value : 0f;
}
// Лимит склада: float.PositiveInfinity -- без лимита
public float GetCapacity(ResourceType type)
{
return capacities.TryGetValue(type, out float cap) ? cap : float.PositiveInfinity;
}
public void SetCapacity(ResourceType type, float capacity)
{
if (capacities.TryGetValue(type, out float old) && Mathf.Approximately(old, capacity)) return;
capacities[type] = Mathf.Max(0f, capacity);
OnCapacityChanged?.Invoke(type, capacities[type]);
}
// Устанавливает значение напрямую, БЕЗ проверок и без учёта прошлого значения.
// Используется только при загрузке сохранения -- обычная игровая логика должна
// использовать AddResource/TrySpend, а не этот метод.
public void SetResource(ResourceType type, float value)
{
resources[type] = value;
NotifyChanged(type);
}
// Все ресурсы разом -- удобно для сохранения
public Dictionary<ResourceType, float> GetAllResources()
{
return new Dictionary<ResourceType, float>(resources);
}
void SetStartValues(bool notify)
{
var start = new Dictionary<ResourceType, float>
{
{ ResourceType.Wood, startWood },
{ ResourceType.Stone, startStone },
{ ResourceType.Iron, startIron },
{ ResourceType.Food, startFood },
{ ResourceType.Gold, startGold },
{ ResourceType.Gems, startGems },
{ ResourceType.Power, startPower },
{ ResourceType.Population, startPopulation },
};
resources.Clear();
foreach (var kvp in start)
{
resources[kvp.Key] = kvp.Value;
if (notify) NotifyChanged(kvp.Key);
}
}
void NotifyChanged(ResourceType type)
{
float value = resources[type];
OnResourceChanged?.Invoke(type, value);
EventBus.Publish(new ResourceChangedEvent { Type = type, Value = value });
}
// ---------------- Контракт IEconomy ----------------
float IEconomy.Get(ResourceType type) => GetResource(type);
float IEconomy.GetCapacity(ResourceType type) => GetCapacity(type);
bool IEconomy.Has(ResourceType type, float amount) => HasEnough(type, amount);
void IEconomy.Add(ResourceType type, float amount) => AddResource(type, amount);
public bool CanAfford(IReadOnlyList<ResourceAmount> cost)
{
foreach (var c in cost)
{
if (!HasEnough(c.Type, c.Amount)) return false;
}
return true;
}
public bool TrySpend(IReadOnlyList<ResourceAmount> cost)
{
if (!CanAfford(cost)) return false;
foreach (var c in cost) AddResource(c.Type, -c.Amount);
return true;
}
// ---------------- Сохранение (секция "economy") ----------------
[Serializable]
class ResourceSaveEntry
{
public ResourceType type;
public float amount;
}
// Имена полей совпадают со старым save.json -- старые сохранения читаются без переделки
[Serializable]
class EconomySaveData
{
public List<ResourceSaveEntry> resources = new List<ResourceSaveEntry>();
public List<ResourceSaveEntry> capacities = new List<ResourceSaveEntry>(); // лимиты склада (этап 1)
}
public string SaveKey => "economy";
public string Capture()
{
var data = new EconomySaveData();
foreach (var kvp in resources)
data.resources.Add(new ResourceSaveEntry { type = kvp.Key, amount = kvp.Value });
foreach (var kvp in capacities)
data.capacities.Add(new ResourceSaveEntry { type = kvp.Key, amount = kvp.Value });
return JsonUtility.ToJson(data);
}
public void Restore(string json)
{
var data = JsonUtility.FromJson<EconomySaveData>(json);
if (data?.resources == null) return;
if (data.capacities != null)
foreach (var entry in data.capacities) SetCapacity(entry.type, entry.amount);
foreach (var entry in data.resources) SetResource(entry.type, entry.amount);
}
// Новая игра -- стартовые значения из Inspector
public void ResetState()
{
SetStartValues(notify: true);
}
}