Weather: WindDirection showed raw int (0-7), now translates to N/NE/E. Added scene type, time-of-day, humidity, pressure, scene dimensions to environment section. Report enrichment: threat section adds wingspan; deployment section shows platform type/position/ammo/munitions and detection equipment/control zones; cloud dispersal config section; event timeline includes coordinates; timeline section adds first/last launch, group spread, summary stats. Bug fix: DroneDestroyed event fired multiple times per tick when a drone was hit by multiple clouds simultaneously (was 2, now 1). Added break after destroy in cloud loop. Tests: 191 pass.
315 lines
16 KiB
C#
315 lines
16 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using CounterDrone.Core.Models;
|
||
using CounterDrone.Core.Simulation;
|
||
|
||
namespace CounterDrone.Core.Services
|
||
{
|
||
public class ReportGenerator
|
||
{
|
||
public string Generate(TaskFullConfig config, List<SimEvent> events,
|
||
Simulation.DroneStatus finalDroneStatus, float simulationDuration)
|
||
{
|
||
var sb = new StringBuilder();
|
||
var platforms = config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform).ToList();
|
||
var target = config.Targets.FirstOrDefault();
|
||
var scene = config.Scene;
|
||
var destroyed = events.Count(e => e.Type == SimEventType.DroneDestroyed);
|
||
var reached = events.Count(e => e.Type == SimEventType.DroneReachedTarget);
|
||
var intruded = events.Count(e => e.Type == SimEventType.ZoneIntruded);
|
||
var totalDrones = config.Targets.Sum(t => t.Quantity);
|
||
var result = DetermineInterceptResult(destroyed, reached + intruded, totalDrones);
|
||
|
||
sb.AppendLine("# 仿真评估报告");
|
||
sb.AppendLine();
|
||
sb.AppendLine($"| 项目 | 值 |");
|
||
sb.AppendLine($"|------|-----|");
|
||
sb.AppendLine($"| 任务名称 | {config.Task.Name} |");
|
||
sb.AppendLine($"| 任务编号 | {config.Task.TaskNumber} |");
|
||
sb.AppendLine($"| 仿真耗时 | {simulationDuration:F1} 秒 |");
|
||
sb.AppendLine($"| 气溶胶类型 | {TranslateAerosol((AerosolType)config.Cloud.AerosolType)} |");
|
||
sb.AppendLine();
|
||
|
||
// === 一、仿真结果 ===
|
||
sb.AppendLine("## 一、仿真结果");
|
||
sb.AppendLine();
|
||
sb.AppendLine($"| 统计项 | 值 |");
|
||
sb.AppendLine($"|--------|-----|");
|
||
sb.AppendLine($"| 无人机总数 | {totalDrones} |");
|
||
sb.AppendLine($"| 被摧毁 | {destroyed} |");
|
||
sb.AppendLine($"| 到达目标 | {reached} |");
|
||
sb.AppendLine($"| 侵入管控区 | {intruded} |");
|
||
sb.AppendLine($"| 弹药发射 | {events.Count(e => e.Type == SimEventType.MunitionLaunched)} |");
|
||
sb.AppendLine($"| 云团生成 | {events.Count(e => e.Type == SimEventType.CloudGenerated)} |");
|
||
sb.AppendLine();
|
||
sb.AppendLine($"**对抗结果**:{TranslateResult(result)}");
|
||
sb.AppendLine();
|
||
|
||
// === 二、作战环境 ===
|
||
sb.AppendLine("## 二、作战环境");
|
||
sb.AppendLine();
|
||
sb.AppendLine($"| 参数 | 值 |");
|
||
sb.AppendLine($"|------|-----|");
|
||
sb.AppendLine($"| 场景类型 | {TranslateScene((SceneType)scene.SceneType)} |");
|
||
sb.AppendLine($"| 时间段 | {scene.TimeOfDay} |");
|
||
sb.AppendLine($"| 天气 | {TranslateWeather((WeatherType)scene.WeatherType)} |");
|
||
sb.AppendLine($"| 风速 | {scene.WindSpeed:F1} m/s |");
|
||
sb.AppendLine($"| 风向 | {TranslateWindDirection((WindDirection)scene.WindDirection)} |");
|
||
sb.AppendLine($"| 温度 | {scene.Temperature:F1}°C |");
|
||
sb.AppendLine($"| 湿度 | {scene.Humidity:F0}%RH |");
|
||
sb.AppendLine($"| 气压 | {scene.Pressure:F0} hPa |");
|
||
sb.AppendLine($"| 能见度 | {scene.Visibility:F0} m |");
|
||
sb.AppendLine($"| 场景范围 | {scene.SceneWidth:F0} × {scene.SceneLength:F0} × {scene.SceneHeight:F0} m (W×L×H) |");
|
||
sb.AppendLine();
|
||
|
||
// === 三、威胁目标 ===
|
||
sb.AppendLine("## 三、威胁目标");
|
||
sb.AppendLine();
|
||
sb.AppendLine($"| 参数 | 值 |");
|
||
sb.AppendLine($"|------|-----|");
|
||
sb.AppendLine($"| 类型 | {TranslateTarget((TargetType)(target?.TargetType ?? 0))} |");
|
||
sb.AppendLine($"| 动力 | {TranslatePower((PowerType)(target?.PowerType ?? 0))} |");
|
||
sb.AppendLine($"| 数量 | {totalDrones} 架 |");
|
||
sb.AppendLine($"| 翼展 | {target?.Wingspan ?? 0:F1} m |");
|
||
sb.AppendLine($"| 速度 | {target?.TypicalSpeed ?? 0:F0} km/h |");
|
||
sb.AppendLine($"| 高度 | {target?.TypicalAltitude ?? 0:F0} m |");
|
||
sb.AppendLine();
|
||
|
||
// === 四、云团抛撒配置 ===
|
||
sb.AppendLine("## 四、云团抛撒配置");
|
||
sb.AppendLine();
|
||
sb.AppendLine($"| 参数 | 值 |");
|
||
sb.AppendLine($"|------|-----|");
|
||
sb.AppendLine($"| 气溶胶类型 | {TranslateAerosol((AerosolType)config.Cloud.AerosolType)} |");
|
||
sb.AppendLine($"| 抛撒高度 | {config.Cloud.DisperseHeight:F0} m |");
|
||
sb.AppendLine($"| 触发模式 | {TranslateTrigger((TriggerMode)config.Cloud.TriggerMode)} |");
|
||
sb.AppendLine($"| 释放方式 | {TranslateRelease((ReleaseMode)config.Cloud.ReleaseMode)} |");
|
||
sb.AppendLine($"| 扩散方向 | {config.Cloud.SpreadAngle:F0}° |");
|
||
sb.AppendLine($"| 初始规模 | {config.Cloud.InitialScale:F0} m³ |");
|
||
sb.AppendLine($"| 持续时间 | {config.Cloud.Duration:F0} s |");
|
||
sb.AppendLine($"| 释放总量 | {config.Cloud.TotalAmount:F1} kg |");
|
||
sb.AppendLine($"| 配置来源 | {config.Cloud.Source} |");
|
||
if (config.Cloud.Source == "Algorithm" && config.Cloud.EstimatedProbability.HasValue)
|
||
{
|
||
sb.AppendLine($"| **算法推荐** | |");
|
||
sb.AppendLine($"| 推荐弹药数 | {config.Cloud.SalvoRounds} 发 |");
|
||
sb.AppendLine($"| 云团间距 | {config.Cloud.SalvoSpacing:F0} m |");
|
||
sb.AppendLine($"| 拦截概率估计 | {config.Cloud.EstimatedProbability.Value:P0} |");
|
||
sb.AppendLine($"| 抛撒时机 | {config.Cloud.RecommendedTiming:F0}s |");
|
||
}
|
||
sb.AppendLine();
|
||
|
||
// 构建 PlatformId → 火力单元 映射(去重,五和六共用)
|
||
var platToEquip = new Dictionary<string, int>();
|
||
var unitLaunchCounts = new Dictionary<int, int>();
|
||
int platIdx = 1, unitNum = 0;
|
||
for (int ei = 0; ei < config.Equipment.Count; ei++)
|
||
{
|
||
var eq = config.Equipment[ei];
|
||
if (eq.EquipmentRole != (int)EquipmentRole.LaunchPlatform) continue;
|
||
int channels = (eq.GunCount ?? 1) * (eq.ChannelsPerGun ?? 1);
|
||
for (int j = 0; j < eq.Quantity; j++)
|
||
{
|
||
unitNum++;
|
||
for (int c = 0; c < channels; c++)
|
||
platToEquip[$"plat_{platIdx++}"] = unitNum;
|
||
}
|
||
}
|
||
foreach (var e in events.Where(e => e.Type == SimEventType.MunitionLaunched))
|
||
{
|
||
if (platToEquip.TryGetValue(e.SourceId, out var u))
|
||
unitLaunchCounts[u] = unitLaunchCounts.GetValueOrDefault(u, 0) + 1;
|
||
}
|
||
|
||
// === 五、我方部署 ===
|
||
sb.AppendLine("## 五、我方部署");
|
||
sb.AppendLine();
|
||
sb.AppendLine($"### 发射平台");
|
||
sb.AppendLine();
|
||
sb.AppendLine($"| # | 平台类型 | 位置 (X,Y,Z) | 弹药类型 | 挂载量 | 发射数 |");
|
||
sb.AppendLine($"|---|---------|-------------|---------|:------:|:------:|");
|
||
int unitNum2 = 0;
|
||
foreach (var eq in config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
|
||
{
|
||
int channels = (eq.GunCount ?? 1) * (eq.ChannelsPerGun ?? 1);
|
||
for (int j = 0; j < eq.Quantity; j++)
|
||
{
|
||
unitNum2++;
|
||
int fired = unitLaunchCounts.GetValueOrDefault(unitNum2, 0);
|
||
string ptype = ((PlatformType?)(eq.PlatformType ?? 0)) == PlatformType.AirBased ? "空基" : "地基";
|
||
string ammo = eq.AerosolType.HasValue ? TranslateAerosol((AerosolType)eq.AerosolType) : "-";
|
||
sb.AppendLine($"| #{unitNum2} | {ptype} | ({eq.PositionX + j * 50:F0}, {eq.PositionY:F0}, {eq.PositionZ:F0}) | {ammo} | {eq.MunitionCount ?? channels} | {fired} |");
|
||
}
|
||
}
|
||
sb.AppendLine();
|
||
// 探测设备
|
||
var detections = config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.Detection).ToList();
|
||
if (detections.Count > 0)
|
||
{
|
||
sb.AppendLine($"### 探测设备");
|
||
sb.AppendLine();
|
||
sb.AppendLine($"| # | 位置 (X,Y,Z) | 探测半径 | 数量 |");
|
||
sb.AppendLine($"|---|-------------|---------|:----:|");
|
||
int detIdx = 0;
|
||
foreach (var d in detections)
|
||
{
|
||
for (int j = 0; j < d.Quantity; j++)
|
||
sb.AppendLine($"| D{++detIdx} | ({d.PositionX:F0}, {d.PositionY:F0}, {d.PositionZ:F0}) | {(d.DetectionRadius ?? 0):F0} m | 1 |");
|
||
}
|
||
sb.AppendLine();
|
||
}
|
||
// 管控区域
|
||
if (config.ControlZones.Count > 0)
|
||
{
|
||
sb.AppendLine($"### 管控区域");
|
||
sb.AppendLine();
|
||
sb.AppendLine($"| # | 名称 | 高度范围 |");
|
||
sb.AppendLine($"|---|------|---------|");
|
||
int zIdx = 0;
|
||
foreach (var z in config.ControlZones)
|
||
sb.AppendLine($"| Z{++zIdx} | {z.Name} | {(int)z.MinAltitude}-{(int)z.MaxAltitude} m |");
|
||
sb.AppendLine();
|
||
}
|
||
|
||
// === 六、事件时序 ===
|
||
sb.AppendLine("## 六、事件时序");
|
||
sb.AppendLine();
|
||
sb.AppendLine("| 时间(s) | 事件 | 详情 | 火力单元 |");
|
||
sb.AppendLine("|---------|------|------|:------:|");
|
||
int munitionIdx = 0, cloudIdx = 0;
|
||
foreach (var e in events.OrderBy(e => e.OccurredAt))
|
||
{
|
||
string unitCell = e.Type == SimEventType.MunitionLaunched && platToEquip.TryGetValue(e.SourceId, out var u)
|
||
? $"#{u}" : "-";
|
||
// 编号前缀 + 引擎生成的 Description(含坐标等调试信息)
|
||
string prefix = e.Type switch
|
||
{
|
||
SimEventType.MunitionLaunched => $"第 {++munitionIdx} 发",
|
||
SimEventType.CloudGenerated => $"云团 #{++cloudIdx}",
|
||
_ => string.Empty,
|
||
};
|
||
string desc = string.IsNullOrEmpty(prefix) ? e.Description : $"{prefix} · {e.Description}";
|
||
sb.AppendLine($"| {e.OccurredAt:F2} | {TranslateEvent(e.Type)} | {desc} | {unitCell} |");
|
||
}
|
||
sb.AppendLine();
|
||
|
||
// === 七、时间线 ===
|
||
var launches = events.Where(e => e.Type == SimEventType.MunitionLaunched).Select(e => e.OccurredAt).OrderBy(t => t).ToList();
|
||
var clouds = events.Where(e => e.Type == SimEventType.CloudGenerated).Select(e => e.OccurredAt).OrderBy(t => t).ToList();
|
||
var destroys = events.Where(e => e.Type == SimEventType.DroneDestroyed).Select(e => e.OccurredAt).OrderBy(t => t).ToList();
|
||
float launchFirst = launches.DefaultIfEmpty(0).FirstOrDefault();
|
||
float launchLast = launches.DefaultIfEmpty(0).LastOrDefault();
|
||
float cloudFirst = clouds.DefaultIfEmpty(0).FirstOrDefault();
|
||
float destroyFirst = destroys.DefaultIfEmpty(0).FirstOrDefault();
|
||
sb.AppendLine("## 七、时间线");
|
||
sb.AppendLine();
|
||
sb.AppendLine($"| 阶段 | 时间 | 耗时 |");
|
||
sb.AppendLine($"|------|------|------|");
|
||
if (launchFirst > 0)
|
||
{
|
||
sb.AppendLine($"| 首发发射 | {launchFirst:F1}s | - |");
|
||
if (launches.Count > 1)
|
||
sb.AppendLine($"| 末发发射 | {launchLast:F1}s | {launchLast - launchFirst:F1}s(弹群展开) |");
|
||
sb.AppendLine($"| 发射 → 云团生成 | {launchFirst:F1}s → {cloudFirst:F1}s | {cloudFirst - launchFirst:F1}s(弹药飞行) |");
|
||
}
|
||
if (destroyFirst > 0)
|
||
sb.AppendLine($"| 云团生成 → 首个击毁 | {cloudFirst:F1}s → {destroyFirst:F1}s | {destroyFirst - cloudFirst:F1}s(暴露毁伤) |");
|
||
sb.AppendLine($"| 总耗时 | 0 → {simulationDuration:F1}s | {simulationDuration:F1}s |");
|
||
sb.AppendLine();
|
||
// 汇总统计(调试用)
|
||
sb.AppendLine("**统计汇总**:");
|
||
sb.AppendLine($"- 弹药发射 {launches.Count} 发,云团生成 {clouds.Count} 朵,击毁 {destroys.Count} 架");
|
||
if (launchFirst > 0 && launchLast > 0)
|
||
sb.AppendLine($"- 发射窗口 {launchLast - launchFirst:F1}s,弹药飞行 {(cloudFirst - launchFirst):F1}s");
|
||
sb.AppendLine();
|
||
|
||
return sb.ToString();
|
||
}
|
||
|
||
public InterceptResult DetermineInterceptResult(int destroyed, int reachedOrIntruded, int total)
|
||
{
|
||
if (total == 0) return InterceptResult.Failed;
|
||
if (destroyed >= total) return InterceptResult.Success;
|
||
if (destroyed > 0) return InterceptResult.Partial;
|
||
return InterceptResult.Failed;
|
||
}
|
||
|
||
private static string TranslateResult(InterceptResult r) => r switch
|
||
{
|
||
InterceptResult.Success => "✅ 成功拦截",
|
||
InterceptResult.Partial => "⚠️ 部分拦截",
|
||
InterceptResult.Failed => "❌ 拦截失败",
|
||
_ => "未知",
|
||
};
|
||
|
||
private static string TranslateAerosol(AerosolType t) => t switch
|
||
{
|
||
AerosolType.InertGas => "惰性气体(吸入式灭火)",
|
||
AerosolType.ActiveMaterial => "活性材料(爆燃式)",
|
||
AerosolType.ActiveFuel => "活性燃料(吸入式爆炸)",
|
||
_ => t.ToString(),
|
||
};
|
||
|
||
private static string TranslateWeather(WeatherType w) => w switch
|
||
{
|
||
WeatherType.Sunny => "晴天", WeatherType.Overcast => "阴天",
|
||
WeatherType.Fog => "雾天", WeatherType.Rain => "雨天",
|
||
WeatherType.Night => "夜间", _ => w.ToString(),
|
||
};
|
||
|
||
private static string TranslateWindDirection(WindDirection d) => d switch
|
||
{
|
||
WindDirection.N => "北 (N)", WindDirection.NE => "东北 (NE)",
|
||
WindDirection.E => "东 (E)", WindDirection.SE => "东南 (SE)",
|
||
WindDirection.S => "南 (S)", WindDirection.SW => "西南 (SW)",
|
||
WindDirection.W => "西 (W)", WindDirection.NW => "西北 (NW)",
|
||
_ => d.ToString(),
|
||
};
|
||
|
||
private static string TranslateScene(SceneType s) => s switch
|
||
{
|
||
SceneType.Urban => "城市", SceneType.Plain => "平原",
|
||
SceneType.Mountain => "山区", SceneType.Coast => "海岸",
|
||
_ => s.ToString(),
|
||
};
|
||
|
||
private static string TranslateTrigger(TriggerMode t) => t switch
|
||
{
|
||
TriggerMode.Time => "时间触发", TriggerMode.Area => "区域触发",
|
||
TriggerMode.Manual => "手动触发", _ => t.ToString(),
|
||
};
|
||
|
||
private static string TranslateTarget(TargetType t) => t switch
|
||
{
|
||
TargetType.Rotor => "旋翼", TargetType.FixedWing => "固定翼",
|
||
TargetType.Electric => "电推", TargetType.Piston => "活塞",
|
||
TargetType.HighSpeed => "高速目标", _ => t.ToString(),
|
||
};
|
||
|
||
private static string TranslatePower(PowerType p) => p switch
|
||
{
|
||
PowerType.Electric => "电推式", PowerType.Piston => "活塞式",
|
||
PowerType.Jet => "喷吸气式", _ => p.ToString(),
|
||
};
|
||
|
||
private static string TranslateRelease(ReleaseMode r) => r switch
|
||
{
|
||
ReleaseMode.Single => "单次齐射", ReleaseMode.Continuous => "连续释放",
|
||
ReleaseMode.Pulse => "脉冲释放", _ => r.ToString(),
|
||
};
|
||
|
||
private static string TranslateEvent(SimEventType t) => t switch
|
||
{
|
||
SimEventType.MunitionLaunched => "🚀 弹药发射",
|
||
SimEventType.CloudGenerated => "☁️ 云团生成",
|
||
SimEventType.DroneDestroyed => "💥 目标击毁",
|
||
SimEventType.DroneReachedTarget => "🏁 抵达目标",
|
||
SimEventType.ZoneIntruded => "🚨 侵入管控区",
|
||
SimEventType.SimulationEnd => "⏹️ 仿真结束",
|
||
_ => t.ToString(),
|
||
};
|
||
}
|
||
}
|