diff --git a/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs b/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs index 0d0a26b..9f07258 100644 --- a/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs +++ b/src/CounterDrone.Core/Algorithms/DefaultDefensePlanner.cs @@ -113,45 +113,64 @@ namespace CounterDrone.Core.Algorithms continue; } - // 选最早可拦截的 - var best = candidates - .OrderBy(c => c.EarliestInterceptTime) - .ThenByDescending(c => c.KillProbability) - .First(); + // 计算总弹药需求 + 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); - var ammo = _ammoCatalog.FirstOrDefault(a => a.AerosolType == (int)best.AmmoType); - int roundsNeeded = CalcRoundsNeeded(threat, ammo, env, best.Unit.Type == PlatformType.AirBased); - int actualRounds = Math.Min(roundsNeeded, remainingMunitions[best.Unit.Id]); + // 从多个候选单元凑弹药,直到满足需求或耗尽 + int roundsCollected = 0; + var assignedUnits = new List<(FireUnit unit, int rounds, List events)>(); - if (actualRounds <= 0) { plan.ThreatsUnengaged++; continue; } - - var fireEvents = GenerateFireEvents(threat, best.Unit, best.AmmoType, - actualRounds, ammo, env); - - plan.Assignments.Add(new UnitAssignment + foreach (var c in candidates.OrderBy(c => c.EarliestInterceptTime) + .ThenByDescending(c => c.KillProbability)) { - FireUnitId = best.Unit.Id, - DroneGroupId = threat.GroupId, - AmmoType = best.AmmoType, - RoundsFired = actualRounds, - FirstFireTime = fireEvents.Count > 0 ? fireEvents[0].FireTime : 0, - FireEvents = fireEvents, - }); + 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.MergedSchedule.AddRange(fireEvents); - remainingMunitions[best.Unit.Id] -= actualRounds; plan.ThreatsEngaged++; } plan.MergedSchedule.Sort((a, b) => a.FireTime.CompareTo(b.FireTime)); - plan.OverallProbability = plan.Assignments.Count > 0 - ? (float)plan.Assignments.Average(a => - { - var t = sortedThreats.First(th => th.GroupId == a.DroneGroupId); - var a2 = _ammoCatalog.FirstOrDefault(s => s.AerosolType == (int)a.AmmoType); - return ComputeInterceptProbability(t, a2, a.RoundsFired, env); - }) - : 0f; + + // 按威胁汇总概率 + 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} 个无方案" : "无威胁被分配拦截方案"; diff --git a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll index 1a4b171..ad033a2 100644 Binary files a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll and b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll differ