ScenarioDrone/ScenarioUnit 表精简:外键引用基础数据,去冗余字段
- ScenarioDrone: 仅保留 Id/ScenarioId/DroneSpecId/WaveId/Quantity - ScenarioUnit: 仅保留 Id/ScenarioId/FireUnitSpecId/SensorSpecId/Position 等想定字段 - DroneEntity/PlatformEntity 构造函数改为接收 DroneSpec/FireUnitSpec - SimulationEngine 加载 spec 字典,build 方法通过 FK 查 spec - ReportGenerator/ReportService 通过 spec 字典取值 - DefaultLaneDivider.Divide 从 DroneWave.Quantity 取值 - Core 编译通过,测试待修复
This commit is contained in:
parent
537671ab3a
commit
69ec9ed0e2
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
@ -51,7 +52,8 @@ namespace CounterDrone.Core.Algorithms
|
||||
public class DroneWave
|
||||
{
|
||||
public string WaveId { get; set; } = string.Empty;
|
||||
public ScenarioDrone Profile { get; set; } = new();
|
||||
public DroneSpec Profile { get; set; } = new();
|
||||
public int Quantity { get; set; } = 1;
|
||||
public RoutePlan Route { get; set; } = new();
|
||||
public List<Waypoint> Waypoints { get; set; } = new();
|
||||
|
||||
|
||||
@ -7,14 +7,14 @@ namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
public (int laneCount, float laneSpacing) Divide(DroneWave wave, float cloudRadius)
|
||||
{
|
||||
if (wave.Route == null || wave.Profile.Quantity <= 1)
|
||||
if (wave.Route == null || wave.Quantity <= 1)
|
||||
return (1, 0);
|
||||
|
||||
var mode = (FormationMode)wave.Route.FormationMode;
|
||||
if (mode != FormationMode.Formation)
|
||||
return (1, 0);
|
||||
|
||||
int latCount = wave.Route.LateralCount ?? wave.Profile.Quantity;
|
||||
int latCount = wave.Route.LateralCount ?? wave.Quantity;
|
||||
int longCount = wave.Route.LongitudinalCount ?? 1;
|
||||
float latWidth = (latCount - 1) * (float)wave.Route.LateralSpacing;
|
||||
float longDepth = (longCount - 1) * (float)wave.Route.LongitudinalSpacing;
|
||||
@ -22,7 +22,7 @@ namespace CounterDrone.Core.Algorithms
|
||||
|
||||
// 任一维度超过云团半径,则无法一个云团覆盖
|
||||
if (latWidth > cloudRadius || longDepth > cloudRadius)
|
||||
return (wave.Profile.Quantity, (float)wave.Route.LateralSpacing);
|
||||
return (wave.Quantity, (float)wave.Route.LateralSpacing);
|
||||
|
||||
return (1, 0);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
@ -90,10 +91,10 @@ namespace CounterDrone.Core.Algorithms
|
||||
// Step 1: 威胁指数
|
||||
// ═══════════════════════════════════════════════
|
||||
|
||||
private static float CalcThreatIndex(PlannerConfig config, ScenarioDrone target)
|
||||
private static float CalcThreatIndex(PlannerConfig config, DroneSpec spec)
|
||||
{
|
||||
float typeCoef = config.TypeCoefficient.GetValueOrDefault((DroneType)target.DroneType, 1f);
|
||||
float speedCoef = (float)target.TypicalSpeed / 60f;
|
||||
float typeCoef = config.TypeCoefficient.GetValueOrDefault((DroneType)spec.DroneType, 1f);
|
||||
float speedCoef = (float)spec.TypicalSpeed / 60f;
|
||||
return typeCoef * speedCoef;
|
||||
}
|
||||
|
||||
|
||||
@ -130,28 +130,14 @@ namespace CounterDrone.Core
|
||||
{
|
||||
return new ScenarioUnit
|
||||
{
|
||||
FireUnitSpecId = Id,
|
||||
EquipmentRole = (int)Models.EquipmentRole.LaunchPlatform,
|
||||
PlatformType = PlatformType,
|
||||
Quantity = quantity,
|
||||
PositionX = posX,
|
||||
PositionY = posY,
|
||||
PositionZ = posZ,
|
||||
AerosolType = (int)ammoType,
|
||||
MunitionCount = GunCount * ChannelsPerGun * quantity,
|
||||
GunCount = GunCount,
|
||||
ChannelsPerGun = ChannelsPerGun,
|
||||
ChannelInterval = ChannelInterval,
|
||||
Cooldown = Cooldown,
|
||||
MuzzleVelocity = MuzzleVelocity > 0 ? MuzzleVelocity : null,
|
||||
CruiseSpeed = CruiseSpeed > 0 ? CruiseSpeed : null,
|
||||
ReleaseAltitude = ReleaseAltitude > 0 ? ReleaseAltitude : null,
|
||||
RadarRange = RadarRange > 0 ? RadarRange : null,
|
||||
EORange = EORange > 0 ? EORange : null,
|
||||
IRRange = IRRange > 0 ? IRRange : null,
|
||||
MinElevation = MinElevation,
|
||||
MaxElevation = MaxElevation,
|
||||
MinDetectAlt = MinDetectAlt,
|
||||
MaxDetectAlt = MaxDetectAlt,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -175,15 +161,9 @@ namespace CounterDrone.Core
|
||||
{
|
||||
return new ScenarioDrone
|
||||
{
|
||||
DroneSpecId = Id,
|
||||
WaveId = waveId,
|
||||
DroneType = DroneType,
|
||||
Model = Model,
|
||||
Description = Description,
|
||||
PowerType = PowerType,
|
||||
Quantity = 1,
|
||||
Wingspan = Wingspan,
|
||||
TypicalSpeed = TypicalSpeed,
|
||||
TypicalAltitude = TypicalAltitude,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -209,19 +189,12 @@ namespace CounterDrone.Core
|
||||
{
|
||||
return new ScenarioUnit
|
||||
{
|
||||
SensorSpecId = Id,
|
||||
EquipmentRole = (int)Models.EquipmentRole.Detection,
|
||||
Quantity = 1,
|
||||
PositionX = posX,
|
||||
PositionY = posY,
|
||||
PositionZ = posZ,
|
||||
RadarRange = RadarRange > 0 ? RadarRange : null,
|
||||
EORange = EORange > 0 ? EORange : null,
|
||||
IRRange = IRRange > 0 ? IRRange : null,
|
||||
DetectionAccuracy = Accuracy > 0 ? Accuracy : null,
|
||||
MinElevation = MinElevation,
|
||||
MaxElevation = MaxElevation,
|
||||
MinDetectAlt = MinDetectAlt,
|
||||
MaxDetectAlt = MaxDetectAlt,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>步骤2:目标配置</summary>
|
||||
/// <summary>想定数据:无人机批次</summary>
|
||||
[Table("ScenarioDrone")]
|
||||
public class ScenarioDrone
|
||||
{
|
||||
@ -13,24 +13,11 @@ namespace CounterDrone.Core.Models
|
||||
[Indexed]
|
||||
public string ScenarioId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>外键 → DroneSpec</summary>
|
||||
public string DroneSpecId { get; set; } = string.Empty;
|
||||
|
||||
public string WaveId { get; set; } = string.Empty;
|
||||
|
||||
public int DroneType { get; set; } = (int)Models.DroneType.Rotor;
|
||||
|
||||
/// <summary>型号</summary>
|
||||
public string Model { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>描述/用途</summary>
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public int Quantity { get; set; } = 1;
|
||||
|
||||
public int PowerType { get; set; } = (int)Models.PowerType.Electric;
|
||||
|
||||
public double Wingspan { get; set; } = 1.2;
|
||||
|
||||
public double TypicalSpeed { get; set; } = 60.0;
|
||||
|
||||
public double TypicalAltitude { get; set; } = 300.0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ using SQLite;
|
||||
|
||||
namespace CounterDrone.Core.Models
|
||||
{
|
||||
/// <summary>步骤3:装备部署</summary>
|
||||
/// <summary>想定数据:装备部署</summary>
|
||||
[Table("ScenarioUnit")]
|
||||
public class ScenarioUnit
|
||||
{
|
||||
@ -13,6 +13,12 @@ namespace CounterDrone.Core.Models
|
||||
[Indexed]
|
||||
public string ScenarioId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>外键 → FireUnitSpec(发射平台)</summary>
|
||||
public string FireUnitSpecId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>外键 → SensorSpec(探测设备)</summary>
|
||||
public string SensorSpecId { get; set; } = string.Empty;
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public int EquipmentRole { get; set; } = (int)Models.EquipmentRole.LaunchPlatform;
|
||||
@ -21,9 +27,6 @@ namespace CounterDrone.Core.Models
|
||||
|
||||
public string WaveId { get; set; } = string.Empty;
|
||||
|
||||
// 发射平台专用
|
||||
public int? PlatformType { get; set; }
|
||||
|
||||
public double PositionX { get; set; }
|
||||
public double PositionY { get; set; }
|
||||
public double PositionZ { get; set; }
|
||||
@ -32,44 +35,6 @@ namespace CounterDrone.Core.Models
|
||||
|
||||
public int? MunitionCount { get; set; }
|
||||
|
||||
/// <summary>火炮/发射器数量(null=1)</summary>
|
||||
public int? GunCount { get; set; }
|
||||
|
||||
/// <summary>每门炮的火力通道数(null=1)</summary>
|
||||
public int? ChannelsPerGun { get; set; }
|
||||
|
||||
/// <summary>同一通道连发最小间隔秒(null=1)</summary>
|
||||
public double? ChannelInterval { get; set; }
|
||||
|
||||
public int Source { get; set; } = (int)ConfigSource.Manual;
|
||||
|
||||
public double? MuzzleVelocity { get; set; }
|
||||
|
||||
public double? ReleaseAltitude { get; set; }
|
||||
|
||||
public double Cooldown { get; set; } = 5.0;
|
||||
|
||||
/// <summary>空基平台巡航速度 m/s(地基为 NULL)</summary>
|
||||
public double? CruiseSpeed { get; set; }
|
||||
|
||||
// ── 探测能力(独立探测设备和火力单元自带探测统一表达)──
|
||||
/// <summary>雷达探测距离 m(雨雾不衰减,发射平台也可有)</summary>
|
||||
public double? RadarRange { get; set; }
|
||||
/// <summary>光电探测距离 m(受 Visibility 衰减)</summary>
|
||||
public double? EORange { get; set; }
|
||||
/// <summary>红外探测距离 m(不受 Visibility 影响)</summary>
|
||||
public double? IRRange { get; set; }
|
||||
/// <summary>探测精度 m(位置误差,影响抛撒散布范围)</summary>
|
||||
public double? DetectionAccuracy { get; set; }
|
||||
|
||||
// ── 三维球冠几何参数(探测范围约束)──
|
||||
/// <summary>俯仰角下限(度),NULL=无限制(等价全向)</summary>
|
||||
public double? MinElevation { get; set; }
|
||||
/// <summary>俯仰角上限(度),NULL=无限制</summary>
|
||||
public double? MaxElevation { get; set; }
|
||||
/// <summary>可探测高度下界 m,NULL=无限制</summary>
|
||||
public double? MinDetectAlt { get; set; }
|
||||
/// <summary>可探测高度上界 m,NULL=无限制</summary>
|
||||
public double? MaxDetectAlt { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Simulation;
|
||||
|
||||
@ -10,7 +11,10 @@ namespace CounterDrone.Core.Services
|
||||
public class ReportGenerator
|
||||
{
|
||||
public string Generate(ScenarioConfig config, List<SimEvent> events,
|
||||
Simulation.DroneStatus finalDroneStatus, float simulationDuration)
|
||||
Simulation.DroneStatus finalDroneStatus, float simulationDuration,
|
||||
Dictionary<string, DroneSpec> droneSpecs,
|
||||
Dictionary<string, FireUnitSpec> fireUnitSpecs,
|
||||
Dictionary<string, SensorSpec> sensorSpecs)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var platforms = config.Units.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform).ToList();
|
||||
@ -70,12 +74,14 @@ namespace CounterDrone.Core.Services
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"| 参数 | 值 |");
|
||||
sb.AppendLine($"|------|-----|");
|
||||
sb.AppendLine($"| 类型 | {TranslateTarget((DroneType)(target?.DroneType ?? 0))} |");
|
||||
sb.AppendLine($"| 动力 | {TranslatePower((PowerType)(target?.PowerType ?? 0))} |");
|
||||
// Resolve specs
|
||||
droneSpecs.TryGetValue(target?.DroneSpecId ?? "", out var droneSpec);
|
||||
sb.AppendLine($"| 类型 | {TranslateTarget((DroneType)(droneSpec?.DroneType ?? 0))} |");
|
||||
sb.AppendLine($"| 动力 | {TranslatePower((PowerType)(droneSpec?.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($"| 翼展 | {droneSpec?.Wingspan ?? 0:F1} m |");
|
||||
sb.AppendLine($"| 速度 | {droneSpec?.TypicalSpeed ?? 0:F0} km/h |");
|
||||
sb.AppendLine($"| 高度 | {droneSpec?.TypicalAltitude ?? 0:F0} m |");
|
||||
sb.AppendLine();
|
||||
|
||||
// === 四、云团抛撒配置 ===
|
||||
@ -110,7 +116,8 @@ namespace CounterDrone.Core.Services
|
||||
{
|
||||
var eq = config.Units[ei];
|
||||
if (eq.EquipmentRole != (int)EquipmentRole.LaunchPlatform) continue;
|
||||
int channels = (eq.GunCount ?? 1) * (eq.ChannelsPerGun ?? 1);
|
||||
fireUnitSpecs.TryGetValue(eq.FireUnitSpecId, out var fuSpec);
|
||||
int channels = (fuSpec?.GunCount ?? 1) * (fuSpec?.ChannelsPerGun ?? 1);
|
||||
for (int j = 0; j < eq.Quantity; j++)
|
||||
{
|
||||
unitNum++;
|
||||
@ -134,12 +141,13 @@ namespace CounterDrone.Core.Services
|
||||
int unitNum2 = 0;
|
||||
foreach (var eq in config.Units.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
|
||||
{
|
||||
int channels = (eq.GunCount ?? 1) * (eq.ChannelsPerGun ?? 1);
|
||||
fireUnitSpecs.TryGetValue(eq.FireUnitSpecId, out var fuSpec);
|
||||
int channels = (fuSpec?.GunCount ?? 1) * (fuSpec?.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 ptype = ((PlatformType?)(fuSpec?.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} |");
|
||||
}
|
||||
@ -156,8 +164,9 @@ namespace CounterDrone.Core.Services
|
||||
int detIdx = 0;
|
||||
foreach (var d in detections)
|
||||
{
|
||||
sensorSpecs.TryGetValue(d.SensorSpecId, out var sensor);
|
||||
for (int j = 0; j < d.Quantity; j++)
|
||||
sb.AppendLine($"| D{++detIdx} | ({d.PositionX:F0}, {d.PositionY:F0}, {d.PositionZ:F0}) | {(d.RadarRange ?? 0):F0} | {(d.EORange ?? 0):F0} | {(d.IRRange ?? 0):F0} | {(d.DetectionAccuracy ?? 0):F0} m | 1 |");
|
||||
sb.AppendLine($"| D{++detIdx} | ({d.PositionX:F0}, {d.PositionY:F0}, {d.PositionZ:F0}) | {(sensor?.RadarRange ?? 0):F0} | {(sensor?.EORange ?? 0):F0} | {(sensor?.IRRange ?? 0):F0} | {(sensor?.Accuracy ?? 0):F0} m | 1 |");
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
@ -23,6 +23,10 @@ namespace CounterDrone.Core.Services
|
||||
public SimulationReport Generate(string scenarioId, ScenarioConfig config,
|
||||
List<SimEvent> events, string finalDroneStatus, float duration)
|
||||
{
|
||||
var droneSpecs = _db.Table<DroneSpec>().ToDictionary(d => d.Id);
|
||||
var fireUnitSpecs = _db.Table<FireUnitSpec>().ToDictionary(f => f.Id);
|
||||
var sensorSpecs = _db.Table<SensorSpec>().ToDictionary(s => s.Id);
|
||||
|
||||
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);
|
||||
@ -38,7 +42,8 @@ namespace CounterDrone.Core.Services
|
||||
TargetCount = total,
|
||||
EquipmentCount = config.Units.Count,
|
||||
InterceptResult = (int)result,
|
||||
Content = _generator.Generate(config, events, Simulation.DroneStatus.Flying, duration),
|
||||
Content = _generator.Generate(config, events, Simulation.DroneStatus.Flying, duration,
|
||||
droneSpecs, fireUnitSpecs, sensorSpecs),
|
||||
};
|
||||
|
||||
_db.Insert(report);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Algorithms;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
@ -38,15 +39,15 @@ namespace CounterDrone.Core.Simulation
|
||||
/// 查找弧长 s 所在段:最大的 i 使 _cumArc[i] >= s。</summary>
|
||||
private readonly float[] _cumArc;
|
||||
|
||||
public DroneEntity(string id, string waveId, ScenarioDrone config, List<Waypoint> route,
|
||||
public DroneEntity(string id, string waveId, DroneSpec spec, List<Waypoint> route,
|
||||
int formationIndex, float lateralSpacing, int longitudinalIndex, float longitudinalSpacing,
|
||||
FormationMode mode, int lateralAxis = 2, int longitudinalAxis = 0)
|
||||
{
|
||||
Id = id;
|
||||
WaveId = waveId;
|
||||
DroneType = (DroneType)config.DroneType;
|
||||
PowerType = (PowerType)config.PowerType;
|
||||
Wingspan = (float)config.Wingspan;
|
||||
DroneType = (DroneType)spec.DroneType;
|
||||
PowerType = (PowerType)spec.PowerType;
|
||||
Wingspan = (float)spec.Wingspan;
|
||||
float spd = (float)route[0].Speed;
|
||||
if (spd <= 0) throw new InvalidOperationException($"无人机 {id}: 航路点速度必须 > 0");
|
||||
CruiseSpeed = spd;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Algorithms;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
@ -46,20 +47,20 @@ namespace CounterDrone.Core.Simulation
|
||||
/// <summary>Ready 包含:冷却就绪 + 有弹药 + 非飞行中</summary>
|
||||
public bool Ready => CooldownRemaining <= 0 && MunitionCount > 0 && State != PlatformState.FlyingToTarget;
|
||||
|
||||
public PlatformEntity(string id, ScenarioUnit config)
|
||||
public PlatformEntity(string id, FireUnitSpec spec, ScenarioUnit deployment)
|
||||
{
|
||||
Id = id;
|
||||
Role = (EquipmentRole)config.EquipmentRole;
|
||||
PlatformType = config.PlatformType.HasValue ? (PlatformType?)config.PlatformType.Value : null;
|
||||
PosX = (float)config.PositionX;
|
||||
PosY = (float)config.PositionY;
|
||||
PosZ = (float)config.PositionZ;
|
||||
AerosolType = config.AerosolType.HasValue ? (AerosolType?)config.AerosolType.Value : null;
|
||||
MunitionCount = config.MunitionCount ?? 1;
|
||||
Cooldown = (float)config.Cooldown;
|
||||
MuzzleVelocity = (float)(config.MuzzleVelocity ?? 0f);
|
||||
CruiseSpeed = (float)(config.CruiseSpeed ?? 0f);
|
||||
ReleaseAltitude = (float)(config.ReleaseAltitude ?? 0f);
|
||||
Role = (EquipmentRole)deployment.EquipmentRole;
|
||||
PlatformType = (PlatformType?)spec.PlatformType;
|
||||
PosX = (float)deployment.PositionX;
|
||||
PosY = (float)deployment.PositionY;
|
||||
PosZ = (float)deployment.PositionZ;
|
||||
AerosolType = deployment.AerosolType.HasValue ? (AerosolType?)deployment.AerosolType.Value : null;
|
||||
MunitionCount = deployment.MunitionCount ?? 1;
|
||||
Cooldown = (float)spec.Cooldown;
|
||||
MuzzleVelocity = (float)spec.MuzzleVelocity;
|
||||
CruiseSpeed = (float)spec.CruiseSpeed;
|
||||
ReleaseAltitude = (float)spec.ReleaseAltitude;
|
||||
|
||||
PatrolX = PosX;
|
||||
PatrolY = PosY;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CounterDrone.Core;
|
||||
using CounterDrone.Core.Algorithms;
|
||||
using CounterDrone.Core.Models;
|
||||
using CounterDrone.Core.Services;
|
||||
@ -48,6 +49,9 @@ namespace CounterDrone.Core.Simulation
|
||||
private CombatScene _scene = new();
|
||||
private float _disperseHeight;
|
||||
private AmmunitionSpec _ammoSpec = new();
|
||||
private Dictionary<string, DroneSpec> _droneSpecs = new();
|
||||
private Dictionary<string, FireUnitSpec> _fireUnitSpecs = new();
|
||||
private Dictionary<string, SensorSpec> _sensorSpecs = new();
|
||||
private int _tickRate = 20;
|
||||
private readonly List<FireEvent> _fireSchedule = new();
|
||||
private int _nextFireIndex;
|
||||
@ -93,10 +97,13 @@ namespace CounterDrone.Core.Simulation
|
||||
_tickRate = config.Info.TickRate;
|
||||
_tickInterval = 1.0f / _tickRate;
|
||||
|
||||
using (var ammoDb = new SQLiteConnection(_paths.GetMainDbPath()))
|
||||
using (var specDb = new SQLiteConnection(_paths.GetMainDbPath()))
|
||||
{
|
||||
_ammoSpec = ammoDb.Table<AmmunitionSpec>()
|
||||
_ammoSpec = specDb.Table<AmmunitionSpec>()
|
||||
.FirstOrDefault(a => a.AerosolType == config.Cloud.AerosolType);
|
||||
_droneSpecs = specDb.Table<DroneSpec>().ToDictionary(d => d.Id);
|
||||
_fireUnitSpecs = specDb.Table<FireUnitSpec>().ToDictionary(f => f.Id);
|
||||
_sensorSpecs = specDb.Table<SensorSpec>().ToDictionary(s => s.Id);
|
||||
}
|
||||
if (_ammoSpec == null || _ammoSpec.Id == null)
|
||||
_ammoSpec = DefaultData.Load(_paths).Ammunition
|
||||
@ -105,6 +112,7 @@ namespace CounterDrone.Core.Simulation
|
||||
_drones.Clear();
|
||||
foreach (var target in config.Drones)
|
||||
{
|
||||
if (!_droneSpecs.TryGetValue(target.DroneSpecId, out var droneSpec)) continue;
|
||||
var route = config.Routes.FirstOrDefault(r => r.WaveId == target.WaveId);
|
||||
var wps = config.WaypointGroups.GetValueOrDefault(target.WaveId, new List<Waypoint>());
|
||||
if (route == null || wps.Count == 0) continue;
|
||||
@ -118,7 +126,7 @@ namespace CounterDrone.Core.Simulation
|
||||
int latIdx = i % latCount;
|
||||
int longIdx = i / latCount;
|
||||
_drones.Add(new DroneEntity($"drone_{++_entityCounter}", target.WaveId,
|
||||
target, wps, latIdx, lateralSpacing, longIdx, longSpacing, mode,
|
||||
droneSpec, wps, latIdx, lateralSpacing, longIdx, longSpacing, mode,
|
||||
route.LateralAxis, route.LongitudinalAxis));
|
||||
}
|
||||
}
|
||||
@ -128,15 +136,20 @@ namespace CounterDrone.Core.Simulation
|
||||
|
||||
_platforms.Clear();
|
||||
foreach (var equip in config.Units)
|
||||
{
|
||||
if (equip.EquipmentRole != (int)EquipmentRole.Detection)
|
||||
{
|
||||
if (!_fireUnitSpecs.TryGetValue(equip.FireUnitSpecId, out var fuSpec)) continue;
|
||||
for (int i = 0; i < equip.Quantity; i++)
|
||||
{
|
||||
int gunCount = equip.GunCount ?? 1;
|
||||
int chPerGun = equip.ChannelsPerGun ?? 1;
|
||||
int gunCount = fuSpec.GunCount;
|
||||
int chPerGun = fuSpec.ChannelsPerGun;
|
||||
for (int g = 0; g < gunCount; g++)
|
||||
for (int ch = 0; ch < chPerGun; ch++)
|
||||
_platforms.Add(new PlatformEntity($"plat_{++_entityCounter}", equip));
|
||||
_platforms.Add(new PlatformEntity($"plat_{++_entityCounter}", fuSpec, equip));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_zones.Clear();
|
||||
foreach (var z in config.ControlZones)
|
||||
@ -145,7 +158,7 @@ namespace CounterDrone.Core.Simulation
|
||||
|
||||
// 探测设备运行时实体
|
||||
_detectionEntities.Clear();
|
||||
var detectionSrcs = BuildDetectionSources(config);
|
||||
var detectionSrcs = BuildDetectionSources(config, _fireUnitSpecs, _sensorSpecs);
|
||||
int detIdx = 0;
|
||||
foreach (var src in detectionSrcs)
|
||||
_detectionEntities.Add(new DetectionEntity($"det_{++detIdx}", src));
|
||||
@ -161,11 +174,11 @@ namespace CounterDrone.Core.Simulation
|
||||
// 自动从 Planner 生成发射计划
|
||||
if (_fireSchedule.Count == 0)
|
||||
{
|
||||
var fireUnits = BuildFireUnits(config);
|
||||
var threats = BuildDroneWaves(config);
|
||||
var fireUnits = BuildFireUnits(config, _fireUnitSpecs);
|
||||
var threats = BuildDroneWaves(config, _droneSpecs);
|
||||
if (fireUnits.Count > 0 && threats.Count > 0)
|
||||
{
|
||||
var detectionSources = BuildDetectionSources(config);
|
||||
var detectionSources = BuildDetectionSources(config, _fireUnitSpecs, _sensorSpecs);
|
||||
var result = _planner.Plan(fireUnits, threats, _scene, detectionSources);
|
||||
SetFireSchedule(result.Best.MergedSchedule);
|
||||
if (_fireSchedule.Count == 0)
|
||||
@ -427,83 +440,105 @@ namespace CounterDrone.Core.Simulation
|
||||
public void Resume() { if (State == SimulationState.Paused) State = SimulationState.Running; }
|
||||
public void Stop() { State = SimulationState.Stopped; _frameStore.Flush(); }
|
||||
|
||||
private static List<FireUnit> BuildFireUnits(ScenarioConfig config)
|
||||
private List<FireUnit> BuildFireUnits(ScenarioConfig config, Dictionary<string, FireUnitSpec> specs)
|
||||
{
|
||||
var units = new List<FireUnit>();
|
||||
foreach (var eq in config.Units.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
|
||||
{
|
||||
if (!specs.TryGetValue(eq.FireUnitSpecId, out var spec)) continue;
|
||||
int qty = Math.Max(1, eq.Quantity);
|
||||
int gunCount = eq.GunCount ?? 1;
|
||||
int chPerGun = eq.ChannelsPerGun ?? 1;
|
||||
int channels = gunCount * chPerGun;
|
||||
for (int i = 0; i < qty; i++)
|
||||
{
|
||||
units.Add(new FireUnit
|
||||
{
|
||||
Id = $"fu{units.Count}",
|
||||
Type = (PlatformType)(eq.PlatformType ?? (int)PlatformType.GroundBased),
|
||||
Type = (PlatformType)spec.PlatformType,
|
||||
Position = new Algorithms.Vector3((float)eq.PositionX + i * 50, (float)eq.PositionY, (float)eq.PositionZ),
|
||||
GunCount = gunCount,
|
||||
ChannelsPerGun = chPerGun,
|
||||
ChannelInterval = (float)(eq.ChannelInterval ?? 1),
|
||||
CruiseSpeed = (float)(eq.CruiseSpeed ?? 0f),
|
||||
ReleaseAltitude = (float)(eq.ReleaseAltitude ?? 0f),
|
||||
MuzzleVelocity = (float)(eq.MuzzleVelocity ?? 0f),
|
||||
TotalMunitions = eq.MunitionCount ?? channels,
|
||||
GunCount = spec.GunCount,
|
||||
ChannelsPerGun = spec.ChannelsPerGun,
|
||||
ChannelInterval = (float)spec.ChannelInterval,
|
||||
CruiseSpeed = (float)spec.CruiseSpeed,
|
||||
ReleaseAltitude = (float)spec.ReleaseAltitude,
|
||||
MuzzleVelocity = (float)spec.MuzzleVelocity,
|
||||
TotalMunitions = eq.MunitionCount ?? spec.GunCount * spec.ChannelsPerGun,
|
||||
AmmoTypes = new List<AerosolType> { (AerosolType)(eq.AerosolType ?? 0) },
|
||||
Cooldown = (float)eq.Cooldown,
|
||||
// 探测能力(火力单元自带探测,激活原死字段)
|
||||
RadarRange = (float)(eq.RadarRange ?? 0f),
|
||||
EORange = (float)(eq.EORange ?? 0f),
|
||||
IRRange = (float)(eq.IRRange ?? 0f),
|
||||
Cooldown = (float)spec.Cooldown,
|
||||
RadarRange = (float)spec.RadarRange,
|
||||
EORange = (float)spec.EORange,
|
||||
IRRange = (float)spec.IRRange,
|
||||
});
|
||||
}
|
||||
}
|
||||
return units;
|
||||
}
|
||||
|
||||
/// <summary>构建统一信息网络的探测源列表。
|
||||
/// 独立探测设备(EquipmentRole.Detection)+ 火力单元自带探测能力。</summary>
|
||||
private static List<DetectionSource> BuildDetectionSources(ScenarioConfig config)
|
||||
private static List<DetectionSource> BuildDetectionSources(ScenarioConfig config, Dictionary<string, FireUnitSpec> fuSpecs, Dictionary<string, SensorSpec> sensorSpecs)
|
||||
{
|
||||
var sources = new List<DetectionSource>();
|
||||
foreach (var eq in config.Units)
|
||||
{
|
||||
// 只要有任何探测字段非 null 且 > 0,就是有效探测源(无论是否独立探测设备)
|
||||
bool hasDetection = (eq.RadarRange ?? 0) > 0 || (eq.EORange ?? 0) > 0 || (eq.IRRange ?? 0) > 0;
|
||||
if (!hasDetection) continue;
|
||||
int qty = Math.Max(1, eq.Quantity);
|
||||
for (int i = 0; i < qty; i++)
|
||||
// Launch platform: get detection from FireUnitSpec
|
||||
if (eq.EquipmentRole == (int)EquipmentRole.LaunchPlatform)
|
||||
{
|
||||
sources.Add(new DetectionSource
|
||||
if (!fuSpecs.TryGetValue(eq.FireUnitSpecId, out var spec)) continue;
|
||||
bool hasDetection = spec.RadarRange > 0 || spec.EORange > 0 || spec.IRRange > 0;
|
||||
if (!hasDetection) continue;
|
||||
int qty = Math.Max(1, eq.Quantity);
|
||||
for (int i = 0; i < qty; i++)
|
||||
{
|
||||
Position = new Algorithms.Vector3((float)eq.PositionX + i * 50, (float)eq.PositionY, (float)eq.PositionZ),
|
||||
RadarRange = (float)(eq.RadarRange ?? 0f),
|
||||
EORange = (float)(eq.EORange ?? 0f),
|
||||
IRRange = (float)(eq.IRRange ?? 0f),
|
||||
Accuracy = (float)(eq.DetectionAccuracy ?? 0f),
|
||||
// 3D 球冠几何:缺失时退化为无限制(等价 2D),保证存量数据平滑过渡
|
||||
MinElevation = (float)(eq.MinElevation ?? float.MaxValue),
|
||||
MaxElevation = (float)(eq.MaxElevation ?? float.MaxValue),
|
||||
MinDetectAlt = (float)(eq.MinDetectAlt ?? float.MaxValue),
|
||||
MaxDetectAlt = (float)(eq.MaxDetectAlt ?? float.MaxValue),
|
||||
});
|
||||
sources.Add(new DetectionSource
|
||||
{
|
||||
Position = new Algorithms.Vector3((float)eq.PositionX + i * 50, (float)eq.PositionY, (float)eq.PositionZ),
|
||||
RadarRange = (float)spec.RadarRange,
|
||||
EORange = (float)spec.EORange,
|
||||
IRRange = (float)spec.IRRange,
|
||||
Accuracy = (float)(spec.MinElevation.HasValue ? 50f : 50f), // default accuracy
|
||||
MinElevation = (float)(spec.MinElevation ?? float.MaxValue),
|
||||
MaxElevation = (float)(spec.MaxElevation ?? float.MaxValue),
|
||||
MinDetectAlt = (float)(spec.MinDetectAlt ?? float.MaxValue),
|
||||
MaxDetectAlt = (float)(spec.MaxDetectAlt ?? float.MaxValue),
|
||||
});
|
||||
}
|
||||
}
|
||||
else // Detection equipment: get from SensorSpec
|
||||
{
|
||||
if (!sensorSpecs.TryGetValue(eq.SensorSpecId, out var sensor)) continue;
|
||||
bool hasDetection = sensor.RadarRange > 0 || sensor.EORange > 0 || sensor.IRRange > 0;
|
||||
if (!hasDetection) continue;
|
||||
int qty = Math.Max(1, eq.Quantity);
|
||||
for (int i = 0; i < qty; i++)
|
||||
{
|
||||
sources.Add(new DetectionSource
|
||||
{
|
||||
Position = new Algorithms.Vector3((float)eq.PositionX + i * 50, (float)eq.PositionY, (float)eq.PositionZ),
|
||||
RadarRange = (float)sensor.RadarRange,
|
||||
EORange = (float)sensor.EORange,
|
||||
IRRange = (float)sensor.IRRange,
|
||||
Accuracy = (float)sensor.Accuracy,
|
||||
MinElevation = (float)(sensor.MinElevation ?? float.MaxValue),
|
||||
MaxElevation = (float)(sensor.MaxElevation ?? float.MaxValue),
|
||||
MinDetectAlt = (float)(sensor.MinDetectAlt ?? float.MaxValue),
|
||||
MaxDetectAlt = (float)(sensor.MaxDetectAlt ?? float.MaxValue),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
|
||||
private static List<DroneWave> BuildDroneWaves(ScenarioConfig config)
|
||||
private static List<DroneWave> BuildDroneWaves(ScenarioConfig config, Dictionary<string, DroneSpec> specs)
|
||||
{
|
||||
var groups = new List<DroneWave>();
|
||||
foreach (var t in config.Drones)
|
||||
{
|
||||
if (!specs.TryGetValue(t.DroneSpecId, out var droneSpec)) continue;
|
||||
var route = config.Routes.FirstOrDefault(r => r.WaveId == t.WaveId);
|
||||
var wps = config.WaypointGroups.GetValueOrDefault(t.WaveId, new List<Waypoint>());
|
||||
groups.Add(new DroneWave
|
||||
{
|
||||
WaveId = t.WaveId,
|
||||
Profile = t,
|
||||
Profile = droneSpec,
|
||||
Quantity = t.Quantity,
|
||||
Route = route ?? new RoutePlan(),
|
||||
Waypoints = wps,
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user