using System;
using System.Collections.Generic;
using System.Linq;
using CounterDrone.Core.Models;
namespace CounterDrone.Core.Algorithms
{
/// 默认防御规划器 — 五步流水线,全部使用真实物理计算
public class DefaultDefensePlanner : IDefensePlanner
{
private readonly List _ammoCatalog;
public DefaultDefensePlanner(List ammoCatalog)
{
_ammoCatalog = ammoCatalog ?? new List();
}
private static readonly Dictionary MatchTable = new()
{
{ PowerType.Electric, AerosolType.InertGas },
{ PowerType.Piston, AerosolType.InertGas },
{ PowerType.Jet, AerosolType.ActiveMaterial },
};
private static readonly Dictionary TypeCoefficient = new()
{
{ TargetType.HighSpeed, 4f },
{ TargetType.FixedWing, 2f },
{ TargetType.Piston, 2f },
{ TargetType.Rotor, 1f },
{ TargetType.Electric, 1f },
};
// ═══════════════════════════════════════════════
// 五步流水线
// ═══════════════════════════════════════════════
public PlannerResult Plan(List fireUnits, List threats, CombatScene environment)
{
var result = new PlannerResult();
if (threats.Count == 0)
{
result.Best.Summary = "无威胁目标";
return result;
}
if (fireUnits.Count == 0)
{
result.Best.Summary = "无可用火力单元";
result.Best.ThreatsUnengaged = threats.Count;
return result;
}
// Step 1: 威胁排序
foreach (var t in threats)
{
t.ArrivalTime = t.GetArrivalTime();
t.ThreatIndex = CalcThreatIndex(t.Target);
}
var sorted = threats.OrderByDescending(t => t.Priority).ToList();
// Step 2-4: 贪心分配求解
result.Best = Solve(sorted, fireUnits, environment);
// Step 5: 临界方案
result.Critical = DeriveCritical(result.Best);
return result;
}
// ═══════════════════════════════════════════════
// Step 1: 威胁指数
// ═══════════════════════════════════════════════
private static float CalcThreatIndex(TargetConfig target)
{
float typeCoef = TypeCoefficient.GetValueOrDefault((TargetType)target.TargetType, 1f);
float speedCoef = (float)target.TypicalSpeed / 60f;
return typeCoef * speedCoef;
}
// ═══════════════════════════════════════════════
// Step 2: 弹药匹配
// ═══════════════════════════════════════════════
private static AerosolType MatchAmmo(PowerType power)
{
return MatchTable.GetValueOrDefault(power, AerosolType.InertGas);
}
// ═══════════════════════════════════════════════
// Step 3-4: 候选生成 → 贪心分配
// ═══════════════════════════════════════════════
private DefensePlan Solve(List sortedThreats,
List fireUnits, CombatScene env)
{
var plan = new DefensePlan();
var remainingMunitions = new Dictionary();
foreach (var u in fireUnits)
remainingMunitions[u.Id] = u.TotalMunitions;
foreach (var threat in sortedThreats)
{
var available = fireUnits
.Where(u => remainingMunitions.GetValueOrDefault(u.Id, 0) > 0)
.ToList();
var candidates = GenerateCandidates(threat, available, env);
if (candidates.Count == 0)
{
plan.ThreatsUnengaged++;
continue;
}
// 计算总弹药需求
var neededAmmo = MatchAmmo((PowerType)threat.Target.PowerType);
var ammo = _ammoCatalog.FirstOrDefault(a => a.AerosolType == (int)neededAmmo);
int totalRoundsNeeded = CalcRoundsNeeded(threat, ammo, env,
candidates[0].Unit.Type == PlatformType.AirBased);
// 从多个候选单元凑弹药,直到满足需求或耗尽
int roundsCollected = 0;
var assignedUnits = new List<(FireUnit unit, int rounds, List events)>();
foreach (var c in candidates.OrderBy(c => c.EarliestInterceptTime)
.ThenByDescending(c => c.KillProbability))
{
if (roundsCollected >= totalRoundsNeeded) break;
int remaining = remainingMunitions.GetValueOrDefault(c.Unit.Id, 0);
if (remaining <= 0) continue;
int toTake = Math.Min(remaining, totalRoundsNeeded - roundsCollected);
var fireEvents = GenerateFireEvents(threat, c.Unit, c.AmmoType, toTake, ammo, env);
assignedUnits.Add((c.Unit, toTake, fireEvents));
remainingMunitions[c.Unit.Id] -= toTake;
roundsCollected += toTake;
}
if (roundsCollected <= 0) { plan.ThreatsUnengaged++; continue; }
foreach (var (unit, rounds, fireEvents) in assignedUnits)
{
plan.Assignments.Add(new UnitAssignment
{
FireUnitId = unit.Id,
DroneGroupId = threat.GroupId,
AmmoType = neededAmmo,
RoundsFired = rounds,
FirstFireTime = fireEvents.Count > 0 ? fireEvents[0].FireTime : 0,
FireEvents = fireEvents,
});
plan.MergedSchedule.AddRange(fireEvents);
}
plan.ThreatsEngaged++;
}
plan.MergedSchedule.Sort((a, b) => a.FireTime.CompareTo(b.FireTime));
// 按威胁汇总概率
var threatProbs = new List();
foreach (var threat in sortedThreats)
{
var a2 = _ammoCatalog.FirstOrDefault(s =>
s.AerosolType == (int)MatchAmmo((PowerType)threat.Target.PowerType));
int totalRounds = plan.Assignments
.Where(a => a.DroneGroupId == threat.GroupId)
.Sum(a => a.RoundsFired);
if (totalRounds > 0)
threatProbs.Add(ComputeInterceptProbability(threat, a2, totalRounds, env));
}
plan.OverallProbability = threatProbs.Count > 0 ? threatProbs.Average() : 0f;
plan.Summary = plan.ThreatsEngaged > 0
? $"分配 {plan.ThreatsEngaged} 个威胁,{plan.ThreatsUnengaged} 个无方案"
: "无威胁被分配拦截方案";
return plan;
}
// ═══════════════════════════════════════════════
// 候选生成(使用真实物理)
// ═══════════════════════════════════════════════
private List GenerateCandidates(DroneGroup threat,
List availableUnits, CombatScene env)
{
var candidates = new List();
var neededAmmo = MatchAmmo((PowerType)threat.Target.PowerType);
var ammo = _ammoCatalog.FirstOrDefault(a => a.AerosolType == (int)neededAmmo);
if (ammo == null) return candidates;
var ammoEff = ComputeEffectiveRadius(threat, ammo, env);
foreach (var unit in availableUnits)
{
if (!unit.AmmoTypes.Contains(neededAmmo)) continue;
if (unit.Type == PlatformType.AirBased)
{
var c = BuildAirBasedCandidate(threat, unit, neededAmmo, ammo, ammoEff, env);
if (c != null) candidates.Add(c);
}
else
{
var c = BuildGroundBasedCandidate(threat, unit, neededAmmo, ammo, ammoEff, env);
if (c != null) candidates.Add(c);
}
}
return candidates;
}
private InterceptCandidate? BuildGroundBasedCandidate(DroneGroup threat,
FireUnit unit, AerosolType ammoType, AmmunitionSpec ammo,
(float radius, float expTime) ammoEff, CombatScene env)
{
var mid = ThreatMidpoint(threat);
float dx = mid.X - unit.Position.X;
float dz = mid.Z - unit.Position.Z;
float dist = (float)Math.Sqrt(dx * dx + dz * dz);
float maxRange = unit.MuzzleVelocity * unit.MuzzleVelocity / 9.81f;
if (dist > maxRange * 0.8f) return null;
float shellTime = dist / unit.MuzzleVelocity;
float interceptTime = threat.ArrivalTime;
float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
float neededExposure = ammoType == AerosolType.ActiveMaterial ? 2f : 6f;
float actualExposure = ammoEff.radius * 2f / avgSpeed;
float prob = Math.Min(0.95f, actualExposure / neededExposure);
return new InterceptCandidate
{
Unit = unit,
AmmoType = ammoType,
EarliestInterceptTime = interceptTime,
CoverageDuration = ammoEff.radius * 2f,
KillProbability = prob,
FlightTime = shellTime,
ShellFlightTime = shellTime,
};
}
private InterceptCandidate? BuildAirBasedCandidate(DroneGroup threat,
FireUnit unit, AerosolType ammoType, AmmunitionSpec ammo,
(float radius, float expTime) ammoEff, CombatScene env)
{
var mid = ThreatMidpoint(threat);
float releaseAlt = unit.ReleaseAltitude > 0 ? unit.ReleaseAltitude : (float)threat.Target.TypicalAltitude + 500f;
float flightDist = Kinematics.Distance3D(
unit.Position.X, unit.Position.Y, unit.Position.Z,
mid.X, releaseAlt, mid.Z);
float cruiseSpd = unit.CruiseSpeed > 0.1f ? unit.CruiseSpeed : 55f;
float flightTime = flightDist / cruiseSpd;
float fallTime = Kinematics.AirDropFallTime(releaseAlt, (float)threat.Target.TypicalAltitude);
float totalTime = flightTime + fallTime;
float interceptTime = threat.ArrivalTime;
if (totalTime > interceptTime * 1.5f) return null;
float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
float neededExposure = ammoType == AerosolType.ActiveMaterial ? 2f : 6f;
float actualExposure = ammoEff.radius * 2f / avgSpeed;
float prob = Math.Min(0.95f, actualExposure / neededExposure);
return new InterceptCandidate
{
Unit = unit,
AmmoType = ammoType,
EarliestInterceptTime = interceptTime,
CoverageDuration = ammoEff.radius * 2f,
KillProbability = prob,
FlightTime = totalTime,
ShellFlightTime = fallTime,
};
}
// ═══════════════════════════════════════════════
// 弹药计算(使用 AmmunitionSpec + 环境参数)
// ═══════════════════════════════════════════════
private (float effectiveRadius, float expansionTime) ComputeEffectiveRadius(
DroneGroup threat, AmmunitionSpec ammo, CombatScene env)
{
float totalFlightTime = threat.ArrivalTime * 2f;
float halfTime = totalFlightTime / 2f;
float windSpeed = (float)env.WindSpeed;
var weather = (WeatherType)env.WeatherType;
float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
// Phase 1 初始半径(基于爆发药 TNT 当量)
float r0 = 3.3f * (float)Math.Pow(Math.Max(0.01, (float)ammo.BurstChargeKg), 0.32);
float k = (float)ammo.TurbulentExpansionK;
float maxDur = (float)ammo.MaxDuration;
// Phase 2 湍流膨胀后的半径
float phase2Time = Math.Min(halfTime, 30f);
float rPhase2 = r0 + k * (float)Math.Sqrt(Math.Max(0, phase2Time));
float expansionTime = (float)Math.Pow((rPhase2 - r0) / Math.Max(k, 0.01f), 2);
float effectiveR = rPhase2;
// Phase 3 高斯扩散
if (halfTime > 30f)
{
float x = Math.Max(1f, windSpeed * (halfTime - 30f));
var cls = Kinematics.GetStabilityClass(weather, windSpeed);
float sY = Kinematics.SigmaY(cls, x);
float sZ = Kinematics.SigmaZ(cls, x);
float peakC = Kinematics.GaussianPeakConcentration((float)ammo.SourceStrength, sY, sZ);
float threshold = (float)ammo.EffectiveConcentration;
if (peakC > threshold)
{
float sigma = (sY + sZ) / 2f;
effectiveR = rPhase2 + sigma * (float)Math.Sqrt(2f * Math.Log(peakC / threshold));
}
}
return (effectiveR, expansionTime);
}
private int CalcRoundsNeeded(DroneGroup threat, AmmunitionSpec? ammo,
CombatScene env, bool isAirBased)
{
if (ammo == null) return 1;
float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
var (effectiveR, _) = ComputeEffectiveRadius(threat, ammo, env);
float spacing = effectiveR * 1.5f;
var aerosolType = (AerosolType)ammo.AerosolType;
float neededExposure = aerosolType == AerosolType.ActiveMaterial ? 2f : 6f;
float requiredCoverage = neededExposure * avgSpeed;
return Math.Max(1,
(int)Math.Ceiling((requiredCoverage - 2f * effectiveR) / spacing) + 1);
}
private float ComputeInterceptProbability(DroneGroup threat,
AmmunitionSpec? ammo, int rounds, CombatScene env)
{
if (ammo == null) return 0f;
float avgSpeed = (float)threat.Target.TypicalSpeed / 3.6f;
var (effectiveR, _) = ComputeEffectiveRadius(threat, ammo, env);
float spacing = effectiveR * 1.5f;
float actualCoverage = spacing * (rounds - 1) + 2f * effectiveR;
float actualExposure = actualCoverage / avgSpeed;
var aerosolType = (AerosolType)ammo.AerosolType;
float neededExposure = aerosolType == AerosolType.ActiveMaterial ? 2f : 6f;
return Math.Min(0.95f, actualExposure / neededExposure);
}
// ═══════════════════════════════════════════════
// 发射事件生成(真实物理)
// ═══════════════════════════════════════════════
private List GenerateFireEvents(DroneGroup threat, FireUnit unit,
AerosolType ammoType, int rounds, AmmunitionSpec? ammo, CombatScene env)
{
var events = new List();
if (ammo == null) return events;
var mid = ThreatMidpoint(threat);
var (effectiveR, expansionTime) = ComputeEffectiveRadius(threat, ammo, env);
float spacing = effectiveR * 1.5f;
// 推荐时机:威胁到达中点时云团应已形成
float recommendedTiming = threat.ArrivalTime - expansionTime;
if (recommendedTiming < 0.1f) recommendedTiming = 0.1f;
for (int i = 0; i < rounds; i++)
{
float offset = (i - (rounds - 1) / 2f) * spacing;
float tx = mid.X + offset;
float tz = mid.Z;
float fireTime;
if (unit.Type == PlatformType.AirBased)
{
float releaseAlt = unit.ReleaseAltitude > 0 ? unit.ReleaseAltitude : (float)threat.Target.TypicalAltitude + 500f;
float flightDist = Kinematics.Distance3D(
unit.Position.X, unit.Position.Y, unit.Position.Z,
tx, releaseAlt, tz);
float cruiseSpd = unit.CruiseSpeed > 0.1f ? unit.CruiseSpeed : 55f;
float flightTime = flightDist / cruiseSpd;
float fallTime = Kinematics.AirDropFallTime(releaseAlt, (float)threat.Target.TypicalAltitude);
fireTime = recommendedTiming - flightTime - fallTime;
}
else
{
float dx = tx - unit.Position.X;
float dz = tz - unit.Position.Z;
float dist = (float)Math.Sqrt(dx * dx + dz * dz);
float shellTime = dist / (unit.MuzzleVelocity > 0.1f ? unit.MuzzleVelocity : 800f);
fireTime = recommendedTiming - shellTime;
}
if (fireTime < 0.1f) fireTime = 0.1f;
events.Add(new FireEvent
{
FireTime = fireTime,
PlatformIndex = i % rounds,
TargetX = tx,
TargetY = mid.Y,
TargetZ = tz,
MuzzleVelocity = unit.MuzzleVelocity,
});
}
return events;
}
// ═══════════════════════════════════════════════
// Step 5: 临界方案(概率阈值 50%)
// ═══════════════════════════════════════════════
private DefensePlan DeriveCritical(DefensePlan best)
{
var critical = new DefensePlan
{
ThreatsEngaged = best.ThreatsEngaged,
ThreatsUnengaged = best.ThreatsUnengaged,
};
foreach (var assignment in best.Assignments)
{
int rounds = assignment.RoundsFired;
while (rounds > 1)
{
// 简化:弹药减半 → 概率减半
if ((float)rounds / assignment.RoundsFired < 0.5f) break;
rounds--;
}
var reduced = new UnitAssignment
{
FireUnitId = assignment.FireUnitId,
DroneGroupId = assignment.DroneGroupId,
AmmoType = assignment.AmmoType,
RoundsFired = rounds,
FirstFireTime = assignment.FirstFireTime,
FireEvents = assignment.FireEvents.Take(rounds).ToList(),
};
critical.Assignments.Add(reduced);
critical.MergedSchedule.AddRange(reduced.FireEvents);
}
critical.MergedSchedule.Sort((a, b) => a.FireTime.CompareTo(b.FireTime));
critical.OverallProbability = 0.5f;
critical.Summary = $"临界方案:刚好满足 50% 拦截概率";
return critical;
}
// ═══════════════════════════════════════════════
// 辅助
// ═══════════════════════════════════════════════
private static Vector3 ThreatMidpoint(DroneGroup threat)
{
if (threat.Waypoints.Count < 2)
return new Vector3(0, (float)threat.Target.TypicalAltitude, 0);
var s = threat.Waypoints[0];
var e = threat.Waypoints[^1];
return new Vector3(
(float)(s.PosX + e.PosX) / 2f,
(float)(s.PosY + e.PosY) / 2f,
(float)(s.PosZ + e.PosZ) / 2f);
}
}
}