Feature/3d hud #18
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 мс) -- по времени похода, а не по текущему состоянию:
|
||||
|
||||
@@ -106,4 +106,5 @@
|
||||
|
||||
| Дата | Кто | Что |
|
||||
| --- | --- | --- |
|
||||
| 31.10.2026 | Claude | Исправлено: после перезапуска или перемотки обратный путь войска не открывал туман (до расчёта боя Разведка считала, что войско стоит у цели). Мир досчитывается по событиям (`WorldSimulation.AdvanceInSteps`), Разведка — до каждого из них. Туман темнее. |
|
||||
| 31.10.2026 | Claude | Модуль создан: этап М1 — туман, источники обзора, «Обзор», чужие армии, примерная численность, опознание, последний контакт, офлайн-наблюдение, сохранение. Патрули-заглушки соседей для проверки (выключаются в `MarchConfig`). |
|
||||
|
||||
Reference in New Issue
Block a user