Армия (п. 2.1): - ReturnFromMarch / ReturnUnits возвращают bool и зачисляют воинов только по известному походу и только один раз (раньше неизвестный поход добавлял выживших пехотой, ReturnUnits — без ограничения) - вернуться может не больше, чем ушло - TakeForMarch отклоняет повторный номер похода (раньше перезапись теряла воинов первого) - загрузка сохранения и перенос старых данных пропускают дубликаты походов Мир (п. 2.2): - TryStartMarch проверяет TakeForMarch; при отказе поход откатывается (WorldSimulation.CancelMarch) - если Армия не знает возвращающийся поход — ни воинов, ни добычи, предупреждение в консоли Тесты (п. 2.1, 2.2, 2.4): - неизвестный поход, повторное возвращение, повторный номер, откат похода - крайние случаи боя: 1×1, 1×10, 10×1, 0×0, пустая сторона, бонусы −100% / −500% / +100000%, бой числом Формула потерь не менялась. Найдено и записано в Battle.md как открытый вопрос: одиночный проигравший не гибнет (потери округляются вниз) — правило округления решает команда. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 KiB
Глобальный мир
Карта и перемещения: замок с боссом, 20 соседних городов, 12 лагерей разбойников, походы по реальному времени (идут, даже когда игра закрыта).
- Сборки:
Bogatyri.World.Simulation— карта и походы, чистый C# без Unity (переедет на сервер);Bogatyri.World—MarchManager, Unity-часть.
- Папка:
Assets/Scripts/Modules/World. - Зависит от: ядра. Войска — через
IArmy, добыча — черезIEconomy, бой — черезIBattleSystem.
Контракт IWorld
| Член | Что делает |
|---|---|
ActiveMarches |
Сколько походов в пути. |
TryStartMarch(targetId, soldiers, out error) |
Отправить войско на цель. |
Получить: GameServices.Get<IWorld>(). Интерфейсу карты (WorldMapController) можно обращаться к MarchManager.Instance и его C#-событиям: там полные данные похода.
События
Публикует:
MarchStartedEvent— войско выступило;MarchBattleResolvedEvent— бой у цели прошёл;MarchReturnedEvent— войско вернулось, солдаты и добыча зачислены. Поля:MarchId,Victory,Survivors,Sent,TargetEmpty(лагерь уже был разбит, боя не было),TargetName,LootText.
Слушает: ничего.
По этим событиям App/GameModules:
- считает статистику для заданий (
marchesSent,dailyMarches,worldWins); - показывает окно «ВОЙСКО ВЕРНУЛОСЬ» в городе (
UI/MarchReturnPopup.cs): куда ходили, победа или поражение, сколько вернулось из скольких, добыча.- Окно появляется и за походы, закончившиеся, пока игра была закрыта: сразу после входа в город.
- Несколько возвращений — окна по очереди.
- На карте мира окно своё (
WorldMapController).
Сохранение
Секция "world": { "marches": [ MarchData ], "camps": [ { "id", "respawnMs" } ] } — походы и разбитые лагеря с моментом их появления. Старые сохранения без camps читаются: все лагеря на месте.
Сброс (ResetState): все походы отменяются, все лагеря на месте. Войска в походе не возвращаются: их сбрасывает GameProgress вместе с армией.
Раньше походы лежали в progress.json (sashaMarches). При первом запуске App/GameModules переносит их в модуль Мира и удаляет из progress.json только после записи save.json.
Файлы
| Файл | Что это |
|---|---|
MarchManager.cs |
Ведёт походы, реализует IWorld и ISaveParticipant. Создаётся сам при запуске игры. |
Simulation/WorldMapLayout.cs |
Карта: замок, город игрока, 20 соседей и 12 лагерей из seed. |
Simulation/WorldSimulation.cs |
Походы: старт, бой по прибытии, возвращение. Бой получает через IBattleSystem. |
Бой с типами войск:
- у похода есть состав по типам (
MarchData.units, берётся у Армии при выступлении) и выжившие по типам (survivorUnits); - у цели — состав гарнизона
WorldMapLayout.GarrisonUnits(node). Он вычисляется из id цели, а не из генератора карты, поэтому карта осталась прежней. У Змея Горыныча 40% пехоты, 30% лучников, 20% кавалерии, 10% осадных; у соседей пропорции свои у каждого; - замок и города сражаются за стенами;
- выжившие возвращаются в Армию точно по типам (
IArmy.ReturnUnits). Старые походы без состава — как раньше, по пропорции; - окно атаки показывает состав гарнизона и ожидаемый исход: силу сторон, «скорее победа / поражение» и примерные потери. Расчёт тот же, что сделает модуль Боя.
Лагеря разбойников (PvE):
- 12 лагерей, по 4 каждого уровня. Ставятся отдельным генератором после городов (
seed ^ CampSeedSalt), поэтому замок, твой город и соседи остались на прежних местах. Лагерь может стоять ближе к городу (2,6 клетки), чем город к городу (3,6).
| Уровень | Название | Гарнизон | Добыча | Нужно пехоты (примерно) |
|---|---|---|---|---|
| 1 | Стан разбойников | 6–9 | 15 еды, 15 дерева | 8–12 |
| 2 | Логово лиходеев | 14–17 | 25 еды, 25 дерева, 8 золота | 18–21 |
| 3 | Становище Соловья-разбойника | 26–29 | 40 еды, 40 дерева, 15 золота, 8 металла | 34–37 |
- бой в поле: стен нет (
WorldMapLayout.IsFortified), у разбойников нет осадных — их доля уходит пехоте; - после победы лагерь разбит на 30 минут (
CampRules.RespawnMs, отсчёт от момента боя): на карте серый, с таймером, напасть нельзя; - если к лагерю шли два войска, бьётся первое пришедшее (бои считаются по времени прихода). Второе находит лагерь пустым: боя нет, домой идут все, добычи нет (
MarchData.targetEmpty, окно «ЛАГЕРЬ ПУСТ»); - на карте лагерь — фиолетовый круг, подпись «название, ур. N, гарнизон».
Отправка целиком или никак (ревью 27.10.2026, п. 2.2): MarchManager.TryStartMarch создаёт поход, затем просит Армию забрать воинов. Если Армия отказала, поход откатывается (WorldSimulation.CancelMarch) и игрок видит ошибку. Если при возвращении Армия не знает поход, ни воины, ни добыча не зачисляются (предупреждение в консоли).
Время походов — по GameClock: кнопки «+1 час» / «+10 часов» в отладочной панели проматывают и походы.
В слое приложения остаются WorldMap/WorldMapController.cs, MarchView.cs, MapCameraController.cs, SceneNavigator.cs.
В игре одна карта мира — сцена WorldMap. Открывается кнопкой «БОЙ» и кнопкой «МИР» в городе (UIPanelManager.Open("World") ведёт в эту сцену).
Решения и план
- Этап 0, решено 19.10.2026: осталась одна карта мира — эта. Маленькая карта (панель «МИР»,
Meta/World) удалена. Перед удалением проверены ссылки в коде, сценах и префабах. Перенос сохранений: солдаты из её походов (marchesвprogress.json) возвращаются в город при загрузке — отправленные, а если отряд уже шёл назад, то выжившие. Добыча таких походов не переносится. - Этап 4 (утверждён без изменений): у позиций на карте — месторождения и их качество.
- Этап 6: временные ресурсные точки и кристальные разломы; войска на точке недоступны дома.
- Лагеря: числа (гарнизон, добыча, 30 минут до появления) — предложение, требуют балансировки. Позже их можно перенести в
EconomyConfig. В мультиплеере разбитый лагерь — общий для всех игроков, его состояние будет вести сервер.
Журнал изменений
| Дата | Кто | Что |
|---|---|---|
| 28.10.2026 | Claude | По ревью 27.10.2026: отправка с проверкой и откатом (CancelMarch), возвращение по неизвестному походу ничего не зачисляет. Тест отмены похода. |
| 27.10.2026 | Claude | В поход уходят только свободные воины: гарнизон остаётся в городе. Верхняя панель карты: «Свободно / Гарнизон / В походе». |
| 27.10.2026 | Claude | Лагеря разбойников (PvE): 12 лагерей трёх уровней, бой в поле, после победы лагерь разбит на 30 минут, второе войско находит его пустым. Секция world хранит разбитые лагеря, в MarchReturnedEvent добавлен TargetEmpty. Тесты: расстановка лагерей, разбит и появляется снова, сохранение. |
| 27.10.2026 | Claude | Бой с типами войск: состав похода и гарнизона, стены у замка и городов, выжившие по типам, ожидаемый исход в окне атаки. Время походов — по GameClock (перемотка в DEV проматывает походы). |
| 23.10.2026 | Claude | Окно «ВОЙСКО ВЕРНУЛОСЬ» в городе. В MarchReturnedEvent добавлены Sent, TargetName, LootText. |
| 19.10.2026 | Claude | Маленькая карта мира (Meta/World: WorldSystem, WorldMapPanel, WorldData, WorldLine) удалена, окно Panel_World убрано из сцены города. Кнопка «МИР» открывает эту карту. Солдаты из походов старой карты возвращаются в город. |
| 19.10.2026 | Claude | ResetState(): при сбросе сохранений походы отменяются. |
| 19.10.2026 | Claude | Модуль выделен. MarchManager больше не обращается к GameProgress и ResourceManager: войска через IArmy, добыча через IEconomy, сохранение — своя секция "world", статистика — через события. Формула боя вынесена в модуль Боя. |