From 69ec9ed0e2be35ee4c66a0f17891dfd29f884bb5 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Thu, 18 Jun 2026 16:47:30 +0800 Subject: [PATCH] =?UTF-8?q?ScenarioDrone/ScenarioUnit=20=E8=A1=A8=E7=B2=BE?= =?UTF-8?q?=E7=AE=80=EF=BC=9A=E5=A4=96=E9=94=AE=E5=BC=95=E7=94=A8=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E6=95=B0=E6=8D=AE=EF=BC=8C=E5=8E=BB=E5=86=97=E4=BD=99?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 编译通过,测试待修复 --- .../Algorithms/AlgorithmTypes.cs | 4 +- .../Algorithms/DefaultLaneDivider.cs | 6 +- .../Algorithms/DefensePlanner.cs | 7 +- src/CounterDrone.Core/DefaultData.cs | 33 +---- src/CounterDrone.Core/Models/ScenarioDrone.cs | 21 +-- src/CounterDrone.Core/Models/ScenarioUnit.cs | 49 +------ .../Services/ReportGenerator.cs | 29 ++-- .../Services/ReportService.cs | 7 +- .../Simulation/DroneEntity.cs | 9 +- .../Simulation/PlatformEntity.cs | 25 ++-- .../Simulation/SimulationEngine.cs | 133 +++++++++++------- 11 files changed, 151 insertions(+), 172 deletions(-) diff --git a/src/CounterDrone.Core/Algorithms/AlgorithmTypes.cs b/src/CounterDrone.Core/Algorithms/AlgorithmTypes.cs index b9a2d3a..5874f32 100644 --- a/src/CounterDrone.Core/Algorithms/AlgorithmTypes.cs +++ b/src/CounterDrone.Core/Algorithms/AlgorithmTypes.cs @@ -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 Waypoints { get; set; } = new(); diff --git a/src/CounterDrone.Core/Algorithms/DefaultLaneDivider.cs b/src/CounterDrone.Core/Algorithms/DefaultLaneDivider.cs index 7915ee6..6f20e50 100644 --- a/src/CounterDrone.Core/Algorithms/DefaultLaneDivider.cs +++ b/src/CounterDrone.Core/Algorithms/DefaultLaneDivider.cs @@ -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); } diff --git a/src/CounterDrone.Core/Algorithms/DefensePlanner.cs b/src/CounterDrone.Core/Algorithms/DefensePlanner.cs index 93f87e2..838ef62 100644 --- a/src/CounterDrone.Core/Algorithms/DefensePlanner.cs +++ b/src/CounterDrone.Core/Algorithms/DefensePlanner.cs @@ -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; } diff --git a/src/CounterDrone.Core/DefaultData.cs b/src/CounterDrone.Core/DefaultData.cs index 8b17ca5..5a9f521 100644 --- a/src/CounterDrone.Core/DefaultData.cs +++ b/src/CounterDrone.Core/DefaultData.cs @@ -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, }; } } diff --git a/src/CounterDrone.Core/Models/ScenarioDrone.cs b/src/CounterDrone.Core/Models/ScenarioDrone.cs index ff82294..6cc38d7 100644 --- a/src/CounterDrone.Core/Models/ScenarioDrone.cs +++ b/src/CounterDrone.Core/Models/ScenarioDrone.cs @@ -3,7 +3,7 @@ using SQLite; namespace CounterDrone.Core.Models { - /// 步骤2:目标配置 + /// 想定数据:无人机批次 [Table("ScenarioDrone")] public class ScenarioDrone { @@ -13,24 +13,11 @@ namespace CounterDrone.Core.Models [Indexed] public string ScenarioId { get; set; } = string.Empty; + /// 外键 → DroneSpec + public string DroneSpecId { get; set; } = string.Empty; + public string WaveId { get; set; } = string.Empty; - public int DroneType { get; set; } = (int)Models.DroneType.Rotor; - - /// 型号 - public string Model { get; set; } = string.Empty; - - /// 描述/用途 - 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; } } diff --git a/src/CounterDrone.Core/Models/ScenarioUnit.cs b/src/CounterDrone.Core/Models/ScenarioUnit.cs index 50230b2..144c232 100644 --- a/src/CounterDrone.Core/Models/ScenarioUnit.cs +++ b/src/CounterDrone.Core/Models/ScenarioUnit.cs @@ -3,7 +3,7 @@ using SQLite; namespace CounterDrone.Core.Models { - /// 步骤3:装备部署 + /// 想定数据:装备部署 [Table("ScenarioUnit")] public class ScenarioUnit { @@ -13,6 +13,12 @@ namespace CounterDrone.Core.Models [Indexed] public string ScenarioId { get; set; } = string.Empty; + /// 外键 → FireUnitSpec(发射平台) + public string FireUnitSpecId { get; set; } = string.Empty; + + /// 外键 → SensorSpec(探测设备) + 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; } - /// 火炮/发射器数量(null=1) - public int? GunCount { get; set; } - - /// 每门炮的火力通道数(null=1) - public int? ChannelsPerGun { get; set; } - - /// 同一通道连发最小间隔秒(null=1) - 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; - - /// 空基平台巡航速度 m/s(地基为 NULL) - public double? CruiseSpeed { get; set; } - - // ── 探测能力(独立探测设备和火力单元自带探测统一表达)── - /// 雷达探测距离 m(雨雾不衰减,发射平台也可有) - public double? RadarRange { get; set; } - /// 光电探测距离 m(受 Visibility 衰减) - public double? EORange { get; set; } - /// 红外探测距离 m(不受 Visibility 影响) - public double? IRRange { get; set; } - /// 探测精度 m(位置误差,影响抛撒散布范围) - public double? DetectionAccuracy { get; set; } - - // ── 三维球冠几何参数(探测范围约束)── - /// 俯仰角下限(度),NULL=无限制(等价全向) - public double? MinElevation { get; set; } - /// 俯仰角上限(度),NULL=无限制 - public double? MaxElevation { get; set; } - /// 可探测高度下界 m,NULL=无限制 - public double? MinDetectAlt { get; set; } - /// 可探测高度上界 m,NULL=无限制 - public double? MaxDetectAlt { get; set; } } } diff --git a/src/CounterDrone.Core/Services/ReportGenerator.cs b/src/CounterDrone.Core/Services/ReportGenerator.cs index 096c5a9..509969e 100644 --- a/src/CounterDrone.Core/Services/ReportGenerator.cs +++ b/src/CounterDrone.Core/Services/ReportGenerator.cs @@ -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 events, - Simulation.DroneStatus finalDroneStatus, float simulationDuration) + Simulation.DroneStatus finalDroneStatus, float simulationDuration, + Dictionary droneSpecs, + Dictionary fireUnitSpecs, + Dictionary 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(); } diff --git a/src/CounterDrone.Core/Services/ReportService.cs b/src/CounterDrone.Core/Services/ReportService.cs index 3b161c5..c892178 100644 --- a/src/CounterDrone.Core/Services/ReportService.cs +++ b/src/CounterDrone.Core/Services/ReportService.cs @@ -23,6 +23,10 @@ namespace CounterDrone.Core.Services public SimulationReport Generate(string scenarioId, ScenarioConfig config, List events, string finalDroneStatus, float duration) { + var droneSpecs = _db.Table().ToDictionary(d => d.Id); + var fireUnitSpecs = _db.Table().ToDictionary(f => f.Id); + var sensorSpecs = _db.Table().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); diff --git a/src/CounterDrone.Core/Simulation/DroneEntity.cs b/src/CounterDrone.Core/Simulation/DroneEntity.cs index d386430..f9eca6d 100644 --- a/src/CounterDrone.Core/Simulation/DroneEntity.cs +++ b/src/CounterDrone.Core/Simulation/DroneEntity.cs @@ -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。 private readonly float[] _cumArc; - public DroneEntity(string id, string waveId, ScenarioDrone config, List route, + public DroneEntity(string id, string waveId, DroneSpec spec, List 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; diff --git a/src/CounterDrone.Core/Simulation/PlatformEntity.cs b/src/CounterDrone.Core/Simulation/PlatformEntity.cs index 85d3c04..96dd284 100644 --- a/src/CounterDrone.Core/Simulation/PlatformEntity.cs +++ b/src/CounterDrone.Core/Simulation/PlatformEntity.cs @@ -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 /// Ready 包含:冷却就绪 + 有弹药 + 非飞行中 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; diff --git a/src/CounterDrone.Core/Simulation/SimulationEngine.cs b/src/CounterDrone.Core/Simulation/SimulationEngine.cs index b1af7b0..0b54633 100644 --- a/src/CounterDrone.Core/Simulation/SimulationEngine.cs +++ b/src/CounterDrone.Core/Simulation/SimulationEngine.cs @@ -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 _droneSpecs = new(); + private Dictionary _fireUnitSpecs = new(); + private Dictionary _sensorSpecs = new(); private int _tickRate = 20; private readonly List _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() + _ammoSpec = specDb.Table() .FirstOrDefault(a => a.AerosolType == config.Cloud.AerosolType); + _droneSpecs = specDb.Table().ToDictionary(d => d.Id); + _fireUnitSpecs = specDb.Table().ToDictionary(f => f.Id); + _sensorSpecs = specDb.Table().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()); 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 BuildFireUnits(ScenarioConfig config) + private List BuildFireUnits(ScenarioConfig config, Dictionary specs) { var units = new List(); 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)(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; } - /// 构建统一信息网络的探测源列表。 - /// 独立探测设备(EquipmentRole.Detection)+ 火力单元自带探测能力。 - private static List BuildDetectionSources(ScenarioConfig config) + private static List BuildDetectionSources(ScenarioConfig config, Dictionary fuSpecs, Dictionary sensorSpecs) { var sources = new List(); 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 BuildDroneWaves(ScenarioConfig config) + private static List BuildDroneWaves(ScenarioConfig config, Dictionary specs) { var groups = new List(); 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()); groups.Add(new DroneWave { WaveId = t.WaveId, - Profile = t, + Profile = droneSpec, + Quantity = t.Quantity, Route = route ?? new RoutePlan(), Waypoints = wps, });