Разведка М3 (ядро): тайные операции -- группа до 10 пехоты/лучников, до 2 операций, проверки обнаружения, прерывание и возврат

- Разведка: CheckCovertGroup, StartCovert, CheckCovertUntil (15/25/35%, при входе и каждые 5 реальных минут, максимум при пересечении, 5-75%, детерминированно по одной сетке), EndCovert; сохранение covert=
- Армия: AvailableUnits, TakeUnitsForMarch -- поход точного состава
- Мир: AbortMission -- домой от места обнаружения, без отчёта; охраняемые области и позиция армии через IWorldMap
- Карта: «Тайно» в окне атаки и окне чужой армии, окно «РАЗВЕДЧИКОВ ЗАМЕТИЛИ»
- «Шпион» -- переключатель IntelligenceConfig (ТЕСТ); 5 тестов

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-11-06 17:15:22 +01:00
co-authored by Claude Opus 5.5
parent 56a5a82a8c
commit 2835c514d8
18 changed files with 622 additions and 14 deletions
@@ -156,6 +156,41 @@ namespace Bogatyri.World
progress = 0;
}
// ---------------- Разведка М3: миссия прервана ----------------
// Тайный отряд обнаружен в момент t: миссия прервана (без отчёта и боя), отряд физически идёт домой от места,
// где был в этот момент. Только для своих разведок, которые ещё не идут домой
public bool AbortMission(MarchData m, long t)
{
if (m == null || !m.scout || m.aborted || !marches.Contains(m)) return false;
if (m.state != MarchState.Outbound && m.state != MarchState.Gathering) return false;
if (!TryGetPositionAt(m, t, out int x, out int y, out _, out _, out _)) return false;
MapNode home = GetNode(m.fromNodeId);
if (home == null) return false;
int[] units = HasUnits(m) ? m.units : null;
m.aborted = true;
m.abortMs = t;
m.leaveX = x; m.leaveY = y;
m.targetEmpty = true; // отчёта не будет
m.victory = false;
m.survivors = m.soldiers;
m.survivorUnits = units != null ? (int[])units.Clone() : null;
m.loot = new System.Collections.Generic.List<RewardEntry>();
m.state = MarchState.Returning;
m.returnMs = t + TravelTimeMs(Point(x, y), home, units);
return true;
}
bool TryGetAbortedPositionAt(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 || m.returnMs > 0 && t >= m.returnMs) return false; // дома
LerpXY(m.leaveX, m.leaveY, home.x, home.y, m.abortMs, m.returnMs, t, out x, out y);
toX = home.x; toY = home.y;
return true;
}
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)
@@ -155,6 +155,7 @@ namespace Bogatyri.World
public bool TryGetPositionAt(MarchData m, long t, out int x, out int y, out int toX, out int toY, out int soldiers)
{
x = y = toX = toY = soldiers = 0;
if (m.aborted && t >= m.abortMs) return TryGetAbortedPositionAt(m, t, out x, out y, out toX, out toY, out soldiers);
if (IsArmyRecon(m)) return TryGetArmyReconPositionAt(m, t, out x, out y, out toX, out toY, out soldiers);
MapNode home = GetNode(m.fromNodeId), target = GetNode(m.targetNodeId);
if (home == null || target == null || t < m.departMs) return false;
@@ -35,6 +35,9 @@ namespace Bogatyri.World
public int targetMarchId; // Разведка М2: цель -- движущаяся армия (номер её похода); 0 -- цель -- узел карты
public int meetX, meetY; // где отряд перехватит армию (милли-единицы карты)
public int leaveX, leaveY; // где наблюдение кончится -- отсюда домой
public bool covert; // Разведка М3: тайная операция (группа до 10 пехоты / лучников)
public bool aborted; // миссию прервали (тайный отряд обнаружен): с abortMs идут домой от leaveX/leaveY
public long abortMs;
public long gatherEndMs; // когда сбор закончится (груз полон, точка истощена или исчезла)
public int cargo; // сколько ресурса несёт войско (известно с начала сбора)
public int defenders; // гарнизон цели в момент боя
@@ -548,6 +551,14 @@ namespace Bogatyri.World
// Где сейчас войско (для отрисовки). Возвращает долю пути 0..1 и точки начала/конца отрезка.
public void GetMarchSegment(MarchData m, long nowMs, out MapNode segmentFrom, out MapNode segmentTo, out double progress)
{
if (m.aborted && nowMs >= m.abortMs)
{
// миссия прервана -- идут домой от места, где их заметили
segmentFrom = Point(m.leaveX, m.leaveY);
segmentTo = GetNode(m.fromNodeId);
progress = m.returnMs > m.abortMs ? Math.Min(1.0, Math.Max(0.0, (nowMs - m.abortMs) / (double)(m.returnMs - m.abortMs))) : 1.0;
return;
}
if (IsArmyRecon(m)) { GetArmyReconSegment(m, nowMs, out segmentFrom, out segmentTo, out progress); return; }
MapNode home = GetNode(m.fromNodeId);
MapNode target = GetNode(m.targetNodeId);