diff --git a/src/CounterDrone.Core/DatabaseManager.cs b/src/CounterDrone.Core/DatabaseManager.cs index d277d9a..c03decb 100644 --- a/src/CounterDrone.Core/DatabaseManager.cs +++ b/src/CounterDrone.Core/DatabaseManager.cs @@ -68,6 +68,7 @@ namespace CounterDrone.Core db.CreateTable(); db.CreateTable(); db.CreateTable(); + db.CreateIndex("RoutePlan", new[] { "TaskId", "GroupId" }, true); db.CreateTable(); db.CreateTable(); db.CreateTable(); diff --git a/src/CounterDrone.Core/Models/RoutePlan.cs b/src/CounterDrone.Core/Models/RoutePlan.cs index cf04618..b457dd0 100644 --- a/src/CounterDrone.Core/Models/RoutePlan.cs +++ b/src/CounterDrone.Core/Models/RoutePlan.cs @@ -1,14 +1,19 @@ +using System; using SQLite; namespace CounterDrone.Core.Models { - /// 步骤5:航路规划 + /// 步骤5:航路规划 — 多编队支持:每个(任务,编队)一条航路 [Table("RoutePlan")] public class RoutePlan { [PrimaryKey] + public string Id { get; set; } = Guid.NewGuid().ToString(); + + [Indexed] public string TaskId { get; set; } = string.Empty; + [Indexed] public string GroupId { get; set; } = string.Empty; public int FormationMode { get; set; } = (int)Models.FormationMode.Single; diff --git a/src/CounterDrone.Core/Models/TaskFullConfig.cs b/src/CounterDrone.Core/Models/TaskFullConfig.cs index 7b7d9ec..d4ef3e8 100644 --- a/src/CounterDrone.Core/Models/TaskFullConfig.cs +++ b/src/CounterDrone.Core/Models/TaskFullConfig.cs @@ -11,8 +11,10 @@ namespace CounterDrone.Core.Models public List Targets { get; set; } = new(); public List Equipment { get; set; } = new(); public CloudDispersal Cloud { get; set; } = new(); - public RoutePlan Route { get; set; } = new(); - public List Waypoints { get; set; } = new(); + /// 多编队航路(多批次支持) + public List Routes { get; set; } = new(); + /// 按 GroupId 分组的航路点 + public Dictionary> WaypointGroups { get; set; } = new(); } /// 分页结果 diff --git a/src/CounterDrone.Core/Models/Waypoint.cs b/src/CounterDrone.Core/Models/Waypoint.cs index bcef32d..67ac110 100644 --- a/src/CounterDrone.Core/Models/Waypoint.cs +++ b/src/CounterDrone.Core/Models/Waypoint.cs @@ -13,6 +13,9 @@ namespace CounterDrone.Core.Models [Indexed] public string TaskId { get; set; } = string.Empty; + /// 关联的编队 ID(多编队支持) + public string GroupId { get; set; } = string.Empty; + [NotNull] public int OrderIndex { get; set; } diff --git a/src/CounterDrone.Core/Repository/RoutePlanRepository.cs b/src/CounterDrone.Core/Repository/RoutePlanRepository.cs index a09cf2d..0cf5d1d 100644 --- a/src/CounterDrone.Core/Repository/RoutePlanRepository.cs +++ b/src/CounterDrone.Core/Repository/RoutePlanRepository.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Linq; using CounterDrone.Core.Models; using SQLite; @@ -6,5 +8,16 @@ namespace CounterDrone.Core.Repository public class RoutePlanRepository : BaseRepository { public RoutePlanRepository(SQLiteConnection db) : base(db) { } + + public List GetByTaskId(string taskId) + { + return Db.Table().Where(r => r.TaskId == taskId).ToList(); + } + + public RoutePlan GetByTaskAndGroup(string taskId, string groupId) + { + return Db.Table() + .FirstOrDefault(r => r.TaskId == taskId && r.GroupId == groupId); + } } } diff --git a/src/CounterDrone.Core/Repository/WaypointRepository.cs b/src/CounterDrone.Core/Repository/WaypointRepository.cs index 7ca00cf..81d917e 100644 --- a/src/CounterDrone.Core/Repository/WaypointRepository.cs +++ b/src/CounterDrone.Core/Repository/WaypointRepository.cs @@ -17,6 +17,14 @@ namespace CounterDrone.Core.Repository .ToList(); } + public List GetByTaskAndGroup(string taskId, string groupId) + { + return Db.Table() + .Where(w => w.TaskId == taskId && w.GroupId == groupId) + .OrderBy(w => w.OrderIndex) + .ToList(); + } + public void DeleteByTaskId(string taskId) { var wps = GetByTaskId(taskId); diff --git a/src/CounterDrone.Core/Services/IScenarioService.cs b/src/CounterDrone.Core/Services/IScenarioService.cs index 8257c01..f57b9e5 100644 --- a/src/CounterDrone.Core/Services/IScenarioService.cs +++ b/src/CounterDrone.Core/Services/IScenarioService.cs @@ -16,7 +16,7 @@ namespace CounterDrone.Core.Services void SaveTarget(string taskId, TargetConfig target); void SaveDeployment(string taskId, List equips); void SaveCloudDispersal(string taskId, CloudDispersal cloud); - void SaveRoute(string taskId, RoutePlan route, List waypoints); + void SaveRoute(string taskId, string groupId, RoutePlan route, List waypoints); void UpdateStep(string taskId, int step); } } diff --git a/src/CounterDrone.Core/Services/ScenarioService.cs b/src/CounterDrone.Core/Services/ScenarioService.cs index aaf289b..5a1af41 100644 --- a/src/CounterDrone.Core/Services/ScenarioService.cs +++ b/src/CounterDrone.Core/Services/ScenarioService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using CounterDrone.Core.Models; using CounterDrone.Core.Repository; @@ -98,8 +99,10 @@ namespace CounterDrone.Core.Services Targets = _targetRepo.GetByTaskId(id), Equipment = _equipRepo.GetByTaskId(id), Cloud = _cloudRepo.GetById(id) ?? new CloudDispersal { TaskId = id }, - Route = _routeRepo.GetById(id) ?? new RoutePlan { TaskId = id }, - Waypoints = _waypointRepo.GetByTaskId(id), + Routes = _routeRepo.GetByTaskId(id), + WaypointGroups = _waypointRepo.GetByTaskId(id) + .GroupBy(w => w.GroupId) + .ToDictionary(g => g.Key, g => g.OrderBy(w => w.OrderIndex).ToList()), }; } @@ -166,19 +169,26 @@ namespace CounterDrone.Core.Services TouchTask(taskId); } - public void SaveRoute(string taskId, RoutePlan route, List waypoints) + public void SaveRoute(string taskId, string groupId, RoutePlan route, List waypoints) { route.TaskId = taskId; - var existingRoute = _routeRepo.GetById(taskId); - if (existingRoute != null) + route.GroupId = groupId; + var existing = _routeRepo.GetByTaskAndGroup(taskId, groupId); + if (existing != null) + { + route.Id = existing.Id; _routeRepo.Update(route); + } else _routeRepo.Insert(route); - _waypointRepo.DeleteByTaskId(taskId); + // 删除该编队的旧航路点,插入新的 + var oldWps = _waypointRepo.GetByTaskAndGroup(taskId, groupId); + foreach (var w in oldWps) _waypointRepo.Delete(w.Id); for (int i = 0; i < waypoints.Count; i++) { waypoints[i].TaskId = taskId; + waypoints[i].GroupId = groupId; waypoints[i].OrderIndex = i; if (string.IsNullOrEmpty(waypoints[i].Id)) waypoints[i].Id = Guid.NewGuid().ToString(); diff --git a/src/CounterDrone.Core/Simulation/SimulationEngine.cs b/src/CounterDrone.Core/Simulation/SimulationEngine.cs index 9ea4baf..3c882c2 100644 --- a/src/CounterDrone.Core/Simulation/SimulationEngine.cs +++ b/src/CounterDrone.Core/Simulation/SimulationEngine.cs @@ -86,11 +86,14 @@ namespace CounterDrone.Core.Simulation _drones.Clear(); foreach (var target in config.Targets) { - var mode = (FormationMode)config.Route.FormationMode; - var spacing = (float)config.Route.FormationSpacing; + 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, config.Waypoints, i, spacing, mode)); + target, wps, i, spacing, mode)); } _platforms.Clear(); diff --git a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll index 419da72..fef6097 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 diff --git a/src/Unity/Assets/Scripts/Managers/GroupManager.cs.meta b/src/Unity/Assets/Scripts/Managers/GroupManager.cs.meta new file mode 100644 index 0000000..499ff8c --- /dev/null +++ b/src/Unity/Assets/Scripts/Managers/GroupManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24c080ea3d6cbcf45aa804fd10269e4a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs b/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs index c93342d..bd31ba1 100644 --- a/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs +++ b/src/Unity/Assets/Scripts/Managers/ManagerVerification.cs @@ -36,7 +36,7 @@ namespace CounterDrone.Unity TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston, Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500, }); - scenarioMgr.SaveRoute(taskId, new RoutePlan { FormationMode = (int)FormationMode.Single }, + scenarioMgr.SaveRoute(taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 500, PosZ = 0 }, diff --git a/src/Unity/Assets/Scripts/Managers/ScenarioManager.cs b/src/Unity/Assets/Scripts/Managers/ScenarioManager.cs index 1f2dba0..8231910 100644 --- a/src/Unity/Assets/Scripts/Managers/ScenarioManager.cs +++ b/src/Unity/Assets/Scripts/Managers/ScenarioManager.cs @@ -35,7 +35,7 @@ namespace CounterDrone.Unity public void SaveTarget(string taskId, TargetConfig t) => _service.SaveTarget(taskId, t); public void SaveDeployment(string taskId, List e) => _service.SaveDeployment(taskId, e); public void SaveCloud(string taskId, CloudDispersal c) => _service.SaveCloudDispersal(taskId, c); - public void SaveRoute(string taskId, RoutePlan r, List w) => _service.SaveRoute(taskId, r, w); + public void SaveRoute(string taskId, string groupId, RoutePlan r, List w) => _service.SaveRoute(taskId, groupId, r, w); public void UpdateStep(string taskId, int step) => _service.UpdateStep(taskId, step); [ContextMenu("Verify")] diff --git a/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs b/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs index 8e32ab0..5a2a2b8 100644 --- a/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs +++ b/src/Unity/Assets/Scripts/Managers/SimulationBootstrap.cs @@ -54,7 +54,7 @@ namespace CounterDrone.Unity TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston, Quantity = 1, TypicalSpeed = _droneSpeed, TypicalAltitude = 500, }); - scenario.SaveRoute(taskId, new RoutePlan { FormationMode = (int)FormationMode.Single }, + scenario.SaveRoute(taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = _droneSpeed }, diff --git a/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs b/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs index 7482d10..78ba518 100644 --- a/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs +++ b/test/unit/CounterDrone.Core.Tests/EdgeCaseTests.cs @@ -49,6 +49,7 @@ namespace CounterDrone.Core.Tests _scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 }); _scenario.SaveTarget(_taskId, new TargetConfig { + GroupId = "default", TargetType = (int)TargetType.Piston, Quantity = 1, TypicalSpeed = 100, @@ -56,7 +57,7 @@ namespace CounterDrone.Core.Tests }); _scenario.SaveDeployment(_taskId, new List()); _scenario.SaveCloudDispersal(_taskId, new CloudDispersal()); - _scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single }, + _scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 300, PosZ = 0 }, @@ -91,13 +92,14 @@ namespace CounterDrone.Core.Tests }); _scenario.SaveTarget(_taskId, new TargetConfig { + GroupId = "default", TargetType = (int)TargetType.Piston, Quantity = 1, TypicalSpeed = 600, // 高速对抗强风 }); _scenario.SaveDeployment(_taskId, new List()); _scenario.SaveCloudDispersal(_taskId, new CloudDispersal()); - _scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single }, + _scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 300, PosZ = 0 }, @@ -129,6 +131,7 @@ namespace CounterDrone.Core.Tests _scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 }); _scenario.SaveTarget(_taskId, new TargetConfig { + GroupId = "default", TargetType = (int)TargetType.HighSpeed, Quantity = 1, TypicalSpeed = 500, @@ -137,7 +140,7 @@ namespace CounterDrone.Core.Tests _scenario.SaveCloudDispersal(_taskId, new CloudDispersal()); // 500 km/h, 100km 航程 - _scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single }, + _scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 500, PosZ = 0 }, diff --git a/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs b/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs index c4a0b7f..c8129a3 100644 --- a/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs +++ b/test/unit/CounterDrone.Core.Tests/FullPipelineTests.cs @@ -85,8 +85,8 @@ namespace CounterDrone.Core.Tests { Environment = detail.Scene, Targets = detail.Targets, - Route = detail.Route, - Waypoints = detail.Waypoints, + Route = detail.Routes[0], + Waypoints = detail.WaypointGroups["default"], }; var rec = new DefaultDefenseAdvisor(_ammoCatalog).Recommend(threat); @@ -151,13 +151,14 @@ namespace CounterDrone.Core.Tests _scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 }); _scenario.SaveTarget(_taskId, new TargetConfig { + GroupId = "default", TargetType = (int)TargetType.FixedWing, PowerType = (int)PowerType.Jet, Quantity = 1, TypicalSpeed = 600, TypicalAltitude = 400, }); - _scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single }, + _scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 400, PosZ = 0, Speed = 600 }, @@ -183,13 +184,14 @@ namespace CounterDrone.Core.Tests _scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 }); _scenario.SaveTarget(_taskId, new TargetConfig { + GroupId = "default", TargetType = (int)TargetType.Electric, PowerType = (int)PowerType.Electric, Quantity = 1, TypicalSpeed = 300, TypicalAltitude = 300, }); - _scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single }, + _scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Speed = 300 }, @@ -228,13 +230,14 @@ namespace CounterDrone.Core.Tests }); _scenario.SaveTarget(_taskId, new TargetConfig { + GroupId = "default", TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston, Quantity = 1, TypicalSpeed = 200, TypicalAltitude = 500, }); - _scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single }, + _scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 500, PosZ = 0, Speed = 200 }, @@ -294,13 +297,14 @@ namespace CounterDrone.Core.Tests _scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 }); _scenario.SaveTarget(_taskId, new TargetConfig { + GroupId = "default", TargetType = (int)TargetType.HighSpeed, PowerType = (int)PowerType.Jet, Quantity = 1, TypicalSpeed = 500, TypicalAltitude = 800, }); - _scenario.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single }, + _scenario.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 800, PosZ = 0, Speed = 500 }, diff --git a/test/unit/CounterDrone.Core.Tests/ScenarioServiceTests.cs b/test/unit/CounterDrone.Core.Tests/ScenarioServiceTests.cs index 3becff0..b183481 100644 --- a/test/unit/CounterDrone.Core.Tests/ScenarioServiceTests.cs +++ b/test/unit/CounterDrone.Core.Tests/ScenarioServiceTests.cs @@ -103,6 +103,7 @@ namespace CounterDrone.Core.Tests _service.SaveTarget(task.Id, new TargetConfig { + GroupId = "default", TargetType = (int)TargetType.FixedWing, Quantity = 3, }); @@ -200,6 +201,7 @@ namespace CounterDrone.Core.Tests var task = _service.CreateTask("Target Test", ""); _service.SaveTarget(task.Id, new TargetConfig { + GroupId = "default", TargetType = (int)TargetType.Piston, Quantity = 5, PowerType = (int)PowerType.Piston, @@ -265,14 +267,14 @@ namespace CounterDrone.Core.Tests new Waypoint { PosX = 10000, PosY = 0, PosZ = 100, Altitude = 300, Speed = 80 }, }; - _service.SaveRoute(task.Id, route, waypoints); + _service.SaveRoute(task.Id, "default", route, waypoints); var detail = _service.GetTaskDetail(task.Id); - Assert.Equal((int)FormationMode.Formation, detail.Route.FormationMode); - Assert.Equal(3, detail.Waypoints.Count); - Assert.Equal(0, detail.Waypoints[0].OrderIndex); - Assert.Equal(2, detail.Waypoints[2].OrderIndex); - Assert.Equal(5000.0, detail.Waypoints[1].PosX); + Assert.Equal((int)FormationMode.Formation, detail.Routes[0].FormationMode); + Assert.Equal(3, detail.WaypointGroups["default"].Count); + Assert.Equal(0, detail.WaypointGroups["default"][0].OrderIndex); + Assert.Equal(2, detail.WaypointGroups["default"][2].OrderIndex); + Assert.Equal(5000.0, detail.WaypointGroups["default"][1].PosX); } // ========== Search & Pagination ========== @@ -392,6 +394,7 @@ namespace CounterDrone.Core.Tests // 4. 步骤2:目标配置 _service.SaveTarget(task.Id, new TargetConfig { + GroupId = "default", TargetType = (int)TargetType.HighSpeed, Quantity = 2, PowerType = (int)PowerType.Jet, @@ -433,7 +436,7 @@ namespace CounterDrone.Core.Tests _service.UpdateStep(task.Id, 4); // 7. 步骤5:航路规划 - _service.SaveRoute(task.Id, new RoutePlan + _service.SaveRoute(task.Id, "default", new RoutePlan { FormationMode = (int)FormationMode.Swarm, FormationSpacing = 30.0, @@ -473,9 +476,9 @@ namespace CounterDrone.Core.Tests Assert.Equal(50.0, detail.Cloud.Duration); // 航路 - Assert.Equal((int)FormationMode.Swarm, detail.Route.FormationMode); - Assert.Equal(2, detail.Waypoints.Count); - Assert.Equal(250.0, detail.Waypoints[0].Speed); + Assert.Equal((int)FormationMode.Swarm, detail.Routes[0].FormationMode); + Assert.Equal(2, detail.WaypointGroups["default"].Count); + Assert.Equal(250.0, detail.WaypointGroups["default"][0].Speed); // 9. 搜索验证 var searchResult = _service.SearchTasks("完整流程", null, null, 1, 10); diff --git a/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs b/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs index 741636b..3a734e6 100644 --- a/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs +++ b/test/unit/CounterDrone.Core.Tests/SimulationEngineTests.cs @@ -53,6 +53,7 @@ namespace CounterDrone.Core.Tests _scenarioService.SaveScene(_taskId, new CombatScene { WindSpeed = 0 }); _scenarioService.SaveTarget(_taskId, new TargetConfig { + GroupId = "default", TargetType = (int)TargetType.Piston, Quantity = 1, PowerType = (int)PowerType.Piston, @@ -79,7 +80,7 @@ namespace CounterDrone.Core.Tests PositionZ = 0, PositionMode = (int)PositionMode.AlgorithmRecommended, }); - _scenarioService.SaveRoute(_taskId, new RoutePlan { FormationMode = (int)FormationMode.Single }, + _scenarioService.SaveRoute(_taskId, "default", new RoutePlan { FormationMode = (int)FormationMode.Single }, new List { new Waypoint { PosX = 0, PosY = 300, PosZ = 0, Altitude = 300, Speed = 60 },