using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using CounterDrone.Core.Algorithms; using CounterDrone.Core.Models; using CounterDrone.Core.Services; using SQLite; namespace CounterDrone.Core.Simulation { /// 仿真引擎 — 纯执行器,接收发射计划按时间表执行 public class SimulationEngine { private readonly IScenarioService _scenarioService; private readonly FrameDataStore _frameStore; private readonly IDamageModel _damageModel; private readonly IPathProvider _paths; public SimulationState State { get; private set; } = SimulationState.Idle; public int FrameIndex { get; private set; } public float SimulationTime { get; private set; } public float TimeScale { get; set; } = 1f; private float _tickInterval; public IReadOnlyList Drones => _drones; public IReadOnlyList Clouds => _clouds; public IReadOnlyList Munitions => _munitions; public IReadOnlyList Events => _allEvents; public event Action OnMunitionLaunched; public event Action OnCloudGenerated; public event Action OnDroneDestroyed; public event Action OnDroneReachedTarget; public event Action OnZoneIntruded; public event Action OnSimulationEnded; private List _drones = new(); private List _platforms = new(); private List _munitions = new(); private List _clouds = new(); private List _zones = new(); private CombatScene _scene = new(); private float _disperseHeight; private AmmunitionSpec _ammoSpec = new(); private int _tickRate = 20; private readonly List _fireSchedule = new(); private int _nextFireIndex; private readonly List _allEvents = new(); private System.Text.StringBuilder _hitLog = new(); private SQLiteConnection _frameDb = null!; private string _taskId = string.Empty; private readonly IDefensePlanner _planner; private int _entityCounter; public SimulationEngine(IScenarioService scenarioService, FrameDataStore frameStore, IDamageModel damageModel, IPathProvider paths, IDefensePlanner planner) { _scenarioService = scenarioService; _frameStore = frameStore; _damageModel = damageModel; _paths = paths; _planner = planner; } public void SetFireSchedule(List schedule) { _fireSchedule.Clear(); _fireSchedule.AddRange(schedule.OrderBy(f => f.FireTime)); _nextFireIndex = 0; } public void Initialize(string taskId) { _taskId = taskId; var config = _scenarioService.GetTaskDetail(taskId); if (config == null) throw new InvalidOperationException("Task not found"); _scene = config.Scene; _disperseHeight = (float)config.Cloud.DisperseHeight; _tickRate = config.Task.TickRate; _tickInterval = 1.0f / _tickRate; using (var ammoDb = new SQLiteConnection(_paths.GetMainDbPath())) _ammoSpec = ammoDb.Table() .FirstOrDefault(a => a.AerosolType == config.Cloud.AerosolType) ?? DefaultAmmunition.GetByType((AerosolType)config.Cloud.AerosolType); _drones.Clear(); foreach (var target in config.Targets) { var route = config.Routes.FirstOrDefault(r => r.GroupId == target.GroupId); var wps = config.WaypointGroups.GetValueOrDefault(target.GroupId, new List()); if (route == null || wps.Count == 0) continue; var mode = (FormationMode)route.FormationMode; var lateralSpacing = (float)route.LateralSpacing; var longSpacing = (float)route.LongitudinalSpacing; int latCount = route.LateralCount ?? target.Quantity; int longCount = route.LongitudinalCount ?? 1; for (int i = 0; i < target.Quantity; i++) { int latIdx = i % latCount; int longIdx = i / latCount; _drones.Add(new DroneEntity($"drone_{++_entityCounter}", target.GroupId, target, wps, latIdx, lateralSpacing, longIdx, longSpacing, mode)); } } // 平台 ID 从 1 开始,确保与报告映射一致 _entityCounter = 0; _platforms.Clear(); foreach (var equip in config.Equipment) if (equip.EquipmentRole != (int)EquipmentRole.Detection) for (int i = 0; i < equip.Quantity; i++) { int gunCount = equip.GunCount ?? 1; int chPerGun = equip.ChannelsPerGun ?? 1; for (int g = 0; g < gunCount; g++) for (int ch = 0; ch < chPerGun; ch++) _platforms.Add(new PlatformEntity($"plat_{++_entityCounter}", equip)); } _zones.Clear(); foreach (var z in config.ControlZones) _zones.Add(new ControlZoneEntity(z.Id, z.Name, z.VerticesJson, (float)z.MinAltitude, (float)z.MaxAltitude)); _munitions.Clear(); _clouds.Clear(); _allEvents.Clear(); _hitLog.Clear(); _hitLog.AppendLine("droneX,droneY,cloudX,cloudY,dist,radius,inside"); FrameIndex = 0; SimulationTime = 0; _entityCounter = 0; _nextFireIndex = 0; // 自动从 Planner 生成发射计划 if (_fireSchedule.Count == 0) { var fireUnits = BuildFireUnits(config); var threats = BuildDroneGroups(config); if (fireUnits.Count > 0 && threats.Count > 0) { var result = _planner.Plan(fireUnits, threats, _scene); SetFireSchedule(result.Best.MergedSchedule); // 诊断:输出 Planner 云团预期位置 var lines = new List { "idx,fireTime,tx,ty,tz,mv" }; for (int i = 0; i < _fireSchedule.Count; i++) { var f = _fireSchedule[i]; lines.Add($"{i},{f.FireTime:F3},{f.TargetX:F1},{f.TargetY:F1},{f.TargetZ:F1},{f.MuzzleVelocity:F0}"); } System.IO.File.WriteAllLines(@"C:\Users\Tellme\Desktop\planner_targets.csv", lines); } } // 初始化 cloud dump System.IO.File.WriteAllText(@"C:\Users\Tellme\Desktop\cloud_actual.csv", "m.id,arrivalTime,px,py,pz,cloud.id,tx,ty,tz\n"); _frameDb = _frameStore.CreateOrOpen(_taskId); State = SimulationState.Running; } public SimulationFrameResult Tick(float deltaTime) { if (State != SimulationState.Running) return new SimulationFrameResult { State = State, FrameIndex = FrameIndex, SimulationTime = SimulationTime }; var scaledDt = deltaTime * TimeScale; SimulationTime += scaledDt; FrameIndex++; var frameEvents = new List(); // 1. 按发射计划执行 while (_nextFireIndex < _fireSchedule.Count && _fireSchedule[_nextFireIndex].FireTime <= SimulationTime) { var fe = _fireSchedule[_nextFireIndex++]; var platform = _platforms.Count > fe.PlatformIndex ? _platforms[fe.PlatformIndex] : null; if (platform == null) continue; if (platform.PlatformType == Models.PlatformType.AirBased && platform.Ready) { platform.CommandFlyTo(fe.TargetX, platform.ReleaseAltitude, fe.TargetZ, fe.FireTime, _disperseHeight); } else if (platform.Ready) { // 地基:直接发射 platform.Release(); var m = new MunitionEntity($"mun_{++_entityCounter}", platform.PlatformType ?? Models.PlatformType.GroundBased, platform.AerosolType ?? Models.AerosolType.InertGas, platform.PosX, platform.PosY, platform.PosZ, fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight, fe.FireTime); _munitions.Add(m); OnMunitionLaunched?.Invoke(m); frameEvents.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = fe.FireTime, SourceId = platform.Id, Description = $"计划发射 {m.Id}" }); } } // 1b. 空基平台到达投放点 → 投弹 foreach (var platform in _platforms.Where(p => p.CanRelease)) { var (cvx, cvy, cvz) = platform.CurrentVelocity; platform.Release(); var m = new MunitionEntity($"mun_{++_entityCounter}", Models.PlatformType.AirBased, platform.AerosolType ?? Models.AerosolType.InertGas, platform.PosX, platform.PosY, platform.PosZ, platform.PosX, _disperseHeight, platform.PosZ, 0f, _disperseHeight, platform.ExactReleaseTime, cvx, cvy, cvz); _munitions.Add(m); OnMunitionLaunched?.Invoke(m); frameEvents.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = platform.ExactReleaseTime, SourceId = platform.Id, Description = $"空基投放 {m.Id}" }); } // 2. 弹药飞行 → 云团生成 foreach (var m in _munitions.ToList()) { m.Update(scaledDt); if (m.HasArrived) { var disp = AlgorithmFactory.Create(); disp.Initialize(_ammoSpec, _scene, new Algorithms.Vector3(m.PosX, m.PosY, m.PosZ), m.ArrivalTime); var cloud = new CloudEntity($"cloud_{++_entityCounter}", m.AerosolType, disp, m.ArrivalTime); _clouds.Add(cloud); _munitions.Remove(m); OnCloudGenerated?.Invoke(cloud); frameEvents.Add(new SimEvent { Type = SimEventType.CloudGenerated, OccurredAt = m.ArrivalTime, Description = "云团生成" }); System.IO.File.AppendAllText(@"C:\Users\Tellme\Desktop\cloud_actual.csv", $"{m.Id},{m.ArrivalTime:F3},{m.PosX:F1},{m.PosY:F1},{m.PosZ:F1},{cloud.Id},{m.TargetX:F1},{m.TargetY:F1},{m.TargetZ:F1}\n"); } } // 3. 云团演化 foreach (var c in _clouds.ToList()) { c.Tick(scaledDt, (float)_scene.WindSpeed, (WindDirection)_scene.WindDirection); if (c.IsDissipated) _clouds.Remove(c); } // 4. 毁伤判定 foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying)) { foreach (var cloud in _clouds) { var dx = drone.PosX - cloud.Dispersion.Center.X; var dy = drone.PosY - cloud.Dispersion.Center.Y; var dz = drone.PosZ - cloud.Dispersion.Center.Z; var dist = (float)System.Math.Sqrt(dx * dx + dy * dy + dz * dz); bool inside = dist <= cloud.Dispersion.EffectiveRadius; _hitLog.AppendLine($"{drone.PosX:F1},{drone.PosY:F1},{cloud.Dispersion.Center.X:F1},{cloud.Dispersion.Center.Y:F1},{dist:F1},{cloud.Dispersion.EffectiveRadius:F1},{inside}"); if (inside && cloud.Dispersion.CoreDensity >= (float)_ammoSpec.EffectiveConcentration) { drone.ExposureTime += scaledDt; var dmg = _damageModel.CalculateDamage(drone.TargetType, drone.PowerType, cloud.AerosolType, cloud.Dispersion.CoreDensity, drone.ExposureTime, scaledDt); drone.ApplyDamage(dmg); if (drone.Status == DroneStatus.Destroyed) { OnDroneDestroyed?.Invoke(drone); frameEvents.Add(new SimEvent { Type = SimEventType.DroneDestroyed, OccurredAt = SimulationTime, TargetId = drone.Id, Description = $"无人机 {drone.Id} 被摧毁" }); } } } } if (_hitLog.Length > 0 && SimulationTime >= 120) System.IO.File.WriteAllText(@"C:\Users\Tellme\Desktop\hit_log.csv", _hitLog.ToString()); // 5. 无人机移动 foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying)) { drone.Update(scaledDt, (float)_scene.WindSpeed, (WindDirection)_scene.WindDirection); if (drone.Status == DroneStatus.ReachedTarget) { OnDroneReachedTarget?.Invoke(drone); frameEvents.Add(new SimEvent { Type = SimEventType.DroneReachedTarget, OccurredAt = SimulationTime, SourceId = drone.Id, Description = "到达攻击目标" }); } } // 6. 平台冷却 foreach (var p in _platforms) p.Update(scaledDt); // 7. 管控区域 foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying)) { foreach (var zone in _zones) { if (zone.ContainsPoint(drone.PosX, drone.PosY, drone.PosZ)) { drone.MarkZoneIntruded(); OnZoneIntruded?.Invoke(drone, zone); frameEvents.Add(new SimEvent { Type = SimEventType.ZoneIntruded, OccurredAt = SimulationTime, SourceId = drone.Id, TargetId = zone.Id, Description = $"侵入 {zone.Name}" }); } } } // 8. 结束判定 if (_drones.All(d => d.Status != DroneStatus.Flying)) { State = SimulationState.Completed; OnSimulationEnded?.Invoke(); frameEvents.Add(new SimEvent { Type = SimEventType.SimulationEnd, OccurredAt = SimulationTime }); } _allEvents.AddRange(frameEvents); var snapshots = CollectSnapshots(); _frameStore.WriteFrame(_frameDb, _taskId, FrameIndex, SimulationTime, snapshots); if (FrameIndex % 50 == 0) _frameDb.Commit(); return new SimulationFrameResult { EntitySnapshots = snapshots, NewEvents = frameEvents, State = State, FrameIndex = FrameIndex, SimulationTime = SimulationTime }; } public void Pause() { if (State == SimulationState.Running) State = SimulationState.Paused; } public void Resume() { if (State == SimulationState.Paused) State = SimulationState.Running; } public void Stop() { State = SimulationState.Stopped; _frameDb?.Close(); } private static List BuildFireUnits(TaskFullConfig config) { var units = new List(); foreach (var eq in config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform)) { 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), 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, AmmoTypes = new List { (AerosolType)(eq.AerosolType ?? 0) }, Cooldown = (float)eq.Cooldown, }); } } return units; } private static List BuildDroneGroups(TaskFullConfig config) { var groups = new List(); foreach (var t in config.Targets) { var route = config.Routes.FirstOrDefault(r => r.GroupId == t.GroupId); var wps = config.WaypointGroups.GetValueOrDefault(t.GroupId, new List()); groups.Add(new DroneGroup { GroupId = t.GroupId, Target = t, Route = route ?? new RoutePlan(), Waypoints = wps, }); } return groups; } private List CollectSnapshots() { var list = new List(); foreach (var d in _drones) list.Add(new EntitySnapshot { EntityId = d.Id, EntityType = Models.EntityType.Drone, PosX = d.PosX, PosY = d.PosY, PosZ = d.PosZ, StateData = JsonSerializer.Serialize(new { damageStage = (int)_damageModel.GetDamageStage(1 - d.Hp), hp = d.Hp }) }); foreach (var p in _platforms.Where(p => p.PlatformType == Models.PlatformType.AirBased)) list.Add(new EntitySnapshot { EntityId = p.Id, EntityType = Models.EntityType.Platform, PosX = p.PosX, PosY = p.PosY, PosZ = p.PosZ, StateData = JsonSerializer.Serialize(new { state = p.State.ToString() }) }); foreach (var c in _clouds) list.Add(new EntitySnapshot { EntityId = c.Id, EntityType = Models.EntityType.Cloud, PosX = c.Dispersion.Center.X, PosY = c.Dispersion.Center.Y, PosZ = c.Dispersion.Center.Z, StateData = JsonSerializer.Serialize(new { radius = c.Dispersion.Radius, opacity = c.Dispersion.Particles.Opacity, phase = c.Dispersion.Phase, elapsed = c.Dispersion.Elapsed }) }); return list; } } }