using System; using System.Collections.Generic; using Bogatyri.World; using UnityEngine; // Хранит мир и походы игрока. Живёт между сценами (создаётся сам при запуске игры), // поэтому войско идёт и возвращается, даже пока игрок в городе. // // Точка расширения под мультиплеер: сейчас WorldSimulation крутится прямо здесь. // Позже StartMarch будет отправлять запрос на сервер, а походы и их итоги -- приходить с сервера. // WorldMapController при этом менять не придётся: он работает через события этого класса. public class MarchManager : MonoBehaviour { public const string LocalPlayerId = "local_player"; public static MarchManager Instance { get; private set; } public WorldSimulation World { get; private set; } public event Action OnMarchStarted; public event Action OnBattleResolved; public event Action OnMarchReturned; // Время мира. В мультиплеере -- время сервера, а не телефона public static long NowMs => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); private readonly List events = new List(); [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void CreateInstance() { var go = new GameObject("MarchManager"); DontDestroyOnLoad(go); go.AddComponent(); } void Awake() { Instance = this; World = new WorldSimulation(WorldMapLayout.CreateDefault()); // [объединение] походы хранятся в нашем progress.json (GameProgress), а не в SaveManager var saved = GameProgress.Instance.Data.sashaMarches; if (saved != null && saved.Count > 0) World.LoadMarches(saved); } void SaveToProgress() { GameProgress.Instance.Data.sashaMarches = GetMarchesForSave(); GameProgress.Instance.MarkChanged(); } void Update() { // Без ResourceManager некуда зачислять вернувшихся солдат -- ждём, пока загрузится город if (ResourceManager.Instance == null) return; events.Clear(); World.Advance(NowMs, events); foreach (var e in events) { if (e.Type == WorldEventType.BattleResolved) HandleBattleResolved(e.March); else if (e.Type == WorldEventType.MarchReturned) HandleMarchReturned(e.March); } if (events.Count > 0) SaveToProgress(); } public bool TryStartMarch(string targetId, int soldiers, out string error) { if (ResourceManager.Instance == null) { error = "Нет данных о войске -- запусти игру из города"; return false; } // [объединение] солдаты = наши воины в городе (все типы войск) if (ArmyBridge.SoldiersAtHome < soldiers) { error = "Не хватает воинов. Наймите их в Казармах"; return false; } MarchData march = World.StartMarch(LocalPlayerId, WorldMapLayout.PlayerCityId, targetId, soldiers, NowMs, out error); if (march == null) return false; // Ушедшие воины недоступны дома, пока не вернутся ArmyBridge.Take(march.id, soldiers); GameProgress.Instance.Data.marchesSent++; GameProgress.Instance.Data.dailyMarches++; SaveToProgress(); Debug.Log($"[ПОХОД] {soldiers} солдат выступили на {World.GetNode(targetId).displayName}"); OnMarchStarted?.Invoke(march); return true; } void HandleBattleResolved(MarchData march) { MapNode target = World.GetNode(march.targetNodeId); Debug.Log($"[ПОХОД] Бой у {target.displayName}: {(march.victory ? "ПОБЕДА" : "поражение")}. " + $"{march.soldiers} против {target.garrison}, возвращаются {march.survivors}."); OnBattleResolved?.Invoke(march); } // Солдаты и добыча зачисляются, только когда войско дошло до дома void HandleMarchReturned(MarchData march) { ResourceManager rm = ResourceManager.Instance; ArmyBridge.Return(march.id, march.survivors); if (march.victory) GameProgress.Instance.Data.worldWins++; foreach (var reward in march.loot) { if (Enum.TryParse(reward.resource, out ResourceType type)) rm.AddResource(type, reward.amount); else Debug.LogWarning($"[ПОХОД] Неизвестный ресурс награды: {reward.resource}"); } Debug.Log($"[ПОХОД] Войско вернулось: {march.survivors} солдат, добыча: {FormatLoot(march.loot)}"); OnMarchReturned?.Invoke(march); } // ---------------- СОХРАНЕНИЕ ---------------- public List GetMarchesForSave() { return new List(World.Marches); } public void LoadMarches(List saved) { World.LoadMarches(saved); } // ---------------- ТЕКСТ ДЛЯ UI ---------------- public static string FormatLoot(List loot) { if (loot == null || loot.Count == 0) return "нет"; var parts = new List(); foreach (var r in loot) { parts.Add($"+{r.amount} {GetResourceName(r.resource)}"); } return string.Join(", ", parts); } static string GetResourceName(string resource) { switch (resource) { case "Food": return "еды"; case "Wood": return "дерева"; case "Stone": return "камня"; case "Iron": return "металла"; case "Gold": return "золота"; case "Gems": return "кристаллов"; default: return resource; } } }