Files
MVP1/Docs/Modules/World.md
T
AlexanderandClaude Opus 5.5 9a6e5a2e7b Карта мира в 2.5D на примитивах
Карта на земле (X/Z): замок, города, лагеря, ресурсные точки, отряды и метки разведки -- примитивы,
подписи смотрят в камеру; наклонная перспективная камера с тем же поведением (перетаскивание, щипок, тап);
туман -- в текстуре земли; солнце и 3D-рендерер URP для камеры (Map3D). Логика и окна не менялись.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 14:55:41 +01:00

33 KiB
Raw Blame History

Глобальный мир

Карта и перемещения: замок с боссом, 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) Отправить войско на цель.
TryStartScout(targetId, soldiers, out error) Поход «Разведка»: дойти до цели и вернуться — без боя, сбора и потерь.

MarchManager реализует и IWorldMap — для Разведки: границы карты, где поселение, где все армии в момент времени (WorldSimulation.TryGetPositionAt). ActiveMarches считает только походы игрока.

Патрули-заглушки (Разведка М1, временно): соседи иногда отправляют отряды к городам и лагерям и обратно. Без боя и добычи, Армия о них не знает. Включаются в MarchConfig (patrolsEnabled и др.), см. Intelligence.md.

Получить: GameServices.Get<IWorld>(). Интерфейсу карты (WorldMapController) можно обращаться к MarchManager.Instance и его C#-событиям: там полные данные похода.

События

Публикует:

У всех событий похода — номер похода и TimeMs: когда это случилось по часам игры, а не когда игра это заметила (после офлайна это разные моменты; документ 02).

  • MarchStartedEvent — войско выступило (TimeMs = выступление);

  • MarchBattleResolvedEvent — бой у цели прошёл (TimeMs = приход, BattleId = номер боя);

  • MarchReturnedEvent — войско вернулось, солдаты и добыча зачислены. Поля: MarchId, Victory, Survivors, Sent, TargetEmpty (лагерь уже был разбит, боя не было), TargetName, LootText.

  • WorldAdvancingEvent — мир сейчас продвинется до ToMs (перед каждым расчётом): Разведка успевает досчитать наблюдение, пока вернувшиеся походы ещё на карте.

У патрулей-заглушек событий нет.

Слушает: ничего.

По этим событиям 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.
Simulation/WorldSimulation.Patrols.cs Патрули-заглушки соседей (PatrolRules, PatrolState) и позиция армии в момент времени (TryGetPositionAt).

Бой с типами войск:

  • у похода есть состав по типам (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) и игрок видит ошибку. Если при возвращении Армия не знает поход, ни воины, ни добыча не зачисляются (предупреждение в консоли).

Номер боя: при выступлении поход получает battleSeed (из номера похода, времени выступления и цели) и хранит его в сохранении. Тот же поход всегда даёт тот же бой. После боя в походе запоминаются силы сторон и выпавшая случайность (attackerPower, defenderPower, attackerLuck, defenderLuck). Окно итога боя на карте показывает «Сила 356 : 160, удача −3,1% / +3,2%» (твоя / врага); текст окна ужимается, чтобы не уходить под кнопку OK. Окно атаки показывает прогноз риска и диапазоны силы и потерь.

Голод в бою: перед боем мир спрашивает состояние войска нападающего (WorldSimulation.AttackerConditions). MarchManager отвечает по модулю Населения: город голодает — войско бьётся на 50% силы. Мир сам голод не считает. Голод берётся на момент прихода войска: IPopulation.WasStarvingAt(время боя). Перед боем, прошедшим пока игра была закрыта, MarchManager просит Население досчитать время (IPopulation.Tick) — офлайн обрабатывается в причинно-следственном порядке (документ 02). Окно атаки показывает шанс победы (решение №11) и предупреждает: «Голод: войско слабее, сила 50%. Накормите город до боя», прогноз учитывает голод. Если голод начался или кончился, пока окно атаки открыто, предупреждение и прогноз обновляются сами. Окно итога боя пишет «голод: сила 50%». В походе запоминается attackerConditions и attackerConditionPercent.

Время походов — по GameClock: кнопки «+1 час» / «+10 часов» в отладочной панели проматывают и походы.

Ресурсные точки (регламент 31.10, раздел 7; решение №46)

