Гарнизон города

Модуль Армии (Bogatyri.Army, без Unity):
- гарнизон — часть воинов в городе: защищает город и в походы не уходит, всего воинов столько же
- IArmy: GarrisonTotal, GetGarrison(type), AvailableForMarch, SetGarrison(count)
- гарнизон набирается по составу армии, не больше воинов в городе; после потерь обрезается по типам
- в поход уходят только свободные воины (TakeForMarch)
- сохранение: строка garrison= в секции army (старые сохранения — без гарнизона); сброс очищает
- TroopsChangedEvent.Garrison

Интерфейс:
- окно «Войска» в городе: карточка «Гарнизон города» (0, -10, -1, +1, +10, «Все»)
- карта мира: «Свободно / Гарнизон / В походе», ползунок похода и прогноз — только по свободным воинам
- понятная ошибка, если воины есть, но все в гарнизоне

Тест «один солдат в одном месте»: 30 воинов, 10 в гарнизон → 20 свободны, всего 30 (решение команды от 19.10.2026).
Документы Army, Core, World, README обновлены.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-10-27 16:54:49 +01:00
co-authored by Claude Opus 5.5
parent b1a396e9a2
commit 4865b14ff2
11 changed files with 154 additions and 19 deletions
+24
View File
@@ -28,6 +28,21 @@ public class TroopsPanel : BasePanel
ProfilePanel.KV(army, "Сила армии с героем", GP.ArmyPower.ToString("N0"));
UIKit.Button(army, "Отправить армию на карте мира", UIKit.Theme.Green, () => OpenPanel("World"), -1, 80, 26);
// --- Гарнизон: часть воинов охраняет город и в походы не уходит ---
IArmy troopsArmy = GameServices.Get<IArmy>();
var garrison = UIKit.Card(list, UIKit.Theme.CardDark);
ProfilePanel.Header(garrison, "Гарнизон города");
ProfilePanel.KV(garrison, "В гарнизоне", troopsArmy.GarrisonTotal.ToString("N0") + " из " + troopsArmy.SoldiersAtHome.ToString("N0"));
ProfilePanel.KV(garrison, "Свободны для походов", troopsArmy.AvailableForMarch.ToString("N0"));
UIKit.Text(garrison, "Гарнизон защищает город и не уходит в походы. Воины в нём не пропадают: всего воинов столько же.", 22, UIKit.Theme.TextDim);
var gRow = UIKit.Row(garrison, 80, 10);
GarrisonButton(gRow, troopsArmy, "0", 0);
GarrisonButton(gRow, troopsArmy, "-10", troopsArmy.GarrisonTotal - 10);
GarrisonButton(gRow, troopsArmy, "-1", troopsArmy.GarrisonTotal - 1);
GarrisonButton(gRow, troopsArmy, "+1", troopsArmy.GarrisonTotal + 1);
GarrisonButton(gRow, troopsArmy, "+10", troopsArmy.GarrisonTotal + 10);
GarrisonButton(gRow, troopsArmy, "Все", troopsArmy.SoldiersAtHome);
// --- Нет казарм -- нанимать негде ---
if (Barracks.Count == 0)
{
@@ -97,6 +112,15 @@ public class TroopsPanel : BasePanel
}
}
// Окно перестраивается само: Армия публикует TroopsChangedEvent, GameProgress сообщает об изменении
static void GarrisonButton(Transform row, IArmy army, string label, int target)
{
target = Mathf.Clamp(target, 0, army.SoldiersAtHome);
bool can = target != army.GarrisonTotal;
var b = UIKit.Button(row, label, can ? UIKit.Theme.Blue : UIKit.Theme.Gray, () => { if (can) army.SetGarrison(target); }, -1, 80, 28);
UIKit.Size(b, -1, 80, 1);
}
protected override void Tick()
{
var d = GP.Data;