Модуль Армии (Bogatyri.Army, без Unity): - ArmyService — единственное хранилище войск (4 типа), секция сохранения "army" - IArmy расширен: войска по типам, обучение (AddTroops), потери (LoseFraction), войска в походах - событие TroopsChangedEvent — по нему перерисовываются панели HUD - GameProgress и панели работают с войсками только через IArmy - перенос из progress.json (troops, sashaMarchUnits) при первой загрузке; старые данные удаляются только после записи save.json (флаг armyInModule) - ArmyBridge удалён: контракты и перенос сохранений проверены тестами - тесты: поход не создаёт и не теряет солдат, потери по типам, сохранение, перенос, сброс Окно «ВОЙСКО ВЕРНУЛОСЬ» в городе (UI/MarchReturnPopup): - куда ходили, победа/поражение, сколько вернулось из скольких, добыча - показывается и за походы, закончившиеся пока игра была закрыта; несколько — по очереди - в MarchReturnedEvent добавлены Sent, TargetName, LootText GameLoadedEvent публикуется и без файла сохранения. Документы Army, Core, Save, World, README обновлены. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
113 lines
5.4 KiB
C#
113 lines
5.4 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using TMPro;
|
||
|
||
// ВОЙСКА: армия, очередь обучения (идёт и когда игра закрыта), найм воинов за ресурсы,
|
||
// ускорение за кристаллы. Новые типы войск открываются уровнем Ратуши.
|
||
public class TroopsPanel : BasePanel
|
||
{
|
||
readonly List<Image> queueBars = new List<Image>();
|
||
readonly List<TextMeshProUGUI> queueTimes = new List<TextMeshProUGUI>();
|
||
Button speedUp;
|
||
|
||
static readonly int[] Amounts = { 1, 10, 50 };
|
||
|
||
protected override void Build(RectTransform root)
|
||
{
|
||
var d = GP.Data;
|
||
queueBars.Clear(); queueTimes.Clear(); speedUp = null;
|
||
var list = ScrollList(root);
|
||
|
||
// --- Армия ---
|
||
var army = UIKit.Card(list, UIKit.Theme.CardDark);
|
||
ProfilePanel.Header(army, "Армия");
|
||
ProfilePanel.KV(army, "Всего воинов в городе", GP.TotalTroops.ToString("N0"));
|
||
ProfilePanel.KV(army, "В походе на карте мира", GameServices.Get<IArmy>().TroopsInMarches.ToString("N0"));
|
||
ProfilePanel.KV(army, "Сила войск", GP.TroopsPower.ToString("N0"));
|
||
ProfilePanel.KV(army, "Сила армии с героем", GP.ArmyPower.ToString("N0"));
|
||
UIKit.Button(army, "Отправить армию на карте мира", UIKit.Theme.Green, () => OpenPanel("World"), -1, 80, 26);
|
||
|
||
// --- Нет казарм -- нанимать негде ---
|
||
if (Barracks.Count == 0)
|
||
{
|
||
var warn = UIKit.Card(list, new Color(0.35f, 0.18f, 0.12f));
|
||
UIKit.Text(warn, "Нанимать войска можно только в Казармах. Постройте их на свободном участке.", 28, UIKit.Theme.Text);
|
||
UIKit.Button(warn, "ПОСТРОИТЬ КАЗАРМЫ", UIKit.Theme.Green, () =>
|
||
{
|
||
if (BuildModeController.Instance != null) BuildModeController.Instance.SetMode(true);
|
||
else UIKit.Toast("Нажмите кнопку СТРОЙКА");
|
||
}, -1, 96, 32);
|
||
}
|
||
|
||
// --- Очередь обучения ---
|
||
var queue = UIKit.Card(list);
|
||
ProfilePanel.Header(queue, "Обучение (" + d.training.Count + " / " + Barracks.QueueSize + ") • казарм: " + Barracks.Count);
|
||
if (d.training.Count == 0)
|
||
{
|
||
UIKit.Text(queue, "Никто не обучается. Выберите войска ниже.", 26, UIKit.Theme.TextDim);
|
||
}
|
||
else
|
||
{
|
||
foreach (var o in d.training)
|
||
{
|
||
var def = GameData.Troops[o.troopIndex];
|
||
var r = UIKit.Row(queue, 44, 10);
|
||
var n = UIKit.Text(r, def.name + " × " + o.count, 28);
|
||
UIKit.Size(n, -1, -1, 1);
|
||
var t = UIKit.Text(r, "", 26, UIKit.Theme.Gold, TextAlignmentOptions.Right);
|
||
UIKit.Size(t, 180);
|
||
queueTimes.Add(t);
|
||
queueBars.Add(UIKit.Bar(queue, 0, def.color, 20));
|
||
}
|
||
speedUp = UIKit.Button(queue, "", UIKit.Theme.Blue, () => GP.SpeedUpTraining(), -1, 80);
|
||
}
|
||
Tick();
|
||
|
||
// --- Типы войск ---
|
||
for (int i = 0; i < GameData.Troops.Count; i++)
|
||
{
|
||
int idx = i;
|
||
var def = GameData.Troops[i];
|
||
bool unlocked = GP.TroopUnlocked(i);
|
||
|
||
var card = UIKit.Card(list, unlocked ? UIKit.Theme.Card : UIKit.Theme.CardDark);
|
||
var top = UIKit.Row(card, 120, 16);
|
||
UIKit.Icon(top, unlocked ? def.color : UIKit.Theme.Gray, def.name.Substring(0, 1), 110);
|
||
var info = UIKit.Card(top, new Color(0, 0, 0, 0), 0, 2);
|
||
UIKit.Size(info, -1, -1, 1);
|
||
UIKit.Text(info, def.name + " <color=#c8c8c8><size=24>в армии: " + GP.TroopCount(i).ToString("N0") + "</size></color>", 32).fontStyle = FontStyles.Bold;
|
||
UIKit.Text(info, def.description, 22, UIKit.Theme.TextDim);
|
||
UIKit.Text(info, "Сила " + def.power + " • " + def.secondsPerUnit + " сек/воин • " + GameData.RewardsText(def.cost), 22, UIKit.Theme.TextDim);
|
||
|
||
if (!unlocked)
|
||
{
|
||
UIKit.Text(card, "Откроется на Ратуше " + def.requiredTownHall + " уровня", 26, UIKit.Theme.Gold, TextAlignmentOptions.Center);
|
||
continue;
|
||
}
|
||
|
||
var btns = UIKit.Row(card, 80, 10);
|
||
foreach (int amount in Amounts)
|
||
{
|
||
int a = amount;
|
||
bool can = Barracks.Count > 0 && GP.CanAfford(def.cost, a) && d.training.Count < Barracks.QueueSize;
|
||
var b = UIKit.Button(btns, "+" + a, can ? UIKit.Theme.Green : UIKit.Theme.Gray, () => GP.Train(idx, a), -1, 80, 30);
|
||
UIKit.Size(b, -1, 80, 1);
|
||
}
|
||
}
|
||
}
|
||
|
||
protected override void Tick()
|
||
{
|
||
var d = GP.Data;
|
||
for (int i = 0; i < queueBars.Count && i < d.training.Count; i++)
|
||
{
|
||
var o = d.training[i];
|
||
UIKit.SetBar(queueBars[i], GP.TrainingProgress(o));
|
||
queueTimes[i].text = GameData.FormatTime(GP.TrainingSecondsLeft(o));
|
||
}
|
||
if (speedUp != null && d.training.Count > 0)
|
||
UIKit.SetButton(speedUp, "Ускорить всё за " + GP.SpeedUpCost + " кристаллов", true);
|
||
}
|
||
}
|