Состояние точки ведёт Мир: запас, появление, истощение, конец экспедиции. Зачисление добычи и складские лимиты — Экономика: груз приходит как добыча похода (MarchData.loot) и зачисляется через IEconomy по возвращении, как трофеи боя. Числа — таблица Assets/Resources/WorldResourceConfig.asset (ТЕСТ).

Ресурс Запас: малая / средняя / крупная Добыча воина за игровой час Груз на воина
Дерево (Лес) 200 / 600 / 1500 2 12
Камень (Каменоломня) 150 / 450 / 1000 1,5 10
Металл (Рудник) 80 / 250 / 600 1 8
Золото (Золотая жила) 30 / 100 / 250 0,5 5
  • Срок жизни — 12 / 24 / 48 реальных часов с момента появления. 1 игровой час = 5 реальных минут.
  • Сбор: дошли до точки → войско стоит и собирает (MarchState.Gathering). Сбор кончается, когда груз полон, запас истощён или точка исчезла — что раньше; затем физический возврат с грузом. Всё известно в момент прихода, поэтому работает и при закрытой игре. Пример: 10 пехотинцев на Лес — 120 дерева за 30 реальных минут.
  • Осадные машины груза не несут (регламент); добывают наравне с остальными.
  • Одна точка — одно войско. Пока к точке идёт или на ней стоит войско, отправить другое нельзя.
  • Точка исчезла до прихода — возврат без добычи (окно «ТОЧКИ НЕТ»). Исчезла во время сбора — уносят, что успели.
  • Истощённая или исчезнувшая точка заменяется новой того же ресурса в другом месте (место и размер — из seed и номера появления, одинаково на сервере). Убирается с карты, когда к ней уже никто не идёт.
  • На карте — ромб цвета ресурса с подписью «запас, сколько осталось жить» или «идёт сбор». Окно отправки показывает запас, добычу и груз на воина, путь и прогноз «унесут N за T, весь поход ~X».
  • Сколько точек (решения по 8 вопросам Саши от 31.10, п. 6, ТЕСТ тестового участка): 24 — лес 8, камень 7, металл 5, золото 4; по размерам 12 малых, 8 средних, 4 крупных. Раскладка по ресурсам (документ её не задаёт, задана так): лес 4/3/1, камень 4/2/1, металл 2/2/1, золото 2/1/1 — WorldResourceConfig (woodCount, stoneCount, metalCount, goldCount). Исчезла точка — появляется точка того же ресурса и размера в другом месте, раскладка не плывёт. Старое сохранение дополняется до таблицы при загрузке.
  • На одной точке собирает только одна экспедиция: пока к точке идёт или на ней стоит войско, другое туда не отправить.
  • Точек еды нет (еда — с ферм). Синие кристаллы — отдельная механика, не в этой сетке.
  • Открыто: предел добычи на точку («скорость с пределом на точку»).
  • Позже: нападение на занятую точку (бой в поле, победитель забирает до 50% накопленного) — когда появятся другие игроки; досрочный отзыв — технология «Связь Волхвов»; переполнение склада при возвращении (5 минут на разгрузку) — вместе с Экономикой.

Панель армий (регламент 31.10, раздел 7: «нужна всем игрокам»): кнопка «Армии» на карте мира, под «Рейтингом» — окно WorldMap/ArmiesWindow.cs. Вверху — дома свободно, гарнизон, походов N из M. По каждому войску игрока: куда и что делает (идёт в бой / идёт на сбор / собирает / возвращается), состав по типам, груз, сколько до цели или до конца сбора и когда будет дома. Таймеры обновляются сами, пока окно открыто. До отправки окно атаки показывает длительность всего задания: для боя «туда и обратно ~X», для сбора «весь поход ~X».

Поход «Разведка» (решения по 8 вопросам Саши от 31.10, п. 2): кнопка «Разведка» в окне атаки, между «Отмена» и «Отправить».

  • Цель — любая, кроме своего города: замок, сосед, лагерь (и разбитый), ресурсная точка (и занятая).
  • Занимает обычный слот похода; воинов забирает Армия, как для боя.
  • У цели боя, сбора и потерь нет: войско поворачивает домой тем же составом (WorldEventType.ScoutArrived) и идёт обратно по времени, без телепортации. Цели уже нет (точка исчезла) — дошли до места и вернулись (targetEmpty).
  • Лагерь не разбивается, точка не занимается и не мешает чужому сбору.
  • Что войско увидит по пути и у цели — показывает Разведка (обзор армии 1 клетка): гарнизон цели, чужие армии. Отчёты и шпионаж — М2–М4.
  • Окно «ВОЙСКО ВЕРНУЛОСЬ»: «Разведка «…»: цель разведана». В событиях — MarchStartedEvent.IsScout, MarchReturnedEvent.IsScout.

