Бой: отдельный статус среди выбывших (доля не утверждена -- 0, выключено), версия правил 8. Армия: пропавшие из похода -- в группу; окно 6 игровых часов после обратного пути, возвращение 2-4 группами, 70% здоровыми / 30% ранеными, остальные списываются; сохранение без повторного возвращения. Население: занимают места, но не едят. Окна: сколько пропало и сколько ещё может вернуться. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
26 KiB
Армия
Войска и их характеристики: сколько воинов каждого типа в городе, сколько из них в гарнизоне, сколько в походах, обучение, потери, уход в поход и возвращение. Единственное место, где хранятся войска.
- Сборка:
Bogatyri.Army(без Unity). - Папка:
Assets/Scripts/Modules/Army. - Зависит от: ядра.
Контракт IArmy
| Член | Что делает |
|---|---|
TypeCount |
Типов войск — 4 (пехота, лучники, кавалерия, осадные; порядок как в GameData.Troops). |
GetTroops(type) |
Воинов этого типа в городе. |
SoldiersAtHome |
Всего воинов в городе (вместе с гарнизоном). |
TroopsInMarches |
Воинов в походах. |
GarrisonTotal, GetGarrison(type) |
Сколько воинов в гарнизоне — всего и по типам. |
MarchSlots, ActiveMarches |
Слоты похода (регламент 31.10): сколько походов может идти одновременно (база 2 из MarchConfig, «Военная логистика» — до 7, позже) и сколько идёт сейчас. TakeForMarch отказывает, если все слоты заняты. Походы из старых сохранений сверх лимита дойдут, новых не будет. |
AvailableForMarch |
Свободные воины: в городе минус гарнизон. Только их можно отправить в поход. |
SetGarrison(count) |
Назначить гарнизон: набирается по составу армии в городе (30 пехоты и 10 лучников, гарнизон 8 → 6 пехоты и 2 лучника). Не больше, чем воинов в городе; возвращает, сколько назначено. |
AddTroops(type, count) |
Добавить воинов (обучение в Казармах). |
LoseFraction(fraction) |
Потери в бою: от каждого типа погибает доля (с округлением вниз); возвращает, сколько погибло. |
TakeForMarch(marchId, count) |
Забрать свободных воинов (без гарнизона) в поход пропорционально составу и запомнить состав. false — ничего не взято: не хватает воинов или поход с таким номером уже есть. |
ReturnFromMarch(marchId, survivors) |
Вернуть выживших по тем же типам (пропорционально). false — поход неизвестен, никто не добавлен. |
GetMarchUnits(marchId) |
Состав похода по типам — для боя с типами войск. |
ReturnUnits(marchId, survivorUnits) |
Вернуть выживших точно по типам, как посчитал бой. Больше, чем ушло, не вернётся. false — поход неизвестен. |
WoundedTotal, GetWoundedWaiting(type), Healing, HospitalBeds |
Раненые всего; ждут места по типам; группы на лечении (HealingInfo); мест в госпитале сейчас. |
ReturnUnits(marchId, survivorUnits, woundedUnits, nowMs) |
Выжившие — в город, раненые — в госпиталь в момент nowMs. Только известный поход, один раз. |
ProcessHealing(nowMs) |
Закончить лечение по сроку (и офлайн), положить ждущих на освободившиеся места; SoldiersHealedEvent — один раз на группу. |
Training |
Очередь обучения: заказы (TrainingOrderInfo: номер, тип, сколько, начало и конец в мс часов игры). |
CanQueueTraining(type, count, queueSize, out error) |
Можно ли поставить заказ: очередь не заполнена, жителей хватает (Население). Ничего не меняет. |
TryQueueTraining(type, count, durationMs, queueSize, nowMs, out error) |
Поставить заказ в конец очереди; люди уходят из жителей в Казармы (IPopulation.TryRecruit). false — ничего не изменилось. |
ProcessTraining(nowMs) |
Закончить заказы, срок которых наступил (и офлайн): воины переходят в Армию через IPopulation.CompleteTraining, каждый заказ — один раз. |
FinishAllTraining(nowMs) |
Ускорение: все заказы готовы сейчас. |
Получить: GameServices.Get<IArmy>(). Регистрирует App/GameModules. GameProgress (обучение, кампания, арена, сила армии) и панели работают с войсками только через этот контракт.
Раненые и госпиталь
Решения по 8 вопросам Саши от 31.10, п. 7, регламент 31.10. Раненых хранит и лечит Армия.
| Кто | Что делает |
|---|---|
| Бой | Делит выбывших на погибших и раненых (Battle.md). |
| Мир | Раненые идут домой вместе с войском (MarchData.woundedUnits). |
| Армия | Принимает раненых при возвращении, кладёт в госпиталь, лечит, возвращает в город. Сохраняет. |
| Население | Считает раненых людьми: Soldiers = в городе + в походах + раненые. Погибшие уходят из людей, вылеченные — не новые люди. |
| Город | Здание «Госпиталь» (Meta/Build/Hospital.cs): уровни I–V, места 20 / 30 / 40 / 50 / 60 (HospitalConfig.bedsByLevel, ТЕСТ), считается самый прокачанный госпиталь. Места — Армии через IHospital.Beds. Без госпиталя — 5 временных мест (temporaryBeds, решения 01.11, п. 3); после постройки — штатные, не вместе с временными. |
| Экономика | Цена лечения — IEconomy.TrySpend целиком: золото за воина 1 / 1 / 2 / 3 (пехота, лучники, кавалерия, осадные; решения 01.11, п. 3). Нет золота — раненые ждут. |
- Таблица
Assets/Resources/HospitalConfig.asset(ТЕСТ):bedsByLevel20…60,temporaryBeds5 (без госпиталя), постройка — Ратуша 1, 250 дерева, 150 камня,healGameHours4 / 4 / 6 / 8 игровых часа (пехота, лучники, кавалерия, осадные; 1 игровой час = 5 реальных минут), цена за воинаhealGoldPerSoldier1 / 1 / 2 / 3,healFoodPerSoldier0. Цена улучшения госпиталя II–V не утверждена — пока общая цена улучшения зданий. - Раненые ложатся на места по типам по порядку; каждая группа лечится своё время и возвращается в город целиком.
- Мест не хватает — лишние ждут (ТЕСТ, решение Саши 01.11; регламент не задаёт). Место освободилось — ждущие ложатся в тот же момент, в том числе пока игра закрыта.
- Не хватает ресурсов на лечение — группа ждёт, ничего не списано.
- Повторное возвращение похода или повторный расчёт никого не создают и не лечат дважды.
- Окно «Войска»: раненые (лечатся / ждут места), мест в госпитале, когда вылечатся ближайшие. Окно возвращения и итог боя на карте показывают, сколько ранено.
- Окно госпиталя (тап по зданию): мест, кто лечится и сколько осталось, кто ждёт места, улучшение до V уровня (цена — общая цена улучшения из
EconomyConfig). - Госпиталь улучшили — ждущие ложатся сразу (в течение секунды).
- Дальше: герои — отдельная очередь лечения (регламент), когда появятся герои.
Пропавшие без вести
Решения по отчёту Саши от 01.11, п. 5.
- Бой отделяет часть выбывших в статус «пропал без вести» (
BattleOutcome.AttackerMissingUnits): солдат ровно в одном статусе — погиб, ранен или пропал. Доля —BattleConfig.missingSharePermille, не утверждена: 0, механика выключена. Для проверки — например 150 (15%). - При бое Мир передаёт пропавших Армии (
MarkMissing): они уходят из похода в отдельную группу. Один раз на поход. - Пропавшие не воюют и не едят, но занимают места в населении до возвращения или списания.
- Окно возвращения открывается после обратного пути от места боя (бой + время пути домой) и длится до 6 игровых часов. Возвращаются 2–4 группами (меньше, если пропавших мало); тип войск сохраняется; из вернувшихся 70% здоровы, 30% ранены (раненые — в госпиталь по обычным правилам). Исходной армии может уже не быть — здоровые идут в город.
- Сколько вернётся —
HospitalConfig.missingReturnPercent, ТЕСТ 50% (утверждён диапазон 10–90%, формула не утверждена). Остальные в конце окна списываются (MissingWrittenOffEvent), места освобождаются. - Кто и когда вернётся, решается сразу при бое (детерминированно); группы сохраняются, вернувшаяся группа удаляется — повторная загрузка или расчёт не возвращают её второй раз.
- Сохранение:
missing=,mgroup=,missingMarch=,missingNext=. Окно «Войска» показывает пропавших и сколько ещё могут вернуться; окна итога боя — сколько пропало.
Очередь обучения
Решения по 8 вопросам Саши от 31.10, п. 8: единственный владелец очереди — Армия. Раньше её вёл GameProgress (progress.json, время по настоящим часам) — перенесено.
| Кто | Что делает |
|---|---|
| Армия | Заказы по порядку (новый начинается, когда закончится последний), сроки, завершение — один раз на заказ (TroopsTrainedEvent), сохранение. |
| Население | Свободные жители и резерв (Recruitable, CivilianReserve), уход людей в Казармы (TryRecruit) и переход в воины (CompleteTraining). |
Слой приложения (GameProgress) |
Есть ли Казармы и открыт ли тип войск, длина очереди (Barracks.QueueSize), цена — через Экономику (если заказ не встал — цена возвращается), окна, статистика заданий по TroopsTrainedEvent. |
- Время — часы игры (
GameClock.UnixNowMs): перемотка «DEV +1 час» и офлайн досчитывают обучение так же, как марши. Раньше обучение шло по настоящим часам и перемотки не замечало. - Повторный расчёт или загрузка не обучают заказ второй раз: он удаляется из очереди в момент завершения.
- Население не нашло новобранцев заказа (испорченное сохранение) — заказ снимается без воинов: люди не появляются из ниоткуда, очередь не встаёт.
- Перенос старой очереди: после загрузки
App/GameModulesодин раз переносит заказы изprogress.json(Data.training) в Армию с тем же оставшимся временем и признаком «люди взяты». Изprogress.jsonони удаляются только после записиsave.json.
Правило «один солдат в одном месте»
Воин либо в городе, либо в походе. Поход забирает воинов из города и запоминает состав, возвращение раздаёт выживших по тем же типам. Тест ArmyService_MarchNeverCreatesOrLosesSoldiers проверяет, что «дома + в походах» всегда равно общему числу.
Возвращение — только по известному походу и только один раз (ревью 27.10.2026, п. 2.1):
- неизвестный поход (повторное событие, испорченное сохранение) не добавляет никого — раньше такие выжившие зачислялись пехотой;
- после возвращения запись похода удаляется, второе возвращение того же похода возвращает
false; - вернуться может не больше, чем ушло;
- два похода с одним номером невозможны:
TakeForMarchотклоняет повтор, загрузка и перенос старых данных пропускают дубликаты; - перенос старых сохранений — отдельно, в
ImportLegacy.
Гарнизон — часть воинов города. Они по-прежнему в городе (SoldiersAtHome), но в поход не уходят:
- 30 воинов, 10 в гарнизоне → 20 свободны, всего по-прежнему 30 (тест
ArmyService_Garrison_OneSoldierInOnePlace); - гарнизон по каждому типу никогда не больше воинов этого типа: после потерь (
LoseFraction) он обрезается; - новые воины из Казарм становятся свободными, гарнизон не меняется;
- назначается в окне «Войска» в городе, карточка «Гарнизон города»: кнопки 0, -10, -1, +1, +10, «Все».
Пока гарнизон только держит воинов дома: на город игрока никто не нападает. Защита города, еда гарнизона и дебаффы при голоде — по плану ниже.
События
- Публикует:
TroopsChangedEvent(воинов в городе, из них в гарнизоне, и в походах) — при обучении, потерях, походе, возвращении, загрузке, сбросе. По немуApp/GameModulesперерисовывает панели HUD. - Публикует:
TroopsTrainedEvent(номер заказа, тип, сколько, когда были готовы) — один раз на заказ. По немуGameProgressведёт статистику заданий и показывает «Обучено». - Слушает: ничего.
Сохранение
Секция "army". Модуль без Unity, поэтому формат — простые строки:
troops=<пехота>,<лучники>,<кавалерия>,<осадные>
garrison=<пехота>,<лучники>,<кавалерия>,<осадные> (часть troops)
march=<id похода>:<пехота>,<лучники>,<кавалерия>,<осадные>
train=<номер>|<тип>|<сколько>|<начало, мс>|<конец, мс>|<люди взяты 1/0> (по порядку очереди)
trainNext=<номер следующего заказа>
wounded=<пехота>,<лучники>,<кавалерия>,<осадные> (ждут места в госпитале)
heal=<номер>|<тип>|<сколько>|<начало, мс>|<конец, мс>
healNext=<номер следующей группы лечения>
Старое сохранение без строки garrison читается: гарнизона нет.
Сброс (ResetState): войск, гарнизона, походов, заказов обучения и раненых нет.
Перенос из старых сохранений:
- Раньше войска лежали в
progress.json:troopsхранилGameProgress,sashaMarchUnits— мостArmyBridge. - Если в
save.jsonнет секции"army", после загрузкиApp/GameModulesпереносит их в модуль (ImportLegacy). - Из
progress.jsonони удаляются только после записиsave.json. ФлагarmyInModuleотмечает, что перенос сделан.
Файлы
| Файл | Что это |
|---|---|
ArmyService.cs |
Хранилище войск, реализует IArmy и ISaveParticipant. |
ArmyMath.cs |
TakeProportional — кого взять в поход; SplitSurvivors — как вернуть выживших. |
Решения и план
- Этап 5 (план разработки): солдат стоит 1 человека, 15 еды, 5 металла, 5 золота; содержание 0,2 еды в час; погибшие уменьшают население. Делается вместе с модулем «Население».
- Решение команды от 19.10.2026:
- войска набираются из населения с сохранением гражданского минимума;
- гарнизон поселения: часть солдат назначается защищать город;
- общая армия = гарнизон + доступные отряды + войска в походах, на точках и в гарнизоне Круга, один солдат не может быть в двух местах;
- гарнизон ест еду и участвует в защите;
- при голоде дебаффы действуют и на гарнизон, и на походы.
- Сделано 27.10.2026: гарнизон города (
SetGarrison,AvailableForMarch). - Дальше по гарнизону: участие в защите города (когда на него смогут напасть — PvP), еда гарнизона (сейчас все воины едят одинаково, считает модуль Населения), дебаффы при голоде. Выбор состава по типам — если понадобится.
- Описания типов войск (
GameData.Troops: цена, сила, время обучения) пока в слое приложения; цены перейдут вEconomyConfig.
Журнал изменений
| Дата | Кто | Что |
|---|---|---|
| 04.11.2026 | Claude | Пропавшие без вести (решения 01.11, п. 5): MissingTotal, Missing, MarkMissing, ProcessMissing, события MissingReturnedEvent, MissingWrittenOffEvent; возвращение 2–4 группами в окне 6 игровых часов после обратного пути, 70/30, списание остальных; сохранение. Доля пропавших в бою и процент возврата не утверждены (0 и 50% в таблицах). |
| 04.11.2026 | Claude | IArmy.GetAllTroops(type) — все воины типа: в городе, в походах и раненые (для еды по типам, решения 01.11, п. 4). |
| 04.11.2026 | Claude | Решения по отчёту Саши от 01.11, п. 3: 5 временных мест до постройки госпиталя, цена лечения 1 / 1 / 2 / 3 золота за воина. |
| 01.11.2026 | Claude | Здание «Госпиталь» в городе: уровни I–V, 20–60 мест, окно с ранеными и улучшением; места Армии — через IHospital (HospitalBeds). Без госпиталя мест нет (temporaryBeds = 0). Тест Hospital_BedsFromBuilding_UpgradeFreesWaiting. |
| 01.11.2026 | Claude | Решения 31.10, п. 7: раненые и лечение у Армии — WoundedTotal, Healing, HospitalBeds, ReturnUnits(…, woundedUnits, nowMs), ProcessHealing, SoldiersHealedEvent; места от IHospital (пока нет здания — HospitalConfig.temporaryBeds), лишние ждут, цена через Экономику (не утверждена). Сохранение wounded=, heal=. Тесты: места и сроки, однократность, люди в Населении, сохранение и цена. |
| 01.11.2026 | Claude | Решения 31.10, п. 8: очередь обучения перенесена из GameProgress в Армию (Training, CanQueueTraining, TryQueueTraining, ProcessTraining, FinishAllTraining, TroopsTrainedEvent, строки train= в сохранении), время — часы игры, перенос старой очереди из progress.json. Изменены GameProgress, TroopsPanel, Barracks, BuildingWindow (код Вальдемара, с разрешения команды). Тесты: порядок и однократность, лимит и ускорение, люди из Населения и обратно, сохранение и перенос. |
| 31.10.2026 | Claude | Слоты похода: MarchSlots, ActiveMarches; ArmyService(baseMarchSlots), база 2 из таблицы MarchConfig; TakeForMarch отказывает при занятых слотах. Тест ArmyService_MarchSlots. |
| 28.10.2026 | Claude | По ревью 27.10.2026 (п. 2.1–2.2): ReturnFromMarch и ReturnUnits возвращают bool и зачисляют только по известному походу и только один раз; TakeForMarch отклоняет повторный номер похода; дубликаты походов в сохранении пропускаются. Тесты: неизвестный поход, повторное возвращение, повторный номер. |
| 27.10.2026 | Claude | Гарнизон города: GarrisonTotal, GetGarrison, AvailableForMarch, SetGarrison; в поход уходят только свободные; строка garrison= в сохранении; поле Garrison в TroopsChangedEvent; карточка «Гарнизон города» в окне «Войска». Тест «один солдат в одном месте» с гарнизоном. |
| 27.10.2026 | Claude | GetMarchUnits и ReturnUnits: бой с типами войск получает состав похода, выжившие возвращаются точно по типам. |
| 23.10.2026 | Claude | Войска переехали в модуль: ArmyService — единственное хранилище войск (секция "army"), IArmy расширен (типы, обучение, потери, войска в походах), событие TroopsChangedEvent. Перенос из progress.json с удалением старых данных только после записи save.json. ArmyBridge удалён — контракты и перенос сохранений проверены тестами. |
| 19.10.2026 | Claude | ArmyBridge.TroopsInMarches для панели «Войска». После удаления маленькой карты войска в походах учитываются только здесь (sashaMarchUnits). |
| 19.10.2026 | Claude | Модуль создан: ArmyMath (правила деления войск, без Unity, тесты на отсутствие двойного учёта). ArmyBridge сохранён и теперь реализует IArmy через ArmyMath; формат sashaMarchUnits не изменён. |