Feature/3d hud #18

Merged
Alexander merged 59 commits from feature/3d-hud into develop 2026-11-06 18:09:33 +03:00
4 changed files with 91 additions and 5 deletions
Showing only changes of commit 54036eafcf - Show all commits
@@ -1499,4 +1499,59 @@ public class ModuleTests
}
Assert.Greater(spotted, 0, "за сутки хотя бы один патруль прошёл рядом с поселением");
}
[Test]
public void Intelligence_OwnMarchExploresWholePath()
{
long t0 = 1_000_000_000_000;
var world = PatrolWorld(false);
var intel = Intel(new WorldAsMap { World = world });
MapNode home = world.GetNode(WorldMapLayout.PlayerCityId);
MapNode far = null;
foreach (var n in world.Nodes)
if (n.type == MapNodeType.NeighborCity && (far == null || WorldMapLayout.Distance(home, n) > WorldMapLayout.Distance(home, far))) far = n;
MarchData m = world.StartMarch(LocalPlayer.Id, home.id, far.id, 17, t0, out _);
world.AssignUnits(m, new[] { 17, 0, 0, 0 });
var events = new List<WorldEvent>();
for (long t = t0; t <= m.arriveMs + 60_000; t += 1000)
{
intel.ObserveUntil(t);
world.Advance(t, events);
}
for (int i = 0; i <= 20; i++)
{
int x = home.x + (far.x - home.x) * i / 20, y = home.y + (far.y - home.y) * i / 20;
Assert.AreNotEqual(MapVisibility.Unexplored, intel.GetVisibility(x, y), $"точка пути {i}/20 ({x},{y})");
}
}
[Test]
public void Intelligence_OfflineMarch_ExploresPathThereAndBack()
{
long t0 = 1_000_000_000_000;
var world = PatrolWorld(false);
var intel = Intel(new WorldAsMap { World = world });
MapNode home = world.GetNode(WorldMapLayout.PlayerCityId);
MapNode far = null;
foreach (var n in world.Nodes)
if (n.type == MapNodeType.NeighborCity && (far == null || WorldMapLayout.Distance(home, n) > WorldMapLayout.Distance(home, far))) far = n;
intel.ObserveUntil(t0);
MarchData m = world.StartMarch(LocalPlayer.Id, home.id, far.id, 200, t0, out _);
world.AssignUnits(m, new[] { 200, 0, 0, 0 });
long back = m.arriveMs + (m.arriveMs - t0);
// Игра закрыта до половины обратного пути: войско дошло, бой, идёт домой
var events = new List<WorldEvent>();
long now = m.arriveMs + (back - m.arriveMs) / 2;
world.AdvanceInSteps(now, events, t => intel.ObserveUntil(t));
Assert.AreEqual(1, events.Count, "бой посчитан");
Assert.IsTrue(m.survivors > 0);
Assert.IsTrue(world.TryGetPositionAt(m, now, out int x, out _, out _, out _, out _));
Assert.AreNotEqual(far.x, x, "войско в пути домой, а не у цели");
for (int i = 0; i <= 20; i++)
{
int px = home.x + (far.x - home.x) * i / 20, py = home.y + (far.y - home.y) * i / 20;
Assert.AreNotEqual(MapVisibility.Unexplored, intel.GetVisibility(px, py), $"точка пути {i}/20");
}
}
}
+3 -5
View File
@@ -101,12 +101,10 @@ public class MarchManager : MonoBehaviour, IWorld, IWorldMap, ISaveParticipant
long now = NowMs;
CatchUpPopulationBeforeBattles(now);
// Сначала выходят патрули, потом Разведка досчитывает наблюдение до "сейчас" -- пока вернувшиеся
// походы ещё на карте (после расчёта они исчезнут)
World.SpawnPatrolsUntil(now);
EventBus.Publish(new WorldAdvancingEvent { ToMs = now });
// Мир досчитывается по событиям, перед каждым шагом Разведка досчитывает наблюдение до него:
// походы ещё на карте и их выжившие уже известны (после офлайна обратный путь тоже виден)
events.Clear();
World.Advance(now, events);
World.AdvanceInSteps(now, events, t => EventBus.Publish(new WorldAdvancingEvent { ToMs = t }));
foreach (var e in events)
{
@@ -115,6 +115,38 @@ namespace Bogatyri.World
});
}
// Продвинуть мир до nowMs по событиям: перед каждым шагом beforeStep(момент) -- Разведка досчитывает
// наблюдение до этого момента. Так после офлайна обратный путь войска считается уже после боя
// (выжившие известны), а не как "стоят у цели". Итог тот же, что у одного Advance(nowMs).
public void AdvanceInSteps(long nowMs, List<WorldEvent> events, Action<long> beforeStep)
{
SpawnPatrolsUntil(nowMs);
long next;
while ((next = NextEventMs(nowMs)) >= 0)
{
beforeStep?.Invoke(next);
Advance(next, events);
}
beforeStep?.Invoke(nowMs);
Advance(nowMs, events);
}
// Ближайшее событие мира не позже nowMs (приход, конец сбора, исчезновение точки, возвращение); -1 -- нет
public long NextEventMs(long nowMs)
{
long best = -1;
foreach (var m in marches)
{
long at = m.state == MarchState.Outbound ? m.arriveMs
: m.state == MarchState.Gathering ? m.gatherEndMs
: m.state == MarchState.Returning ? m.returnMs : long.MaxValue;
if (at <= nowMs && (best < 0 || at < best)) best = at;
}
foreach (var p in points)
if (p.active && p.occupiedBy == 0 && p.expireMs <= nowMs && (best < 0 || p.expireMs < best)) best = p.expireMs;
return best;
}
static long Dist(MapNode a, MapNode b) => WorldMapLayout.Distance(a, b);
// Где войско в момент t (Unix мс) -- по времени похода, а не по текущему состоянию:
+1
View File
@@ -106,4 +106,5 @@
| Дата | Кто | Что |
| --- | --- | --- |
| 31.10.2026 | Claude | Исправлено: после перезапуска или перемотки обратный путь войска не открывал туман (до расчёта боя Разведка считала, что войско стоит у цели). Мир досчитывается по событиям (`WorldSimulation.AdvanceInSteps`), Разведка — до каждого из них. Туман темнее. |
| 31.10.2026 | Claude | Модуль создан: этап М1 — туман, источники обзора, «Обзор», чужие армии, примерная численность, опознание, последний контакт, офлайн-наблюдение, сохранение. Патрули-заглушки соседей для проверки (выключаются в `MarchConfig`). |