using System; using System.Collections.Generic; using UnityEngine; // МОДУЛЬ ЭКОНОМИКИ. Центральное хранилище всех ресурсов игрока. // Другие модули работают с ним только через контракт IEconomy (GameServices.Get()), // а изменения узнают из события 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 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 resources = new Dictionary(); // Лимиты склада (этап 1). Нет записи -- лимита нет. Задаёт слой приложения (App/CitySync) по Складам и Амбарам, // запоминаются в сохранении: на карте мира город не открыт, а лимиты действуют. private Dictionary capacities = new Dictionary(); // Лимит изменился (для интерфейса: "запас / лимит") public event Action OnCapacityChanged; void Awake() { // Классический паттерн Singleton: если уже есть экземпляр -- уничтожаем дубликат if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); // менеджер ресурсов должен жить между сценами (меню -> игра) SetStartValues(notify: false); GameServices.Register(this); SaveRegistry.Register(this); } void OnDestroy() { if (Instance != this) return; GameServices.Unregister(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 GetAllResources() { return new Dictionary(resources); } void SetStartValues(bool notify) { var start = new Dictionary { { 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 cost) { foreach (var c in cost) { if (!HasEnough(c.Type, c.Amount)) return false; } return true; } public bool TrySpend(IReadOnlyList 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 resources = new List(); public List capacities = new List(); // лимиты склада (этап 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(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); } }