Files
MVP1/Assets/Scripts/Modules/World/Simulation/WorldSimulation.ArmyRecon.cs
T

170 lines
10 KiB
C#
Raw Normal View History

using System;
namespace Bogatyri.World
{
// РАЗВЕДКА ДВИЖУЩЕЙСЯ АРМИИ (Разведка М2, ТЗ разведки, раздел 6: «вражеская армия -- 1 / 2 игровых часа»).
// Весь путь -- физический марш, без телепорта и без постоянного слежения:
// 1) отряд идёт из города к точке перехвата -- где армия окажется, когда отряд до неё дойдёт (по маршруту армии
// и скорости отряда, с шагом InterceptStepMs);
// 2) наблюдает reconHoldMs, идя рядом с армией (позиция отряда = позиция армии);
// 3) отчёт на конец наблюдения, затем домой из места, где наблюдение кончилось.
// Всё время похода известно при выступлении (маршрут армии известен Миру), поэтому офлайн считается так же.
// Догнать нельзя или армия уйдёт с карты раньше конца наблюдения -- поход не начинается (ошибка для игрока).
// Армия-цель исчезла до перехвата (патрули выключили) -- отряд дошёл до точки и вернулся (targetEmpty).
public sealed partial class WorldSimulation
{
public const long InterceptStepMs = 10_000;
// Поход ли это на армию (а не на узел карты)
public static bool IsArmyRecon(MarchData m) => m != null && m.targetMarchId > 0;
public MarchData FindMarch(int id) => marches.Find(m => m.id == id);
// Начать разведку армии. Состав ещё неизвестен -- план как для пехоты; AssignUnits пересчитает по составу
public MarchData StartArmyRecon(string ownerId, string fromId, int targetMarchId, int soldiers, long nowMs, int level, long holdMs, out string error)
{
MapNode from = GetNode(fromId);
MarchData target = FindMarch(targetMarchId);
if (from == null) { error = "Неизвестная точка на карте"; return null; }
if (target == null || !TryGetPositionAt(target, nowMs, out _, out _, out _, out _, out _)) { error = "Этой армии уже нет на карте"; return null; }
if (target.ownerId == ownerId) { error = "Это ваша армия"; return null; }
if (soldiers <= 0) { error = "Нужно отправить хотя бы одного солдата"; return null; }
if (level < 2 || level > 3 || holdMs <= 0) { error = "Уровень разведки -- II или III"; return null; }
var march = new MarchData
{
id = nextMarchId++,
ownerId = ownerId,
fromNodeId = fromId,
targetNodeId = target.targetNodeId, // узел, куда идёт армия (для старого кода, которому нужен узел)
targetMarchId = targetMarchId,
targetName = "чужая армия",
soldiers = soldiers,
survivors = soldiers,
departMs = nowMs,
state = MarchState.Outbound,
scout = true,
reconLevel = level,
reconHoldMs = holdMs,
};
if (!PlanArmyRecon(march, null, out error)) { nextMarchId--; return null; }
march.battleSeed = BattleSeed(march.id, nowMs, "march_" + targetMarchId);
marches.Add(march);
return march;
}
// Рассчитать поход без старта (для окна: «догонят через N, вернутся через M»)
public bool TryPlanArmyRecon(string fromId, int targetMarchId, long nowMs, long holdMs, out long meetMs, out long endMs, out long returnMs, out string error)
{
var probe = new MarchData { fromNodeId = fromId, targetMarchId = targetMarchId, departMs = nowMs, reconHoldMs = holdMs, scout = true, reconLevel = 2 };
bool ok = PlanArmyRecon(probe, null, out error);
meetMs = probe.arriveMs; endMs = probe.gatherEndMs; returnMs = probe.returnMs;
return ok;
}
// Перехват, наблюдение, обратный путь -- по составу units (null -- как пехота)
bool PlanArmyRecon(MarchData m, int[] units, out string error)
{
error = null;
MapNode home = GetNode(m.fromNodeId);
MarchData target = FindMarch(m.targetMarchId);
if (home == null || target == null) { error = "Этой армии уже нет на карте"; return false; }
// 1) Перехват: первый момент, когда отряд успевает дойти до места, где в этот момент будет армия
long limit = target.returnMs > 0 ? target.returnMs : m.departMs + 48L * 3_600_000;
long meet = -1;
int mx = 0, my = 0;
for (long t = m.departMs; t <= limit; t += InterceptStepMs)
{
if (!TryGetPositionAt(target, t, out int x, out int y, out _, out _, out _)) break; // армия ушла с карты
if (m.departMs + TravelTimeMs(home, Point(x, y), units) <= t) { meet = t; mx = x; my = y; break; }
}
if (meet < 0) { error = "Армию не догнать: она уйдёт раньше, чем разведчики до неё доберутся"; return false; }
// 2) Наблюдение рядом с армией: она должна оставаться на карте до конца
long end = meet + m.reconHoldMs;
if (!TryGetPositionAt(target, end, out int lx, out int ly, out _, out _, out _))
{
error = "Армия уйдёт с карты раньше, чем закончится наблюдение";
return false;
}
// 3) Домой -- из места, где наблюдение кончилось
m.arriveMs = meet;
m.meetX = mx; m.meetY = my;
m.gatherEndMs = end;
m.leaveX = lx; m.leaveY = ly;
m.returnMs = end + TravelTimeMs(Point(lx, ly), home, units);
return true;
}
// Отряд дошёл до точки перехвата: армия на месте -- наблюдает (время уже рассчитано); армии нет -- домой
void ArriveArmyRecon(MarchData m)
{
MapNode home = GetNode(m.fromNodeId);
int[] units = HasUnits(m) ? m.units : null;
m.victory = false;
m.survivors = m.soldiers;
m.survivorUnits = units != null ? (int[])units.Clone() : null;
m.loot = new System.Collections.Generic.List<RewardEntry>();
MarchData target = FindMarch(m.targetMarchId);
m.targetEmpty = target == null || !TryGetPositionAt(target, m.arriveMs, out _, out _, out _, out _, out _);
if (m.targetEmpty)
{
m.gatherEndMs = 0;
m.leaveX = m.meetX; m.leaveY = m.meetY;
m.returnMs = m.arriveMs + TravelTimeMs(Point(m.meetX, m.meetY), home, units);
m.state = MarchState.Returning;
return;
}
m.state = MarchState.Gathering; // наблюдает до gatherEndMs, обратный путь уже рассчитан
}
// Позиция отряда разведки армии в момент t
bool TryGetArmyReconPositionAt(MarchData m, long t, out int x, out int y, out int toX, out int toY, out int soldiers)
{
x = y = toX = toY = 0;
soldiers = m.soldiers;
MapNode home = GetNode(m.fromNodeId);
if (home == null || t < m.departMs) return false;
if (t < m.arriveMs)
{
LerpXY(home.x, home.y, m.meetX, m.meetY, m.departMs, m.arriveMs, t, out x, out y);
toX = m.meetX; toY = m.meetY;
return true;
}
long backStart = m.targetEmpty || m.gatherEndMs <= 0 ? m.arriveMs : m.gatherEndMs;
if (t < backStart)
{
// идёт рядом с армией; армии уже нет (не должно быть по плану) -- стоит, где кончилось наблюдение
MarchData target = FindMarch(m.targetMarchId);
if (target == null || !TryGetPositionAt(target, t, out x, out y, out toX, out toY, out _)) { x = m.leaveX; y = m.leaveY; toX = x; toY = y; }
return true;
}
if (m.returnMs > 0 && t >= m.returnMs) return false; // дома
LerpXY(m.leaveX, m.leaveY, home.x, home.y, backStart, m.returnMs, t, out x, out y);
toX = home.x; toY = home.y;
return true;
}
// Отрезок для отрисовки: точки как временные узлы карты (не входят в Nodes)
void GetArmyReconSegment(MarchData m, long nowMs, out MapNode from, out MapNode to, out double progress)
{
TryGetArmyReconPositionAt(m, nowMs, out int x, out int y, out int tx, out int ty, out _);
from = Point(x, y);
to = Point(tx, ty);
progress = 0;
}
static MapNode Point(int x, int y) => new MapNode { id = "", x = x, y = y, displayName = "" };
static void LerpXY(int ax, int ay, int bx, int by, long start, long end, long t, out int x, out int y)
{
if (end <= start) { x = bx; y = by; return; }
long p = Math.Max(0, Math.Min(end - start, t - start));
x = ax + (int)((long)(bx - ax) * p / (end - start));
y = ay + (int)((long)(by - ay) * p / (end - start));
}
}
}