Files
MVP1/Assets/Scripts/WorldMap/ReconReportText.cs
T
AlexanderandClaude Opus 5.5 2835c514d8 Разведка М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>
2026-11-06 17:15:22 +01:00

120 lines
7.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using TMPro;
using UnityEngine;
// Отчёты разведки на экране (Разведка М2): текст отчёта и окно «Отчёты» с последними отчётами.
// Сами отчёты хранит модуль Разведки (IIntelligence.Reports) -- здесь только показ.
// Сведения -- на момент отчёта: сколько прошло с тех пор, видно в каждом отчёте.
public static class ReconReportText
{
static readonly string[] TroopNames = { "пехота", "лучники", "кавалерия", "осадные" };
static readonly Dictionary<string, string> ResourceNames = new Dictionary<string, string>
{
{ "Food", "еда" }, { "Wood", "дерево" }, { "Stone", "камень" }, { "Iron", "металл" }, { "Gold", "золото" }, { "Gems", "кристаллы" }
};
public static string Format(ReconReport r, long nowMs)
{
var lines = new List<string>
{
$"<b>{r.TargetName}</b> — разведка {(r.Level == 3 ? "III" : "II")}, {Ago(nowMs - r.TimeMs)} назад",
};
int total = 0;
var parts = new List<string>();
for (int i = 0; r.Units != null && i < r.Units.Length; i++)
{
total += r.Units[i];
if (r.Units[i] > 0) parts.Add($"{TroopNames[i % TroopNames.Length]} {r.Units[i]}");
}
lines.Add($"Гарнизон: {total}" + (parts.Count > 0 ? $" ({string.Join(", ", parts)})" : ""));
lines.Add((r.Fortified ? "Защитники за стенами" : "Стен нет, бой в поле") + (r.HasHero ? ", есть герой" : ", героя нет"));
if (r.Level >= 3)
{
if (!string.IsNullOrEmpty(r.Modifiers)) lines.Add("Защита: " + r.Modifiers);
var res = new List<string>();
for (int i = 0; r.ResourceNames != null && i < r.ResourceNames.Length; i++)
{
string name = ResourceNames.TryGetValue(r.ResourceNames[i], out var n) ? n : r.ResourceNames[i];
res.Add($"{name} ~{r.ResourceMin[i]}–{r.ResourceMax[i]}");
}
if (res.Count > 0) lines.Add("Ресурсы (оценка ±15%): " + string.Join(", ", res));
}
return string.Join("\n", lines);
}
static string Ago(long ms)
{
long min = System.Math.Max(0, ms) / 60_000;
if (min < 1) return "только что";
return min < 60 ? $"{min} мин" : $"{min / 60} ч {min % 60} мин";
}
// Окно чужой армии (Разведка М2): что видно сейчас, последний отчёт и «Разведка II / III» -- отряд догонит армию,
// пойдёт рядом время наблюдения и вернётся с отчётом. Воинов -- до ArmyReconSoldiers из свободных
public const int ArmyReconSoldiers = 10;
// Тайный режим разведки (Разведка М3): общий для окна атаки и окна чужой армии
public static bool CovertMode;
public static void OpenArmyWindow(KnownContact army, System.Action<string> hint)
{
var card = UIKit.Modal("ЧУЖАЯ АРМИЯ", out GameObject root);
long now = MarchManager.NowMs;
string owner = army.Identified ? IntelligenceOverlay.OwnerName(MarchManager.Instance.World, army.OwnerId) : "владелец неизвестен";
UIKit.Text(card, $"~{army.ApproxMin}–{army.ApproxMax} воинов, {owner}", 26, UIKit.Theme.Text, TextAlignmentOptions.Center);
if (GameServices.TryGet(out IIntelligence intel) && intel.TryGetLatestReport(army.ArmyId, out var report))
UIKit.Text(card, Format(report, now), 22, UIKit.Theme.TextDim, TextAlignmentOptions.Left);
int free = GameServices.TryGet(out IArmy myArmy) ? myArmy.AvailableForMarch : 0;
int soldiers = Mathf.Min(ArmyReconSoldiers, free);
bool spy = GameServices.TryGet(out IIntelligence spyIntel) && spyIntel.CovertEnabled;
if (spy)
UIKit.Button(card, CovertMode ? "Тайно: ВКЛ (до 10 пехоты и лучников)" : "Тайно: выкл", CovertMode ? UIKit.Theme.Green : UIKit.Theme.Gray, () =>
{
CovertMode = !CovertMode;
Object.Destroy(root);
OpenArmyWindow(army, hint);
}, -1, 70, 24);
UIKit.Text(card, soldiers > 0 ? $"Отправить {soldiers} воинов: догонят армию, пойдут рядом и вернутся с отчётом. Без боя."
: "Нет свободных воинов для разведки", 22, UIKit.Theme.TextDim, TextAlignmentOptions.Center);
for (int level = 2; level <= 3; level++)
{
int lv = level;
string name = lv == 3 ? "III" : "II";
bool ok = MarchManager.Instance.TryPlanArmyRecon(army.ArmyId, lv, out long meet, out long reportAt, out long home, out string error);
string plan = ok ? $"догонят через {Ago(meet - now)}, отчёт через {Ago(reportAt - now)}, дома через {Ago(home - now)}" : error;
UIKit.Text(card, $"Разведка {name}: {plan}", 22, ok ? UIKit.Theme.Text : UIKit.Theme.Gold, TextAlignmentOptions.Center);
var b = UIKit.Button(card, $"Разведка {name}", ok && soldiers > 0 ? UIKit.Theme.Blue : UIKit.Theme.Gray, () =>
{
bool covert = spy && CovertMode;
bool started = MarchManager.Instance.TryStartArmyRecon(army.ArmyId, soldiers, lv, covert, out string err);
hint?.Invoke(started ? $"Разведка {name}{(covert ? " (тайно)" : "")}: воины идут догонять армию" : "Разведка не начата: " + err);
Object.Destroy(root);
}, -1, 80, 26);
b.interactable = ok && soldiers > 0;
}
UIKit.Button(card, "Закрыть", UIKit.Theme.Gray, () => Object.Destroy(root), -1, 80, 28);
}
// Окно «Отчёты»: последние отчёты, новые сверху
public static void OpenWindow()
{
var card = UIKit.Modal("ОТЧЁТЫ РАЗВЕДКИ", out GameObject root);
if (!GameServices.TryGet(out IIntelligence intel) || intel.Reports.Count == 0)
{
UIKit.Text(card, "Отчётов пока нет. Нажмите на город, лагерь или замок и отправьте «Разведку II» или «Разведку III».",
26, UIKit.Theme.TextDim, TextAlignmentOptions.Center);
}
else
{
long now = MarchManager.NowMs;
int shown = 0;
for (int i = intel.Reports.Count - 1; i >= 0 && shown < 5; i--, shown++)
UIKit.Text(card, Format(intel.Reports[i], now), 24, UIKit.Theme.Text, TextAlignmentOptions.Left);
if (intel.Reports.Count > 5)
UIKit.Text(card, $"И ещё {intel.Reports.Count - 5} более старых", 22, UIKit.Theme.TextDim, TextAlignmentOptions.Center);
}
UIKit.Button(card, "Закрыть", UIKit.Theme.Gray, () => Object.Destroy(root), -1, 80, 28);
}
}