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 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 = null)
{
_scenarioService = scenarioService;
_frameStore = frameStore;
_damageModel = damageModel;
_paths = paths;
_planner = planner ?? new DefaultDefensePlanner(new List());
}
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 spacing = (float)route.FormationSpacing;
for (int i = 0; i < target.Quantity; i++)
_drones.Add(new DroneEntity($"drone_{++_entityCounter}", target.GroupId,
target, wps, i, spacing, mode));
}
_platforms.Clear();
foreach (var equip in config.Equipment)
if (equip.EquipmentRole != (int)EquipmentRole.Detection)
for (int i = 0; i < equip.Quantity; i++)
_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();
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)
{
using (var ammoDb = new SQLiteConnection(_paths.GetMainDbPath()))
{
var catalog = new List(ammoDb.Table().ToList());
if (catalog.Count == 0)
catalog = DefaultAmmunition.GetAll();
var planner = new DefaultDefensePlanner(catalog);
var result = planner.Plan(fireUnits, threats, _scene);
SetFireSchedule(result.Best.MergedSchedule);
}
}
}
_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)
{
// 空基:下令飞往投放点(投放点 = 目标正上方,ReleaseAltitude 高度)
var releaseAlt = platform.ReleaseAltitude > 0 ? platform.ReleaseAltitude : _disperseHeight;
platform.CommandFlyTo(fe.TargetX, releaseAlt, fe.TargetZ);
}
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);
_munitions.Add(m);
OnMunitionLaunched?.Invoke(m);
frameEvents.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = SimulationTime, 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,
cvx, cvy, cvz);
_munitions.Add(m);
OnMunitionLaunched?.Invoke(m);
frameEvents.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = SimulationTime, 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.TargetX, m.TargetY, m.TargetZ), SimulationTime);
var cloud = new CloudEntity($"cloud_{++_entityCounter}", m.AerosolType, disp, SimulationTime);
_clouds.Add(cloud);
_munitions.Remove(m);
OnCloudGenerated?.Invoke(cloud);
frameEvents.Add(new SimEvent { Type = SimEventType.CloudGenerated, OccurredAt = SimulationTime, Description = "云团生成" });
}
}
// 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)
{
if (cloud.ContainsPoint(drone.PosX, drone.PosY, drone.PosZ))
{
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} 被摧毁" });
}
}
}
}
// 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);
_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();
int idx = 0;
foreach (var eq in config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform))
{
int qty = Math.Max(1, eq.Quantity);
for (int i = 0; i < qty; i++)
{
units.Add(new FireUnit
{
Id = $"fu{idx++}",
Type = (PlatformType)(eq.PlatformType ?? (int)PlatformType.GroundBased),
Position = new Algorithms.Vector3((float)eq.PositionX + i * 50, (float)eq.PositionY, (float)eq.PositionZ),
CruiseSpeed = (float)(eq.CruiseSpeed ?? 55f),
ReleaseAltitude = (float)(eq.ReleaseAltitude ?? 1000f),
MuzzleVelocity = (float)(eq.MuzzleVelocity ?? 800f),
TotalMunitions = eq.MunitionCount ?? 1,
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;
}
}
}