using System.Collections.Generic; using Bogatyri.World; using TMPro; using UnityEngine; using UnityEngine.UI; // Сцена карты мира: рисует замок и города, открывает окно атаки, показывает войска в походе // и итоги боёв. Правил тут нет -- они в WorldSimulation, походы ведёт MarchManager. public class WorldMapController : MonoBehaviour { [Header("Камера")] public MapCameraController mapCamera; [Header("Верхняя панель")] public TMP_Text soldiersText; public Button backButton; public Button myCityButton; [Header("Подсказка")] public TMP_Text hintText; [Header("Рейтинг")] public Button ratingButton; public RatingPanel ratingPanel; [Header("Нижняя панель (пока без функционала)")] public Button worldMapNavButton; public Button eventsNavButton; public Button circleNavButton; public Button settlementNavButton; [Header("Окно атаки")] public GameObject attackPanel; public TMP_Text attackTitleText; public TMP_Text attackInfoText; public Slider soldiersSlider; public TMP_Text soldiersToSendText; public Button sendButton; public Button cancelButton; [Header("Уведомление (итог боя / возвращение)")] public GameObject popupPanel; public TMP_Text popupTitleText; public TMP_Text popupBodyText; public Button popupOkButton; const string DefaultHint = "Нажми на замок, соседний город или лагерь разбойников, чтобы напасть. Карту можно двигать и приближать"; const float CastleSize = 2.2f; const float CitySize = 1.1f; const float CampSize = 0.8f; const float StartZoom = 8f; static readonly Color GroundColor = new Color(0.3f, 0.42f, 0.25f); static readonly Color CastleColor = new Color(0.55f, 0.15f, 0.15f); static readonly Color PlayerCityColor = new Color(0.25f, 0.5f, 0.95f); static readonly Color NeighborCityColor = new Color(0.75f, 0.55f, 0.3f); static readonly Color CampColor = new Color(0.45f, 0.2f, 0.45f); static readonly Color CampClearedColor = new Color(0.35f, 0.35f, 0.35f, 0.6f); private Transform mapRoot; private readonly Dictionary marchViews = new Dictionary(); private readonly Queue<(string title, string body)> popupQueue = new Queue<(string, string)>(); private readonly List staleMarchIds = new List(); private MapNode selectedTarget; // Подписи замка и соседей: гарнизон виден только в зоне обзора (решения 31.10, п. 1) private readonly List<(MapNode node, TextMeshPro label)> garrisonLabels = new List<(MapNode, TextMeshPro)>(); // Лагеря меняют вид: разбит -- серый, с таймером до появления private readonly List<(MapNode node, SpriteRenderer sprite, TextMeshPro label)> campViews = new List<(MapNode, SpriteRenderer, TextMeshPro)>(); // Ресурсные точки появляются и исчезают -- виды создаются и убираются по ходу игры private readonly Dictionary pointViews = new Dictionary(); private readonly List stalePoints = new List(); const float PointSize = 0.7f; static readonly Color[] PointColors = { new Color(0.2f, 0.55f, 0.2f), // дерево new Color(0.6f, 0.6f, 0.62f), // камень new Color(0.35f, 0.45f, 0.6f), // металл new Color(0.95f, 0.78f, 0.2f) // золото }; static readonly string[] ResourceGenitive = { "дерева", "камня", "металла", "золота" }; private float nextCampRefresh; // Окно атаки: постоянная часть текста и состояние войска, с которым оно показано private string attackInfoBase; private BattleCondition shownConditions; private bool shownObserved; // цель была в зоне обзора, когда окно атаки открылось WorldSimulation World => MarchManager.Instance.World; void Start() { mapRoot = new GameObject("Map").transform; BuildMap(); IntelligenceOverlay.Create(mapRoot, World); // туман и чужие армии (модуль Разведки) SetUpCamera(); backButton.onClick.AddListener(SceneNavigator.ReturnToCity); if (myCityButton != null) myCityButton.onClick.AddListener(CenterOnMyCity); if (ratingButton != null) ratingButton.onClick.AddListener(ratingPanel.Open); CreateArmiesButton(); HookUpComingSoon(worldMapNavButton, "Карта мира"); HookUpComingSoon(eventsNavButton, "События"); if (circleNavButton != null) circleNavButton.onClick.AddListener(CircleWindow.Open); // модуль Круга HookUpComingSoon(settlementNavButton, "Поселение"); sendButton.onClick.AddListener(OnSendPressed); cancelButton.onClick.AddListener(CloseAttackPanel); popupOkButton.onClick.AddListener(ShowNextPopup); soldiersSlider.onValueChanged.AddListener(_ => { UpdateSoldiersToSendText(); if (attackPanel.activeSelf) RefreshAttackConditions(); }); attackPanel.SetActive(false); popupPanel.SetActive(false); if (ratingPanel != null) ratingPanel.Close(); hintText.text = DefaultHint; if (ResourceManager.Instance == null) hintText.text = "Запусти игру из сцены города (MVP1), иначе войско неоткуда брать"; } void OnEnable() { if (MarchManager.Instance == null) return; MarchManager.Instance.OnBattleResolved += HandleBattleResolved; MarchManager.Instance.OnGatherStarted += HandleGatherStarted; MarchManager.Instance.OnMarchReturned += HandleMarchReturned; } void OnDisable() { if (MarchManager.Instance == null) return; MarchManager.Instance.OnBattleResolved -= HandleBattleResolved; MarchManager.Instance.OnGatherStarted -= HandleGatherStarted; MarchManager.Instance.OnMarchReturned -= HandleMarchReturned; } void Update() { HandleMapTap(); SyncMarchViews(); RefreshCamps(); RefreshOpenAttackPanel(); // Воины, а не отряды: "В походе: 1" читалось как один воин int inMarches = 0, garrison = 0; if (GameServices.TryGet(out IArmy army)) { inMarches = army.TroopsInMarches; garrison = army.GarrisonTotal; } string slots = army != null ? $"{army.ActiveMarches} из {army.MarchSlots}" : World.Marches.Count.ToString(); soldiersText.text = $"Свободно: {GetSoldiersAtHome()} Гарнизон: {garrison} В походе: {inMarches} (отрядов: {slots})"; } // Кнопка «Армии» -- копия кнопки «Рейтинг» прямо под ней: тот же вид, без правки сцены void CreateArmiesButton() { if (ratingButton == null) return; var copy = Instantiate(ratingButton.gameObject, ratingButton.transform.parent); copy.name = "ArmiesButton"; var rt = (RectTransform)copy.transform; var src = (RectTransform)ratingButton.transform; rt.anchoredPosition = src.anchoredPosition - new Vector2(0, src.rect.height + 12); var label = copy.GetComponentInChildren(); if (label != null) label.text = "Армии"; var button = copy.GetComponent