Армии в пути проходят друг через друга (решения по 8 вопросам Саши от 31.10, п. 3): встреча маршрутов, даже в одной точке в один момент, — не столкновение, не остановка и не перехват. Бой — только у выбранной цели. Так же и с патрулями-заглушками. Команды «Перехватить» нет. Тест World_ArmiesPassThroughEachOther_NoBattleOnTheWay.

Порядок событий (документ 02: офлайн — в причинно-следственном порядке): WorldSimulation.Advance каждый раз берёт самое раннее событие — приход к цели (бой или начало сбора), конец сбора, исчезновение ресурсной точки или возвращение. Бой назначает время возвращения, поэтому за один вызов поход может и сразиться, и вернуться. При равном времени — бой раньше возвращения, затем по номеру похода. Раньше за один вызов сначала шли все бои, потом все возвращения — войско, вернувшееся раньше чужого боя, попадало в события после него. Повторный вызов с тем же временем ничего не создаёт заново.

Таймеры и единицы (документ 02: у каждого параметра — единицы):

Таймер Значение Единицы Статус
Скорость войска (MarchConfig, регламент 31.10) пехота 8, лучники 9, кавалерия 12, осадные 4 клетки за 1 реальный час ТЕСТ
Время в пути пехота: ближняя цель ~53 мин, замок ~65 мин, дальняя ~3 ч 12 мин; осадные — вдвое дольше пехоты реальных минут/часов ТЕСТ
Появление разбитого лагеря (CampRules.RespawnMs) 30 реальных минут после боя ТЕСТ (план 27.10: «30 реальных минут»)

Все таймеры мира — в миллисекундах часов GameClock (реальное время + перемотка DEV). Регламент 31.10: 1 игровой час = 5 реальных минут, но физический марш идёт в реальном времени — поэтому таймеры мира остаются реальными.

Скорость марша (регламент 31.10, раздел 7): у каждого типа своя скорость, таблица Assets/Resources/MarchConfig.asset (клеток карты за реальный час; 1 клетка = 1000 единиц координат). Смешанное войско идёт со средней скоростью по численности (решения по 8 вопросам Саши от 31.10, п. 4): V = Σ(Nᵢ × Vᵢ × Mᵢ) / ΣNᵢ, где Mᵢ — модификатор типа, а общий модификатор войска применяется один раз. Промежуточного округления нет: время пути считается точной дробью и округляется один раз в конце. Пример: 10 пехоты + 10 кавалерии = 10 клеток в час, с общим +10% — 11. Модификаторы в MarchConfig: typeSpeedBonusPercent (по типам) и armySpeedBonusPercent (всё войско), сейчас 0 — сюда придут бонусы героев и технологий. Совместные рейды нескольких армий (средняя скорость + штраф координации 0 / −5 / −10 / −15%) — когда появятся рейды. Время прихода пересчитывается, когда Армия забрала воинов (WorldSimulation.AssignUnits); обратно идут выжившие — со скоростью своего состава (погибли осадные — войско возвращается быстрее). Окно атаки показывает путь по составу при выбранном числе воинов. Бонусы героя (+15%) и технологии (+20%) — когда появятся герои и технологии.

Слоты похода: база 2 (MarchConfig.baseMarchSlots, ТЕСТ), «Военная логистика» поднимет до 7 — уровни и цены не утверждены. Слоты ведёт модуль Армии (IArmy.MarchSlots, ActiveMarches). При занятых слотах окно атаки пишет «Все слоты похода заняты», кнопка «В поход» неактивна; верхняя строка карты — «отрядов: 1 из 2».

В слое приложения остаются 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. В мультиплеере разбитый лагерь — общий для всех игроков, его состояние будет вести сервер.

Журнал изменений

Дата Кто Что
04.11.2026 Claude Карта мира в 2.5D на примитивах: карта лежит на земле (X/Z), замок — стена с башней, города — кубы, лагеря — цилиндры, точки — ромбы, отряды — кубики с полосой маршрута; подписи смотрят в камеру; наклонная перспективная камера (MapCameraController: перетаскивание, щипок, тап, границы); солнце в сцене; камера — 3D-рендерер URP (Map3D.UseRenderer3D). Логика, окна и кнопки не менялись. Ветка feature/3d-settlement.
01.11.2026 Claude Раненые (решения 31.10, п. 7): MarchData.woundedUnits — идут домой с войском, при возвращении Армия кладёт их в госпиталь; MarchReturnedEvent.Wounded; итог боя на карте показывает раненых.
01.11.2026 Claude Решения 31.10, п. 2: поход «Разведка» (IWorld.TryStartScout, MarchData.scout, WorldEventType.ScoutArrived), кнопка в окне атаки, тексты в окне армий, на отряде и в окне возвращения.
01.11.2026 Claude Решения 31.10, п. 3: армии в пути проходят друг через друга — правило записано, тест на одну точку в один момент.
01.11.2026 Claude Решения 31.10, п. 6: 24 ресурсные точки (лес 8, камень 7, металл 5, золото 4; размеры 12 / 8 / 4), число по ресурсу и размеру — в WorldResourceConfig; новая точка — того же размера; старые сохранения дополняются.
01.11.2026 Claude Решения 31.10, п. 4: скорость смешанного войска — средняя по численности с модификаторами (вместо «по самому медленному»), без промежуточного округления. MarchConfig: mixedUsesSlowest убран, добавлены typeSpeedBonusPercent, armySpeedBonusPercent.
31.10.2026 Claude Для Разведки М1: IWorldMap (армии на карте в момент времени), WorldAdvancingEvent, патрули-заглушки соседей (MarchData.patrol, сохранение patrols, таблица MarchConfig).
31.10.2026 Claude Панель армий (WorldMap/ArmiesWindow.cs, кнопка «Армии» на карте мира): местонахождение и действие каждого войска, состав, груз, время до цели / конца сбора / возвращения. В окне атаки — длительность всего задания для боя.
31.10.2026 Claude Ресурсные точки (регламент 31.10, раздел 7): ResourcePoint, ResourcePointRules, WorldSimulation.Points.cs — появление, сбор (MarchState.Gathering), истощение, исчезновение, замена новой точкой; события GatherStarted, GatherFinished; сохранение в секции world; таблица WorldResourceConfig; на карте — ромбы точек, окно отправки с прогнозом сбора, подпись отряда «сбор». Тесты: появление, сбор и груз, истощение и новая точка, осадные без груза, точка исчезла до прихода и во время сбора, одна точка — одно войско, сохранение посреди сбора.
31.10.2026 Claude Скорости маршей по типам (регламент 31.10: 8 / 9 / 12 / 4 клетки за реальный час, таблица MarchConfig), войско — по самому медленному типу, обратно — по составу выживших; WorldSimulation.TravelTimeMs, AssignUnits, MarchSpeedRules; слоты похода (база 2) в окне атаки и верхней строке. Тесты: скорости по типам, смешанное войско, обратный путь; старые тесты переведены на часовые марши.
30.10.2026 Claude События строго по времени после офлайна (раньше — сначала все бои, потом все возвращения); TimeMs у событий похода и WorldEvent, BattleId у события боя; таблица таймеров с единицами. Тесты: порядок после офлайна, повторный вызов без дублей, границы таймеров, 0 и 1 солдат.
30.10.2026 Claude Голод на момент боя — по истории голода Населения (WasStarvingAt), перед офлайн-боем Население досчитывает время. Окно атаки: шанс победы в процентах.
28.10.2026 Claude Голод в момент боя ослабляет войско игрока (−50%): WorldSimulation.AttackerConditions, MarchManager.PlayerConditions; предупреждение в окне атаки, строка в итоге боя. Тест: голод в момент боя — поражение.
28.10.2026 Claude Номер боя у похода (battleSeed), силы и удача сторон в итоге; прогноз риска в окне атаки, удача в окне итога боя. Тест: тот же поход — тот же бой.
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", статистика — через события. Формула боя вынесена в модуль Боя.