From 50a369c722b0b9a3624cc36045539bef9a1dc8c2 Mon Sep 17 00:00:00 2001 From: MHW Date: Sat, 4 Apr 2026 07:07:20 +0800 Subject: [PATCH] =?UTF-8?q?=E7=81=AB=E5=8A=9B=E8=A7=84=E5=88=99=EF=BC=9A?= =?UTF-8?q?=E6=9C=80=E7=BB=88=E7=89=88json=E5=AE=9E=E4=BD=93=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/rule/FireRuleController.java | 16 + .../rule/domain/simplerulepojo/Task.java | 12 + .../rule/domain/simplerulepojo/Weapon.java | 9 + .../dto/FireRuleComponentParamDTO.java | 32 + .../ultimately/dto/FireRuleCoordinateDTO.java | 27 + .../dto/FireRuleInputArithmeticRefDTO.java | 13 + .../ultimately/dto/FireRuleInputDTO.java | 29 + .../dto/FireRuleInputDeviceRefDTO.java | 15 + ...FireRuleInputLauncherConfigurationDTO.java | 17 + .../dto/FireRuleInputMountedWeaponRefDTO.java | 15 + .../dto/FireRuleInputRedCircleStyleDTO.java | 20 + .../dto/FireRuleInputRedCommSlotDTO.java | 37 + .../dto/FireRuleInputRedLeadPayloadDTO.java | 34 + .../dto/FireRuleInputRedLeadPositionDTO.java | 18 + .../dto/FireRuleInputRedMotorizedSlotDTO.java | 33 + .../dto/FireRuleInputRedPlatformSlotDTO.java | 42 + .../dto/FireRuleInputRedSubComponentsDTO.java | 21 + .../dto/FireRuleInputRedWeaponElementDTO.java | 70 + .../dto/FireRuleInputRedWeaponSlotDTO.java | 43 + .../dto/FireRuleInputRedWingItemDTO.java | 17 + .../ultimately/dto/FireRuleTaskInputDTO.java | 59 + .../ultimately/dto/FireRuleTaskWeaponDTO.java | 50 + .../ultimately/dto/FireRuleTrackPointDTO.java | 37 + .../dto/FireRuleWeaponComponentDTO.java | 29 + .../domain/ultimately/dto/package-info.java | 7 + .../vo/FireRuleArithmeticRefVO.java | 22 + .../vo/FireRuleComponentParamVO.java | 32 + .../ultimately/vo/FireRuleCoordinateVO.java | 27 + .../vo/FireRuleCruiseRouteOffsetItemVO.java | 27 + .../ultimately/vo/FireRuleDeviceRefVO.java | 27 + .../ultimately/vo/FireRuleExecuteBlockVO.java | 21 + .../vo/FireRuleExecuteTargetItemVO.java | 63 + .../vo/FireRuleLauncherConfigurationVO.java | 32 + .../vo/FireRuleMissionListItemVO.java | 23 + .../vo/FireRuleMountedWeaponRefVO.java | 24 + .../ultimately/vo/FireRuleOutputVO.java | 42 + .../vo/FireRuleRedCommunicationSlotVO.java | 40 + .../vo/FireRuleRedMotorizedSlotVO.java | 36 + .../vo/FireRuleRedPlatformSlotVO.java | 45 + .../vo/FireRuleRedSubComponentsVO.java | 24 + .../vo/FireRuleRedWeaponEquipmentVO.java | 49 + .../vo/FireRuleRedWeaponSlotVO.java | 46 + .../vo/FireRuleSceneGroupNodeVO.java | 44 + .../vo/FireRuleSceneTaskNodeVO.java | 42 + .../vo/FireRuleSceneTaskPayloadVO.java | 42 + .../vo/FireRuleTargetAttTimeVO.java | 22 + .../ultimately/vo/FireRuleTaskInputVO.java | 64 + .../ultimately/vo/FireRuleTaskWeaponVO.java | 49 + .../ultimately/vo/FireRuleTrackPointVO.java | 37 + .../vo/FireRuleWeaponComponentVO.java | 29 + .../ultimately/vo/FireRuleWingmanDataVO.java | 22 + .../rule/service/FireRuleService.java | 9 + .../service/impl/FireRuleServiceImpl.java | 36 +- .../resources/json/火力规则输入-无注释.json | 349 +++++ .../src/main/resources/json/火力规则输入.json | 474 ++++++ .../src/main/resources/json/火力规则输出.json | 355 +++++ .../src/main/resources/rules/README.md | 313 ++-- .../src/main/resources/rules/fire-rule.drl | 1395 +++++++++++++++-- .../src/main/resources/rules/rule.drl | 0 59 files changed, 4320 insertions(+), 244 deletions(-) create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleComponentParamDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleCoordinateDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputArithmeticRefDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputDeviceRefDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputLauncherConfigurationDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputMountedWeaponRefDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedCircleStyleDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedCommSlotDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedLeadPayloadDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedLeadPositionDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedMotorizedSlotDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedPlatformSlotDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedSubComponentsDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedWeaponElementDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedWeaponSlotDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedWingItemDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleTaskInputDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleTaskWeaponDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleTrackPointDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleWeaponComponentDTO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/package-info.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleArithmeticRefVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleComponentParamVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleCoordinateVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleCruiseRouteOffsetItemVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleDeviceRefVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleExecuteBlockVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleExecuteTargetItemVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleLauncherConfigurationVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleMissionListItemVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleMountedWeaponRefVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleOutputVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedCommunicationSlotVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedMotorizedSlotVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedPlatformSlotVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedSubComponentsVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedWeaponEquipmentVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedWeaponSlotVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleSceneGroupNodeVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleSceneTaskNodeVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleSceneTaskPayloadVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTargetAttTimeVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTaskInputVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTaskWeaponVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTrackPointVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleWeaponComponentVO.java create mode 100644 auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleWingmanDataVO.java create mode 100644 auto-solution-rule/src/main/resources/json/火力规则输入-无注释.json create mode 100644 auto-solution-rule/src/main/resources/json/火力规则输入.json create mode 100644 auto-solution-rule/src/main/resources/json/火力规则输出.json create mode 100644 auto-solution-rule/src/main/resources/rules/rule.drl diff --git a/auto-solution-admin/src/main/java/com/solution/web/controller/rule/FireRuleController.java b/auto-solution-admin/src/main/java/com/solution/web/controller/rule/FireRuleController.java index 8c4eac0..4abef30 100644 --- a/auto-solution-admin/src/main/java/com/solution/web/controller/rule/FireRuleController.java +++ b/auto-solution-admin/src/main/java/com/solution/web/controller/rule/FireRuleController.java @@ -4,6 +4,7 @@ import com.solution.common.core.controller.BaseController; import com.solution.common.core.domain.AjaxResult; import com.solution.rule.domain.FireRuleExecuteDTO; import com.solution.rule.domain.simplerulepojo.Task; +import com.solution.rule.domain.ultimately.dto.FireRuleInputDTO; import com.solution.rule.service.FireRuleService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -95,4 +96,19 @@ public class FireRuleController extends BaseController { public AjaxResult execute(@RequestBody Task task){ return success(ruleService.executeTask(task)); } + + + /** + * 开始执行规则匹配 + * @param task 敌方参数 + * @return + */ + @PostMapping("/rule") + @ApiOperation("开始执行规则匹配") + public AjaxResult rule(@RequestBody FireRuleInputDTO task){ + return success(ruleService.rule(task)); + } + + + } diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/Task.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/Task.java index c2d10c3..d1c68f5 100644 --- a/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/Task.java +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/Task.java @@ -39,4 +39,16 @@ public class Task { //防区经纬高 private List defZoneLocation; + + //阵型类型:TRIANGLE/DIAMOND/LINE/COLUMN/WEDGE + private String formationType; + + //阵型基础间距(米) + private Integer formationSpacingMeters; + + //主僚标准距离(米) + private Integer mainWingDistanceMeters; + + //阵型朝向(0~359) + private Integer formationHeadingDeg; } diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/Weapon.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/Weapon.java index 1956766..14f0de9 100644 --- a/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/Weapon.java +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/Weapon.java @@ -30,4 +30,13 @@ public class Weapon { //目标id private String targetId; + + //编队角色:MAIN=主机,WING=僚机 + private String formationRole; + + //僚机相对主机距离(米) + private Integer wingRelativeDistanceMeters; + + //僚机相对主机方位角(度) + private Integer wingRelativeBearingDeg; } diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleComponentParamDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleComponentParamDTO.java new file mode 100644 index 0000000..55563ee --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleComponentParamDTO.java @@ -0,0 +1,32 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 装备组件参数项 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleComponentParamDTO { + + /** + * 参数唯一标识 + */ + private String uuid; + + /** + * 属性默认值 + */ + private String attDefaultValue; + + /** + * 属性说明 + */ + private String attExplain; + + /** + * 数量 + */ + private Integer number; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleCoordinateDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleCoordinateDTO.java new file mode 100644 index 0000000..94a55c5 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleCoordinateDTO.java @@ -0,0 +1,27 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 经纬高坐标点 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleCoordinateDTO { + + /** + * 经度 + */ + private Double longitude; + + /** + * 纬度 + */ + private Double latitude; + + /** + * 高程(米) + */ + private Double height; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputArithmeticRefDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputArithmeticRefDTO.java new file mode 100644 index 0000000..5bd82c7 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputArithmeticRefDTO.java @@ -0,0 +1,13 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputArithmeticRefDTO { + + private String id; + + private String name; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputDTO.java new file mode 100644 index 0000000..9c30a96 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputDTO.java @@ -0,0 +1,29 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +/** + * 火力规则输入根文档,与 {@code 火力规则输入-无注释.json} 顶层字段对应。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputDTO { + + /** + * 来源场景文件名 + */ + private String sourceFile; + + /** + * 蓝方任务列表 + */ + private List tasks; + + /** + * 红方侧异构条目列表(武器、场景任务节点、装备、指挥所、防区等) + */ + private List redWeapons; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputDeviceRefDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputDeviceRefDTO.java new file mode 100644 index 0000000..baf4286 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputDeviceRefDTO.java @@ -0,0 +1,15 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputDeviceRefDTO { + + private String id; + + private String name; + + private String refId; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputLauncherConfigurationDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputLauncherConfigurationDTO.java new file mode 100644 index 0000000..0b0fc82 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputLauncherConfigurationDTO.java @@ -0,0 +1,17 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputLauncherConfigurationDTO { + + private String classifyName; + + private Integer isMount; + + private FireRuleInputMountedWeaponRefDTO mountedWeapon; + + private Integer number; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputMountedWeaponRefDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputMountedWeaponRefDTO.java new file mode 100644 index 0000000..5d275ac --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputMountedWeaponRefDTO.java @@ -0,0 +1,15 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputMountedWeaponRefDTO { + + @JsonProperty("_id") + private String id; + + private String name; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedCircleStyleDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedCircleStyleDTO.java new file mode 100644 index 0000000..de251a8 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedCircleStyleDTO.java @@ -0,0 +1,20 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 防区圆形条目中 isWing 为对象时的样式字段 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputRedCircleStyleDTO { + + private String outlineWidth; + + private String lineType; + + private String outlineColor; + + private String color; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedCommSlotDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedCommSlotDTO.java new file mode 100644 index 0000000..d4e82b5 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedCommSlotDTO.java @@ -0,0 +1,37 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputRedCommSlotDTO { + + @JsonProperty("ObjectHandle") + private String objectHandle; + + private FireRuleInputArithmeticRefDTO arithmetic; + + private FireRuleInputDeviceRefDTO device; + + private String deviceId; + + private String deviceName; + + private Boolean employLabel; + + private String facilityName; + + private String soleId; + + private Map twiceModified; + + private List zLists; + + @JsonProperty("ParentPlat") + private String parentPlat; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedLeadPayloadDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedLeadPayloadDTO.java new file mode 100644 index 0000000..6acd324 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedLeadPayloadDTO.java @@ -0,0 +1,34 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +/** + * redWeapons 条目中 isLead:可能是长机编队参数,也可能是圆形防区参数(字段并集) + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputRedLeadPayloadDTO { + + private String leader; + + private String side; + + private String supportType; + + private List positions; + + private String drawName; + + private String airspaceType; + + private String selectLonLat; + + private String radius; + + private String height; + + private String extrudedHeight; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedLeadPositionDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedLeadPositionDTO.java new file mode 100644 index 0000000..96be030 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedLeadPositionDTO.java @@ -0,0 +1,18 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 长机/圆形等结构中的坐标点(示例中可能为数值或占位字符串) + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputRedLeadPositionDTO { + + private String longitude; + + private String latitude; + + private String height; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedMotorizedSlotDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedMotorizedSlotDTO.java new file mode 100644 index 0000000..4c3c670 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedMotorizedSlotDTO.java @@ -0,0 +1,33 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputRedMotorizedSlotDTO { + + @JsonProperty("ObjectHandle") + private String objectHandle; + + private FireRuleInputArithmeticRefDTO arithmetic; + + private FireRuleInputDeviceRefDTO device; + + private String deviceId; + + private String deviceName; + + private String soleId; + + @JsonProperty("ParentPlat") + private String parentPlat; + + private Map twiceModified; + + private List zLists; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedPlatformSlotDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedPlatformSlotDTO.java new file mode 100644 index 0000000..afc801f --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedPlatformSlotDTO.java @@ -0,0 +1,42 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputRedPlatformSlotDTO { + + @JsonProperty("ObjectHandle") + private String objectHandle; + + private FireRuleInputArithmeticRefDTO arithmetic; + + private FireRuleInputDeviceRefDTO device; + + private String deviceId; + + private String deviceName; + + private Boolean employLabel; + + private String facilityName; + + private String soleId; + + private Map twiceModified; + + private List zLists; + + @JsonProperty("TrackParamId") + private String trackParamId; + + private List positions; + + @JsonProperty("ParentPlat") + private String parentPlat; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedSubComponentsDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedSubComponentsDTO.java new file mode 100644 index 0000000..bdf951c --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedSubComponentsDTO.java @@ -0,0 +1,21 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputRedSubComponentsDTO { + + private List communication; + + @JsonProperty("motorized_assembly") + private List motorizedAssembly; + + private List platform; + + private List weapon; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedWeaponElementDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedWeaponElementDTO.java new file mode 100644 index 0000000..1f0b3de --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedWeaponElementDTO.java @@ -0,0 +1,70 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 输入 JSON 中 {@code redWeapons} 数组元素并集:武器摘要、红方装备、营指挥所、防区圆形等形态共用此类。 + *

+ * {@code isWing} 在 JSON 中可能为僚机数组或圆形样式对象,故使用 {@link Object} 承接;可结合 + * {@link FireRuleInputRedWingItemDTO}、{@link FireRuleInputRedCircleStyleDTO} 在业务层转型。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputRedWeaponElementDTO { + + private String name; + + @JsonProperty("Name") + private String displayName; + + private String id; + + @JsonProperty("EquipmentID") + private String equipmentId; + + private List components; + + private Object number; + + private String successTargetRad; + + private FireRuleInputRedLeadPayloadDTO isLead; + + private Object isWing; + + /** + * 装备类条目中为 {@code equipment} 等 + */ + private String groupType; + + @JsonProperty("SupportType") + private String supportType; + + @JsonProperty("TroopsDetail") + private Map troopsDetail; + + @JsonProperty("Platform_type") + private String platformType; + + private Boolean isStrikeTarget; + + private Boolean isReconTarget; + + private Boolean isInterferenceTarget; + + private Boolean isDefendImportantPlace; + + @JsonProperty("OwnerForceSide") + private String ownerForceSide; + + @JsonProperty("PlatID") + private String platId; + + @JsonProperty("SubComponents") + private FireRuleInputRedSubComponentsDTO subComponents; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedWeaponSlotDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedWeaponSlotDTO.java new file mode 100644 index 0000000..ebcaf6b --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedWeaponSlotDTO.java @@ -0,0 +1,43 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputRedWeaponSlotDTO { + + @JsonProperty("ObjectHandle") + private String objectHandle; + + private FireRuleInputArithmeticRefDTO arithmetic; + + private String codedQueue; + + private FireRuleInputLauncherConfigurationDTO configuration; + + private FireRuleInputDeviceRefDTO device; + + private String deviceId; + + private String deviceName; + + private Boolean employLabel; + + private String facilityName; + + private String serialNumber; + + private String soleId; + + private Map twiceModified; + + private List zLists; + + @JsonProperty("ParentPlat") + private String parentPlat; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedWingItemDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedWingItemDTO.java new file mode 100644 index 0000000..be303e3 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleInputRedWingItemDTO.java @@ -0,0 +1,17 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleInputRedWingItemDTO { + + private String name; + + private String distance; + + private String angle; + + private String alt; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleTaskInputDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleTaskInputDTO.java new file mode 100644 index 0000000..965faa5 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleTaskInputDTO.java @@ -0,0 +1,59 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +/** + * 单条火力规则任务输入(蓝方多任务时为列表中的一项) + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleTaskInputDTO { + + /** + * 任务唯一标识 + */ + private String id; + + /** + * 任务名称 + */ + private String drawName; + + /** + * 任务类型 + */ + private String dataType; + + /** + * 任务所属阵营 + */ + private String side; + + /** + * 航迹所属实体或阵营标识 + */ + private String trackPointsId; + + /** + * 任务航迹点列表 + */ + private List trackPoints; + + /** + * 任务武器配置列表 + */ + private List taskWeapons; + + /** + * 任务目标 id + */ + private String targetId; + + /** + * 作战区经纬高坐标列表 + */ + private List warZoneLocation; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleTaskWeaponDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleTaskWeaponDTO.java new file mode 100644 index 0000000..2a3c047 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleTaskWeaponDTO.java @@ -0,0 +1,50 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +/** + * 任务挂载的武器配置 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleTaskWeaponDTO { + + /** + * 装备 id(武器唯一标识) + */ + private String equipmentId; + + /** + * 装备名称 + */ + private String name; + + /** + * 装备类型 + */ + private String supportType; + + /** + * 装备组件列表 + */ + private List components; + + /** + * 装备部署位置 + */ + private FireRuleCoordinateDTO coordinate; + + /** + * 武器数量;对应 JSON 键 {@code number}。{@code _comment_number} 为注释键,不映射。 + * 可为数值或业务文案(如「最大载弹量」)。 + */ + private Object number; + + /** + * 该武器打击目标 id + */ + private String targetId; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleTrackPointDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleTrackPointDTO.java new file mode 100644 index 0000000..ef0e346 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleTrackPointDTO.java @@ -0,0 +1,37 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 任务航迹点 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleTrackPointDTO { + + /** + * 航迹点序号 + */ + private Integer index; + + /** + * 经度 + */ + private Double longitude; + + /** + * 纬度 + */ + private Double latitude; + + /** + * 高度 + */ + private Double height; + + /** + * 速度 + */ + private Double speed; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleWeaponComponentDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleWeaponComponentDTO.java new file mode 100644 index 0000000..6830981 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/FireRuleWeaponComponentDTO.java @@ -0,0 +1,29 @@ +package com.solution.rule.domain.ultimately.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +/** + * 任务武器下的设备组件 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleWeaponComponentDTO { + + /** + * 设备 id + */ + private String deviceId; + + /** + * 设备名称 + */ + private String deviceName; + + /** + * 组件参数列表 + */ + private List componentParams; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/package-info.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/package-info.java new file mode 100644 index 0000000..58083f2 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/dto/package-info.java @@ -0,0 +1,7 @@ +/** + * 火力规则输入侧 DTO,与 {@code resources/json/火力规则输入-无注释.json} 对齐。 + *

+ * {@code redWeapons} 数组元素为并集类型 {@link com.solution.rule.domain.ultimately.dto.FireRuleInputRedWeaponElementDTO}, + * 常见形态包括:武器摘要、红方装备(含子组件)、营指挥所、防区圆形等。 + */ +package com.solution.rule.domain.ultimately.dto; diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleArithmeticRefVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleArithmeticRefVO.java new file mode 100644 index 0000000..f467715 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleArithmeticRefVO.java @@ -0,0 +1,22 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 算法引用(id / 名称) + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleArithmeticRefVO { + + /** + * 算法 id + */ + private String id; + + /** + * 算法名称 + */ + private String name; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleComponentParamVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleComponentParamVO.java new file mode 100644 index 0000000..904e4cd --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleComponentParamVO.java @@ -0,0 +1,32 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 装备组件参数项 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleComponentParamVO { + + /** + * 参数唯一标识 + */ + private String uuid; + + /** + * 属性默认值 + */ + private String attDefaultValue; + + /** + * 属性说明 + */ + private String attExplain; + + /** + * 数量或序号 + */ + private Integer number; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleCoordinateVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleCoordinateVO.java new file mode 100644 index 0000000..5fa4486 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleCoordinateVO.java @@ -0,0 +1,27 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 经纬高坐标点 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleCoordinateVO { + + /** + * 经度 + */ + private Double longitude; + + /** + * 纬度 + */ + private Double latitude; + + /** + * 高程(米) + */ + private Double height; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleCruiseRouteOffsetItemVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleCruiseRouteOffsetItemVO.java new file mode 100644 index 0000000..bee5bdd --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleCruiseRouteOffsetItemVO.java @@ -0,0 +1,27 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 巡航航线偏移量项(上下 / 前后 / 左右 三选一与取值) + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleCruiseRouteOffsetItemVO { + + @JsonProperty("UpOrDown") + private Boolean upOrDown; + + @JsonProperty("BeforeOrAfter") + private Boolean beforeOrAfter; + + @JsonProperty("LeftOrRight") + private Boolean leftOrRight; + + /** + * 偏移数值 + */ + private Number value; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleDeviceRefVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleDeviceRefVO.java new file mode 100644 index 0000000..0f4eaba --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleDeviceRefVO.java @@ -0,0 +1,27 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 设备引用 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleDeviceRefVO { + + /** + * 设备 id + */ + private String id; + + /** + * 设备名称 + */ + private String name; + + /** + * 引用 id + */ + private String refId; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleExecuteBlockVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleExecuteBlockVO.java new file mode 100644 index 0000000..fb505c1 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleExecuteBlockVO.java @@ -0,0 +1,21 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +/** + * 任务载荷中的单次执行块(含目标列表与类型) + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleExecuteBlockVO { + + private List targetList; + + /** + * 执行类型,如 assault + */ + private String type; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleExecuteTargetItemVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleExecuteTargetItemVO.java new file mode 100644 index 0000000..b337bc8 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleExecuteTargetItemVO.java @@ -0,0 +1,63 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 打击执行块中单个目标条目 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleExecuteTargetItemVO { + + @JsonProperty("ID") + private String id; + + /** + * 阵列位置 id + */ + private Integer arrayPositionId; + + /** + * 阵列位置 id 备份 + */ + private String arrayPositionIdBak; + + /** + * 到达时间配置 + */ + @JsonProperty("at_time") + private FireRuleTargetAttTimeVO atTime; + + @JsonProperty("times_interval") + private Integer timesInterval; + + private String attackType; + + private String boost; + + private String bootTime; + + private String companion; + + private String cruiseRouteId; + + private List cruiseRouteOffset; + + private String fireType; + + private List strategy; + + private String targetId; + + private String weaponId; + + private String weaponRelease; + + private String weaponType; + + private Integer weaponUseCount; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleLauncherConfigurationVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleLauncherConfigurationVO.java new file mode 100644 index 0000000..946209d --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleLauncherConfigurationVO.java @@ -0,0 +1,32 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 发射架配置块 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleLauncherConfigurationVO { + + /** + * 分类名称 + */ + private String classifyName; + + /** + * 是否挂载 + */ + private Integer isMount; + + /** + * 已挂载武器 + */ + private FireRuleMountedWeaponRefVO mountedWeapon; + + /** + * 数量 + */ + private Integer number; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleMissionListItemVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleMissionListItemVO.java new file mode 100644 index 0000000..6f10e13 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleMissionListItemVO.java @@ -0,0 +1,23 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 任务载荷中的挂载/任务清单项 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleMissionListItemVO { + + private String label; + + /** + * 发射器类型,如 sam + */ + private String launcherType; + + private Integer number; + + private String value; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleMountedWeaponRefVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleMountedWeaponRefVO.java new file mode 100644 index 0000000..9c34cd6 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleMountedWeaponRefVO.java @@ -0,0 +1,24 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 挂载武器简要信息 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleMountedWeaponRefVO { + + /** + * 武器文档 id + */ + @JsonProperty("_id") + private String id; + + /** + * 武器名称 + */ + private String name; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleOutputVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleOutputVO.java new file mode 100644 index 0000000..38071b0 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleOutputVO.java @@ -0,0 +1,42 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 火力规则输出根文档(与 {@code 火力规则输出.json} 字段一一对应)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleOutputVO { + + /** + * 来源场景文件名 + */ + private String sourceFile; + + /** + * 火力规则任务列表(可含防区等输出字段) + */ + private List fireRuleInputs; + + /** + * 场景任务节点列表 + */ + @JsonProperty("Tasks") + private List tasks; + + /** + * 编组列表 + */ + @JsonProperty("Groups") + private List groups; + + /** + * 红方装备列表 + */ + private List redWeapons; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedCommunicationSlotVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedCommunicationSlotVO.java new file mode 100644 index 0000000..fc2b134 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedCommunicationSlotVO.java @@ -0,0 +1,40 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 红方装备子组件:通信类数组元素 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleRedCommunicationSlotVO { + + @JsonProperty("ObjectHandle") + private String objectHandle; + + private FireRuleArithmeticRefVO arithmetic; + + private FireRuleDeviceRefVO device; + + private String deviceId; + + private String deviceName; + + private Boolean employLabel; + + private String facilityName; + + private String soleId; + + private Map twiceModified; + + private List zLists; + + @JsonProperty("ParentPlat") + private String parentPlat; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedMotorizedSlotVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedMotorizedSlotVO.java new file mode 100644 index 0000000..a2ed317 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedMotorizedSlotVO.java @@ -0,0 +1,36 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 红方装备子组件:机动组件数组元素 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleRedMotorizedSlotVO { + + @JsonProperty("ObjectHandle") + private String objectHandle; + + private FireRuleArithmeticRefVO arithmetic; + + private FireRuleDeviceRefVO device; + + private String deviceId; + + private String deviceName; + + private String soleId; + + @JsonProperty("ParentPlat") + private String parentPlat; + + private Map twiceModified; + + private List zLists; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedPlatformSlotVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedPlatformSlotVO.java new file mode 100644 index 0000000..619f94b --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedPlatformSlotVO.java @@ -0,0 +1,45 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 红方装备子组件:平台数组元素 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleRedPlatformSlotVO { + + @JsonProperty("ObjectHandle") + private String objectHandle; + + private FireRuleArithmeticRefVO arithmetic; + + private FireRuleDeviceRefVO device; + + private String deviceId; + + private String deviceName; + + private Boolean employLabel; + + private String facilityName; + + private String soleId; + + private Map twiceModified; + + private List zLists; + + @JsonProperty("TrackParamId") + private String trackParamId; + + private List positions; + + @JsonProperty("ParentPlat") + private String parentPlat; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedSubComponentsVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedSubComponentsVO.java new file mode 100644 index 0000000..268e249 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedSubComponentsVO.java @@ -0,0 +1,24 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 红方装备子组件分组(按 communication / motorized_assembly / platform / weapon) + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleRedSubComponentsVO { + + private List communication; + + @JsonProperty("motorized_assembly") + private List motorizedAssembly; + + private List platform; + + private List weapon; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedWeaponEquipmentVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedWeaponEquipmentVO.java new file mode 100644 index 0000000..47c4548 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedWeaponEquipmentVO.java @@ -0,0 +1,49 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Map; + +/** + * 火力规则输出中 redWeapons 数组的单项(红方装备/平台) + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleRedWeaponEquipmentVO { + + @JsonProperty("SupportType") + private String supportType; + + @JsonProperty("TroopsDetail") + private Map troopsDetail; + + @JsonProperty("Platform_type") + private String platformType; + + private Boolean isStrikeTarget; + + private Boolean isReconTarget; + + private Boolean isInterferenceTarget; + + private Boolean isDefendImportantPlace; + + private String groupType; + + @JsonProperty("EquipmentID") + private String equipmentId; + + @JsonProperty("Name") + private String name; + + @JsonProperty("OwnerForceSide") + private String ownerForceSide; + + @JsonProperty("PlatID") + private String platId; + + @JsonProperty("SubComponents") + private FireRuleRedSubComponentsVO subComponents; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedWeaponSlotVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedWeaponSlotVO.java new file mode 100644 index 0000000..e7cd92a --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleRedWeaponSlotVO.java @@ -0,0 +1,46 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +/** + * 红方装备子组件:武器 / 发射架子项 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleRedWeaponSlotVO { + + @JsonProperty("ObjectHandle") + private String objectHandle; + + private FireRuleArithmeticRefVO arithmetic; + + private String codedQueue; + + private FireRuleLauncherConfigurationVO configuration; + + private FireRuleDeviceRefVO device; + + private String deviceId; + + private String deviceName; + + private Boolean employLabel; + + private String facilityName; + + private String serialNumber; + + private String soleId; + + private Map twiceModified; + + private List zLists; + + @JsonProperty("ParentPlat") + private String parentPlat; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleSceneGroupNodeVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleSceneGroupNodeVO.java new file mode 100644 index 0000000..eeba417 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleSceneGroupNodeVO.java @@ -0,0 +1,44 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +/** + * 火力规则输出中 Groups 数组的单项 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleSceneGroupNodeVO { + + private Number allAngle; + + private String drawName; + + private List editPermission; + + private String groupType; + + private String id; + + private String idKey; + + private Boolean isSelected; + + private Boolean isShow; + + private String leader; + + private String name; + + private String parentId; + + private List permission; + + private Boolean show; + + private Long sort; + + private List wingmanData; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleSceneTaskNodeVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleSceneTaskNodeVO.java new file mode 100644 index 0000000..7de8b12 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleSceneTaskNodeVO.java @@ -0,0 +1,42 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +/** + * 火力规则输出中 Tasks 数组的单项(场景树任务节点) + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleSceneTaskNodeVO { + + private String color; + + private String dataType; + + private String drawName; + + private String groupType; + + private String id; + + private String idKey; + + private Boolean isSelected; + + private String name; + + private String parentId; + + private List permission; + + private Boolean show; + + private String side; + + private Long sort; + + private FireRuleSceneTaskPayloadVO task; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleSceneTaskPayloadVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleSceneTaskPayloadVO.java new file mode 100644 index 0000000..ed3dbec --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleSceneTaskPayloadVO.java @@ -0,0 +1,42 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 场景任务节点中的内层 task 对象 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleSceneTaskPayloadVO { + + @JsonProperty("at_time") + private String atTime; + + private String attackId; + + private String color; + + private String departureAirport; + + private List execute; + + private String landAirport; + + private List missionList; + + private String name; + + private String side; + + private String sideId; + + private Integer speed; + + private String type; + + private String weaponId; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTargetAttTimeVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTargetAttTimeVO.java new file mode 100644 index 0000000..badb157 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTargetAttTimeVO.java @@ -0,0 +1,22 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 目标项中的到达时间开关与取值 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleTargetAttTimeVO { + + /** + * 时间上限是否为真 + */ + private Boolean timeUp; + + /** + * 时间值 + */ + private Number value; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTaskInputVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTaskInputVO.java new file mode 100644 index 0000000..476f0e1 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTaskInputVO.java @@ -0,0 +1,64 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +/** + * 单条火力规则任务输出(相对输入增加防区坐标) + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleTaskInputVO { + + /** + * 任务唯一标识 + */ + private String id; + + /** + * 任务名称 + */ + private String drawName; + + /** + * 任务类型 + */ + private String dataType; + + /** + * 任务所属阵营 + */ + private String side; + + /** + * 航迹所属实体或阵营标识 + */ + private String trackPointsId; + + /** + * 任务航迹点列表 + */ + private List trackPoints; + + /** + * 任务武器配置列表 + */ + private List taskWeapons; + + /** + * 任务目标 id + */ + private String targetId; + + /** + * 作战区经纬高坐标列表 + */ + private List warZoneLocation; + + /** + * 防区经纬高坐标列表 + */ + private List defZoneLocation; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTaskWeaponVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTaskWeaponVO.java new file mode 100644 index 0000000..ffe0fce --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTaskWeaponVO.java @@ -0,0 +1,49 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +/** + * 任务挂载的武器配置 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleTaskWeaponVO { + + /** + * 装备 id(武器唯一标识) + */ + private String equipmentId; + + /** + * 装备名称 + */ + private String name; + + /** + * 装备类型编码 + */ + private String supportType; + + /** + * 装备组件列表 + */ + private List components; + + /** + * 装备部署位置 + */ + private FireRuleCoordinateVO coordinate; + + /** + * 武器数量;可为数值或业务文案(如「最大载弹量」) + */ + private Object number; + + /** + * 该武器打击目标 id + */ + private String targetId; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTrackPointVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTrackPointVO.java new file mode 100644 index 0000000..6f08fc9 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleTrackPointVO.java @@ -0,0 +1,37 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 任务航迹点 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleTrackPointVO { + + /** + * 航迹点序号 + */ + private Integer index; + + /** + * 经度 + */ + private Double longitude; + + /** + * 纬度 + */ + private Double latitude; + + /** + * 高度 + */ + private Double height; + + /** + * 速度 + */ + private Double speed; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleWeaponComponentVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleWeaponComponentVO.java new file mode 100644 index 0000000..3c46888 --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleWeaponComponentVO.java @@ -0,0 +1,29 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; + +/** + * 任务武器下的设备组件 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleWeaponComponentVO { + + /** + * 设备 id + */ + private String deviceId; + + /** + * 设备名称 + */ + private String deviceName; + + /** + * 组件参数列表 + */ + private List componentParams; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleWingmanDataVO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleWingmanDataVO.java new file mode 100644 index 0000000..fba994e --- /dev/null +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/ultimately/vo/FireRuleWingmanDataVO.java @@ -0,0 +1,22 @@ +package com.solution.rule.domain.ultimately.vo; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +/** + * 编组中的僚机相对参数 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FireRuleWingmanDataVO { + + private Integer alt; + + private String angle; + + private Integer distance; + + private Integer key; + + private String name; +} diff --git a/auto-solution-rule/src/main/java/com/solution/rule/service/FireRuleService.java b/auto-solution-rule/src/main/java/com/solution/rule/service/FireRuleService.java index 53af5ef..bb6ea62 100644 --- a/auto-solution-rule/src/main/java/com/solution/rule/service/FireRuleService.java +++ b/auto-solution-rule/src/main/java/com/solution/rule/service/FireRuleService.java @@ -5,6 +5,8 @@ import com.solution.rule.domain.FireRuleExecuteDTO; import com.solution.rule.domain.Platform; import com.solution.rule.domain.PlatformComponent; import com.solution.rule.domain.simplerulepojo.Task; +import com.solution.rule.domain.ultimately.dto.FireRuleInputDTO; +import com.solution.rule.domain.ultimately.vo.FireRuleOutputVO; import com.solution.rule.domain.vo.PlatformComponentNamesVO; import com.solution.rule.domain.vo.PlatformWeaponAggregateVO; @@ -58,4 +60,11 @@ public interface FireRuleService { * @return */ Task executeTask(Task task); + + /** + * 火力规则 + * @param task + * @return + */ + FireRuleOutputVO rule(FireRuleInputDTO task); } diff --git a/auto-solution-rule/src/main/java/com/solution/rule/service/impl/FireRuleServiceImpl.java b/auto-solution-rule/src/main/java/com/solution/rule/service/impl/FireRuleServiceImpl.java index c6e2fb5..d9773f8 100644 --- a/auto-solution-rule/src/main/java/com/solution/rule/service/impl/FireRuleServiceImpl.java +++ b/auto-solution-rule/src/main/java/com/solution/rule/service/impl/FireRuleServiceImpl.java @@ -7,6 +7,10 @@ import com.solution.rule.domain.*; import com.solution.rule.domain.dto.WeaponModelDTO; import com.solution.rule.domain.simplerulepojo.Task; import com.solution.rule.domain.simplerulepojo.Weapon; +import com.solution.rule.domain.simplerulepojo.fact.FactTask; +import com.solution.rule.domain.ultimately.dto.FireRuleInputDTO; +import com.solution.rule.domain.ultimately.dto.FireRuleTaskInputDTO; +import com.solution.rule.domain.ultimately.vo.FireRuleOutputVO; import com.solution.rule.domain.vo.ComponentCountVO; import com.solution.rule.domain.vo.PlatformComponentNamesVO; import com.solution.rule.domain.vo.PlatformWeaponAggregateVO; @@ -19,13 +23,11 @@ import com.solution.rule.simpstrategy.FireRuleStrategyFactory; import com.solution.rule.strategy.SceneStrategy; import com.solution.rule.strategy.SceneStrategyFactory; import org.kie.api.KieBase; +import org.kie.api.runtime.KieSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; @Service @@ -186,6 +188,32 @@ public class FireRuleServiceImpl implements FireRuleService { return task; } + /** + * 执行任务 + * @param task + * @return + */ + @Override + public FireRuleOutputVO rule(FireRuleInputDTO task) { + if(ObjectUtil.isEmpty(task)){ + throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION); + } + List tasks = task.getTasks(); + if(CollUtil.isEmpty( tasks)){ + throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION); + } + KieSession kieSession = kieBase.newKieSession(); + Map globalParams = new HashMap<>(); + kieSession.setGlobal("globalParams", globalParams); + for (FireRuleTaskInputDTO fireRuleTaskInputDTO : tasks) { + + + } + + FireRuleOutputVO FireRuleOutputVO = new FireRuleOutputVO(); + return FireRuleOutputVO; + } + /** * 获取所有组件以及数量 * @return diff --git a/auto-solution-rule/src/main/resources/json/火力规则输入-无注释.json b/auto-solution-rule/src/main/resources/json/火力规则输入-无注释.json new file mode 100644 index 0000000..d1b25d3 --- /dev/null +++ b/auto-solution-rule/src/main/resources/json/火力规则输入-无注释.json @@ -0,0 +1,349 @@ +{ + "sourceFile": "区域防空31111_2026-04-02 15_29_03.json", + "tasks": [ + { + "id": "ed0e67e8-691f-4902-bb67-bd289aec89a1", + "drawName": "F-22-1打击任务", + "dataType": "taskPlane", + "side": "BLUE", + "trackPointsId": "d685ca26-2ffa-4b2b-8e75-ecf5897338f5", + "trackPoints": [ + { + "index": 1, + "longitude": 124.02895007578839, + "latitude": 26.836172676390202, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 2, + "longitude": 122.81034067987203, + "latitude": 27.017765463140986, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 3, + "longitude": 121.49435868664283, + "latitude": 27.17048502942595, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 4, + "longitude": 120.87774502345792, + "latitude": 27.16848996890842, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 5, + "longitude": 120.86773193816859, + "latitude": 26.390372000100005, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 6, + "longitude": 122.81161998730377, + "latitude": 25.50052904615808, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 7, + "longitude": 125.09035998725984, + "latitude": 24.79876865711152, + "height": 6000.0, + "speed": 600.0 + } + ], + "taskWeapons": [ + { + "equipmentId": "40b341f6-d60a-4a29-b84d-2973a3065124", + "name": "导弹", + "supportType": "2001", + "components": [ + { + "deviceId": "81c7e7da-cb7b-4435-bba3-1648e946d2b2", + "deviceName": "导弹机动组件", + "componentParams": [ + { + "uuid": "b47f34c2-3cba-4be3-a1d5-be0a986dff6f", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + }, + { + "deviceId": "4cf26cd0-bd5b-4d4c-a2eb-bca6184be5a1", + "deviceName": "导弹平台", + "componentParams": [ + { + "uuid": "32beea38-ffc2-445a-9687-e9bdb087727f", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + } + ], + "coordinate": { + "longitude": 124.74107151788, + "latitude": 26.74143394432, + "height": -498.06469972014 + }, + "number": "1" , + "targetId": "0c058874-0c12-4902-8fd0-2cde015965e1" + } + ], + "targetId": "0c058874-0c12-4902-8fd0-2cde015965e1", + "warZoneLocation": [ + { + "longitude": 122.18971775079, + "latitude": 28.64177652916, + "height": -99.95248993318 + }, + { + "longitude": 127.22874089381, + "latitude": 28.54075963352, + "height": -1063.0224849918 + }, + { + "longitude": 127.99831970891, + "latitude": 23.81306022325, + "height": -5900.62524601637 + }, + { + "longitude": 120.14866648107, + "latitude": 23.90272134296, + "height": -22.17614107099 + } + ] + } +], + "redWeapons": [ + { + "name": "HQ-9", + "id": "07e18ffb-3826-4e84-b884-68d77838ae25", + "components": [ + { + "deviceId": "89a639f5-3675-457b-ad4c-6b91a8d900e3", + "deviceName": "", + "componentParams": [ + { + "uuid": "647087e6-ccad-4b14-ba32-9962ae42eee7", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + }, + { + "deviceId": "7c6529f5-3fe5-4cfe-b4c8-b5a5a8178cda", + "deviceName": "导弹平台", + "componentParams": [ + { + "uuid": "a2ac6d74-09ac-49d5-8e05-fcaec47757c5", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + } + ], + "number": 1, + "successTargetRad": "命中率", + "isLead": { + "leader": "长机平台的唯一标识 id", + "side": "阵营", + "supportType": "长机平台类型", + "positions": [ + { + "longitude": "经度", + "latitude": "纬度", + "height": "高度" + } + ] + }, + "isWing": [ + { + "name": "僚机名称", + "distance": "距离", + "angle": "角度", + "alt": "高度差" + } + ] + }, + { + "SupportType": "car", + "TroopsDetail": {}, + "Platform_type": "HQ-9发射车", + "isStrikeTarget": false, + "isReconTarget": false, + "isInterferenceTarget": false, + "isDefendImportantPlace": false, + "groupType": "equipment", + "EquipmentID": "b3c6de29-2b27-4500-a9ce-95d0bebc5cb9", + "Name": "HQ-9发射车--5", + "OwnerForceSide": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831", + "PlatID": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831_equipmentPlane", + "SubComponents": { + "communication": [ + { + "ObjectHandle": "3ed86636-a33a-41ac-aff2-0627eb1f9b28", + "arithmetic": { + "id": "66cd29fdce08f520f1d9bf0e", + "name": "短波电台" + }, + "device": { + "id": "5e0e5b064f1fae2ee9fa1000", + "name": "3MHZ通信电台", + "refId": "5e0e5b064f1fae2ee9fa1000" + }, + "deviceId": "0b700ca6-290e-422a-b4e3-73b222809938", + "deviceName": "3MHZ通信电台", + "employLabel": false, + "facilityName": "3MHZ通信电台", + "soleId": "3ed86636-a33a-41ac-aff2-0627eb1f9b28", + "twiceModified": {}, + "zLists": [], + "ParentPlat": "04a96dbc-837a-48be-8c04-e4f53586c47e" + } + ], + "motorized_assembly": [ + { + "ObjectHandle": "83ea5d33-90f4-4fd8-9121-eced6c7a44ea", + "arithmetic": {}, + "device": {}, + "deviceId": "55915bc4-c825-43c4-93eb-6a76ab345e42", + "deviceName": "", + "soleId": "83ea5d33-90f4-4fd8-9121-eced6c7a44ea", + "ParentPlat": "04a96dbc-837a-48be-8c04-e4f53586c47e" + } + ], + "platform": [ + { + "ObjectHandle": "04a96dbc-837a-48be-8c04-e4f53586c47e", + "arithmetic": { + "id": "68876d0fd41989f086e905b8", + "name": "通用发射车算法" + }, + "device": { + "id": "68876d2dd41989f086e905b9", + "name": "发射车平台", + "refId": "68876d2dd41989f086e905b9" + }, + "deviceId": "164c75ec-6db5-48f9-8285-a7e726fd2a11", + "deviceName": "发射车平台", + "employLabel": false, + "facilityName": "发射车平台", + "soleId": "87905d6b-857a-4c5a-a922-5e2d191588d4", + "twiceModified": {}, + "zLists": [], + "TrackParamId": "", + "positions": [ + 119.28585462691, + 25.67974332623, + 34.28352933882 + ] + } + ], + "weapon": [ + { + "ObjectHandle": "4e575d4d-2f36-436b-9939-0b96cde96b89", + "arithmetic": { + "id": "669dd6356bc286bd64e5d66c", + "name": "发射架算法" + }, + "codedQueue": "weapon.launcher", + "configuration": { + "classifyName": "导弹平台", + "isMount": 1, + "mountedWeapon": { + "_id": "68f681794ddde62a52c7e569", + "name": "HQ-9" + }, + "number": 4 + }, + "device": { + "id": "67ff279dafa7ea5aaa3a1236", + "name": "通用发射架", + "refId": "b9665788-9437-40fb-8a24-eff3d4d3b529" + }, + "deviceId": "4e910ccf-d937-4a93-9b68-116167eda202", + "deviceName": "通用发射架", + "employLabel": false, + "facilityName": "通用发射架", + "serialNumber": "launcher", + "soleId": "4e575d4d-2f36-436b-9939-0b96cde96b89", + "twiceModified": { + "launcherType": "sam" + }, + "zLists": [], + "ParentPlat": "04a96dbc-837a-48be-8c04-e4f53586c47e" + } + ] + } + }, + { + "name": "营指挥所", + "id": "9843e2ec-59a5-440f-9669-67f910b8edc3", + "components": [ + { + "deviceId": "7f41d14e-83bc-41c7-8e20-cb61f4fc1289", + "deviceName": "", + "componentParams": [ + { + "uuid": "5da5f834-a8a9-407d-8a10-6dc78e488af9", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + }, + { + "deviceId": "591217ed-04e5-4120-87db-b5896d87b44d", + "deviceName": "地面指挥所", + "componentParams": [ + { + "uuid": "2c53125c-76da-4454-9f91-04ab3c940b9a", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + } + ], + "number": 1, + "successTargetRad": "命中率" + }, + { + "name": "防区2", + "id": "c2259481-279a-4c10-ae45-cf2a669e7b52_Circle", + "isLead": { + "drawName": "圆形名称", + "airspaceType": "空域类型", + "side": "阵营", + "selectLonLat": "positions", + "positions": [ + { + "longitude": "圆心经度", + "latitude": "圆心纬度", + "height": "圆心高程" + } + ], + "radius": "圆半径(KM)", + "height": "高度(米)", + "extrudedHeight": "离地高度(米)" + }, + "isWing": { + "outlineWidth": "线宽", + "lineType": "线条", + "outlineColor": "轮廓色", + "color": "填充色" + } + } + ] +} \ No newline at end of file diff --git a/auto-solution-rule/src/main/resources/json/火力规则输入.json b/auto-solution-rule/src/main/resources/json/火力规则输入.json new file mode 100644 index 0000000..149e36a --- /dev/null +++ b/auto-solution-rule/src/main/resources/json/火力规则输入.json @@ -0,0 +1,474 @@ +{ + "_comment_sourceFile": "来源场景文件名", + "sourceFile": "区域防空31111_2026-04-02 15_29_03.json", + "_comment_tasks": "蓝方火力规则任务列表(数组,每项为一条任务)", + "tasks": [ + { + "_comment_id": "任务id:唯一标识符", + "id": "ed0e67e8-691f-4902-bb67-bd289aec89a1", + "_comment_drawName": "任务名称", + "drawName": "F-22-1打击任务", + "_comment_dataType": "任务类型", + "dataType": "taskPlane", + "_comment_side": "任务所属阵营", + "side": "BLUE", + "_comment_trackPointsId": "航迹所属实体/阵营标识", + "trackPointsId": "d685ca26-2ffa-4b2b-8e75-ecf5897338f5", + "_comment_trackPoints": "任务航迹点列表", + "trackPoints": [ + { + "index": 1, + "longitude": 124.02895007578839, + "latitude": 26.836172676390202, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 2, + "longitude": 122.81034067987203, + "latitude": 27.017765463140986, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 3, + "longitude": 121.49435868664283, + "latitude": 27.17048502942595, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 4, + "longitude": 120.87774502345792, + "latitude": 27.16848996890842, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 5, + "longitude": 120.86773193816859, + "latitude": 26.390372000100005, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 6, + "longitude": 122.81161998730377, + "latitude": 25.50052904615808, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 7, + "longitude": 125.09035998725984, + "latitude": 24.79876865711152, + "height": 6000.0, + "speed": 600.0 + } + ], + "_comment_taskWeapons": "任务武器配置列表", + "taskWeapons": [ + { + "_comment_equipmentId": "装备id:武器唯一标识", + "equipmentId": "40b341f6-d60a-4a29-b84d-2973a3065124", + "_comment_name": "装备名称", + "name": "导弹", + "_comment_supportType": "装备类型", + "supportType": "2001", + "_comment_components": "装备组件列表", + "components": [ + { + "deviceId": "81c7e7da-cb7b-4435-bba3-1648e946d2b2", + "deviceName": "导弹机动组件", + "componentParams": [ + { + "uuid": "b47f34c2-3cba-4be3-a1d5-be0a986dff6f", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + }, + { + "deviceId": "4cf26cd0-bd5b-4d4c-a2eb-bca6184be5a1", + "deviceName": "导弹平台", + "componentParams": [ + { + "uuid": "32beea38-ffc2-445a-9687-e9bdb087727f", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + } + ], + "_comment_coordinate": "装备部署位置:经纬高坐标", + "coordinate": { + "longitude": 124.74107151788, + "latitude": 26.74143394432, + "height": -498.06469972014 + }, + "_comment_number": "武器数量", + "number": "最大载弹量" , + "_comment_targetId": "目标id", + "targetId": "0c058874-0c12-4902-8fd0-2cde015965e1" + } + ], + "_comment_targetId": "任务目标id", + "targetId": "0c058874-0c12-4902-8fd0-2cde015965e1", + "_comment_warZoneLocation": "作战区经纬高坐标列表", + "warZoneLocation": [ + { + "longitude": 122.18971775079, + "latitude": 28.64177652916, + "height": -99.95248993318 + }, + { + "longitude": 127.22874089381, + "latitude": 28.54075963352, + "height": -1063.0224849918 + }, + { + "longitude": 127.99831970891, + "latitude": 23.81306022325, + "height": -5900.62524601637 + }, + { + "longitude": 120.14866648107, + "latitude": 23.90272134296, + "height": -22.17614107099 + } + ] + } + ], + "_comment_redWeapons": "红方武器汇总()", + "redWeapons": [ + { + "_comment_name": "红方武器名称", + "name": "HQ-9", + "_comment_id": "红方武器id", + "id": "07e18ffb-3826-4e84-b884-68d77838ae25", + "_comment_components": "红方武器下组件列表", + "components": [ + { + "deviceId": "89a639f5-3675-457b-ad4c-6b91a8d900e3", + "deviceName": "", + "componentParams": [ + { + "uuid": "647087e6-ccad-4b14-ba32-9962ae42eee7", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + }, + { + "deviceId": "7c6529f5-3fe5-4cfe-b4c8-b5a5a8178cda", + "deviceName": "导弹平台", + "componentParams": [ + { + "uuid": "a2ac6d74-09ac-49d5-8e05-fcaec47757c5", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + } + ], + "_comment_number": "红方武器弹药数量", + "number": 1, + "_comment_successTargetRad": "红方武器命中率(目前无)", + "successTargetRad": "命中率", + "_comment_isLead": "长机入参结构", + "isLead": { + "leader": "长机平台的唯一标识 id", + "side": "阵营", + "supportType": "长机平台类型", + "positions": [ + { + "longitude": "经度", + "latitude": "纬度", + "height": "高度" + } + ] + }, + "_comment_isWing": "僚机入参结构", + "isWing": [ + { + "name": "僚机名称", + "distance": "距离", + "angle": "角度", + "alt": "高度差" + } + ] + }, + { + "color": "rgb(220,39,39)", + "dataType": "taskPlane", + "drawName": "", + "groupType": "tasks", + "id": "41d3c5ce-3fda-4dee-a38f-fd3ae7e15e69", + "idKey": "id", + "isSelected": false, + "name": "", + "parentId": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831_taskPlane", + "permission": [ + "14bc8ff9-3c93-4218-b01a-e144add196f9", + "56a96b1b-14a8-4daf-a2d0-47c7faa4b831" + ], + "show": true, + "side": "红方", + "sort": 1774271151846, + "task": { + "at_time": "180", + "attackId": "267c5d87-bf36-42f2-b0a8-09c608d94b62", + "color": "rgb(220,39,39)", + "departureAirport": "", + "execute": [ + { + "targetList": [ + { + "ID": "fbddc49c-f7b0-4d07-8000-1b5a2633c461", + "arrayPositionId": 4, + "arrayPositionIdBak": "", + "at_time": { + "timeUp": true, + "value": 180 + }, + "attackType": "", + "boost": "", + "bootTime": "", + "companion": "", + "cruiseRouteId": "routeLine_f01be820-33ca-4843-8354-3f93a9986fe3", + "cruiseRouteOffset": [ + { + "UpOrDown": true, + "value": 0 + }, + { + "BeforeOrAfter": true, + "value": 0 + }, + { + "LeftOrRight": true, + "value": 0 + } + ], + "decoyNum": "0", + "decoyTime": "0", + "disturbNum": "0", + "disturbStartTime": "0", + "droneNum": "", + "fireType": "absolute", + "hitNum": "", + "launchAngle": "", + "launchAzimuth": "", + "noFlyZone": "", + "orbitInclination": "", + "refulId": "", + "salvo": { + "salvoUp": true, + "value": 0 + }, + "strategy": [], + "targetId": "dd20d7d9-7ce8-4531-aa0c-0b2056e7fbcd", + "times_interval": 1, + "weaponId": "HQ-9", + "weaponRelease": "", + "weaponType": "", + "weaponUseCount": 3 + } + ], + "type": "assault" //任务类型 + } + ], + "landAirport": "", + "missionList": [ + { + "label": "HQ-9(4)", + "launcherType": "sam", + "number": 4, + "value": "HQ-9" + } + ], + "name": "HQ-9发射车--3打击任务", + "side": "红方", + "sideId": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831", + "speed": 600, + "type": "assault", + "weaponId": "" + } + }, + { + "SupportType": "car", + "TroopsDetail": {}, + "Platform_type": "HQ-9发射车", + "isStrikeTarget": false, + "isReconTarget": false, + "isInterferenceTarget": false, + "isDefendImportantPlace": false, + "groupType": "equipment", + "EquipmentID": "b3c6de29-2b27-4500-a9ce-95d0bebc5cb9", + "Name": "HQ-9发射车--5", + "OwnerForceSide": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831", + "PlatID": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831_equipmentPlane", + "SubComponents": { + "communication": [ + { + "ObjectHandle": "3ed86636-a33a-41ac-aff2-0627eb1f9b28", + "arithmetic": { + "id": "66cd29fdce08f520f1d9bf0e", + "name": "短波电台" + }, + "device": { + "id": "5e0e5b064f1fae2ee9fa1000", + "name": "3MHZ通信电台", + "refId": "5e0e5b064f1fae2ee9fa1000" + }, + "deviceId": "0b700ca6-290e-422a-b4e3-73b222809938", + "deviceName": "3MHZ通信电台", + "employLabel": false, + "facilityName": "3MHZ通信电台", + "soleId": "3ed86636-a33a-41ac-aff2-0627eb1f9b28", + "twiceModified": {}, + "zLists": [], + "ParentPlat": "04a96dbc-837a-48be-8c04-e4f53586c47e" + } + ], + "motorized_assembly": [ + { + "ObjectHandle": "83ea5d33-90f4-4fd8-9121-eced6c7a44ea", + "arithmetic": {}, + "device": {}, + "deviceId": "55915bc4-c825-43c4-93eb-6a76ab345e42", + "deviceName": "", + "soleId": "83ea5d33-90f4-4fd8-9121-eced6c7a44ea", + "ParentPlat": "04a96dbc-837a-48be-8c04-e4f53586c47e" + } + ], + "platform": [ + { + "ObjectHandle": "04a96dbc-837a-48be-8c04-e4f53586c47e", + "arithmetic": { + "id": "68876d0fd41989f086e905b8", + "name": "通用发射车算法" + }, + "device": { + "id": "68876d2dd41989f086e905b9", + "name": "发射车平台", + "refId": "68876d2dd41989f086e905b9" + }, + "deviceId": "164c75ec-6db5-48f9-8285-a7e726fd2a11", + "deviceName": "发射车平台", + "employLabel": false, + "facilityName": "发射车平台", + "soleId": "87905d6b-857a-4c5a-a922-5e2d191588d4", + "twiceModified": {}, + "zLists": [], + "TrackParamId": "", + "positions": [ + 119.28585462691, + 25.67974332623, + 34.28352933882 + ] + } + ], + "weapon": [ + { + "ObjectHandle": "4e575d4d-2f36-436b-9939-0b96cde96b89", + "arithmetic": { + "id": "669dd6356bc286bd64e5d66c", + "name": "发射架算法" + }, + "codedQueue": "weapon.launcher", + "configuration": { + "classifyName": "导弹平台", + "isMount": 1, + "mountedWeapon": { + "_id": "68f681794ddde62a52c7e569", + "name": "HQ-9" + }, + "number": 4 + }, + "device": { + "id": "67ff279dafa7ea5aaa3a1236", + "name": "通用发射架", + "refId": "b9665788-9437-40fb-8a24-eff3d4d3b529" + }, + "deviceId": "4e910ccf-d937-4a93-9b68-116167eda202", + "deviceName": "通用发射架", + "employLabel": false, + "facilityName": "通用发射架", + "serialNumber": "launcher", + "soleId": "4e575d4d-2f36-436b-9939-0b96cde96b89", + "twiceModified": { + "launcherType": "sam" + }, + "zLists": [], + "ParentPlat": "04a96dbc-837a-48be-8c04-e4f53586c47e" + } + ] + } + }, + { + "name": "营指挥所", + "id": "9843e2ec-59a5-440f-9669-67f910b8edc3", + "components": [ + { + "deviceId": "7f41d14e-83bc-41c7-8e20-cb61f4fc1289", + "deviceName": "", + "componentParams": [ + { + "uuid": "5da5f834-a8a9-407d-8a10-6dc78e488af9", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + }, + { + "deviceId": "591217ed-04e5-4120-87db-b5896d87b44d", + "deviceName": "地面指挥所", + "componentParams": [ + { + "uuid": "2c53125c-76da-4454-9f91-04ab3c940b9a", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + } + ], + "number": 1, + "successTargetRad": "命中率" + }, + { + "name": "防区2", + "id": "c2259481-279a-4c10-ae45-cf2a669e7b52_Circle", + "_comment_isLead": "圆形编辑入参结构", + "isLead": { + "drawName": "圆形名称", + "airspaceType": "空域类型", + "side": "阵营", + "selectLonLat": "positions", + "positions": [ + { + "longitude": "圆心经度", + "latitude": "圆心纬度", + "height": "圆心高程" + } + ], + "radius": "圆半径(KM)", + "height": "高度(米)", + "extrudedHeight": "离地高度(米)" + }, + "_comment_isWing": "圆形样式入参结构", + "isWing": { + "outlineWidth": "线宽", + "lineType": "线条", + "outlineColor": "轮廓色", + "color": "填充色" + } + } + ] +} \ No newline at end of file diff --git a/auto-solution-rule/src/main/resources/json/火力规则输出.json b/auto-solution-rule/src/main/resources/json/火力规则输出.json new file mode 100644 index 0000000..7ebc365 --- /dev/null +++ b/auto-solution-rule/src/main/resources/json/火力规则输出.json @@ -0,0 +1,355 @@ +{ + "sourceFile": "区域防空31111_2026-04-02 15_29_03.json", + "fireRuleInputs": [ + + { + "id": "ed0e67e8-691f-4902-bb67-bd289aec89a1", + "drawName": "红方打击任务", + "dataType": "taskPlane", + "side": "RED", + "trackPointsId": "d685ca26-2ffa-4b2b-8e75-ecf5897338f5", + "trackPoints": [ + { + "index": 1, + "longitude": 124.02895007578839, + "latitude": 26.836172676390202, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 2, + "longitude": 122.81034067987203, + "latitude": 27.017765463140986, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 3, + "longitude": 121.49435868664283, + "latitude": 27.17048502942595, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 4, + "longitude": 120.87774502345792, + "latitude": 27.16848996890842, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 5, + "longitude": 120.86773193816859, + "latitude": 26.390372000100005, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 6, + "longitude": 122.81161998730377, + "latitude": 25.50052904615808, + "height": 6000.0, + "speed": 600.0 + }, + { + "index": 7, + "longitude": 125.09035998725984, + "latitude": 24.79876865711152, + "height": 6000.0, + "speed": 600.0 + } + ], + "taskWeapons": [ + { + "equipmentId": "40b341f6-d60a-4a29-b84d-2973a3065124", + "name": "导弹", + "supportType": "2001", + "components": [ + { + "deviceId": "81c7e7da-cb7b-4435-bba3-1648e946d2b2", + "deviceName": "导弹机动组件", + "componentParams": [ + { + "uuid": "b47f34c2-3cba-4be3-a1d5-be0a986dff6f", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + }, + { + "deviceId": "4cf26cd0-bd5b-4d4c-a2eb-bca6184be5a1", + "deviceName": "导弹平台", + "componentParams": [ + { + "uuid": "32beea38-ffc2-445a-9687-e9bdb087727f", + "attDefaultValue": "", + "attExplain": "", + "number": 1 + } + ] + } + ], + "coordinate": { + "longitude": 124.74107151788, + "latitude": 26.74143394432, + "height": -498.06469972014 + }, + "number": 10, + "targetId": "0c058874-0c12-4902-8fd0-2cde015965e1" + } + ], + "targetId": "0c058874-0c12-4902-8fd0-2cde015965e1", + "warZoneLocation": [ + { + "longitude": 122.18971775079, + "latitude": 28.64177652916, + "height": -99.95248993318 + }, + { + "longitude": 127.22874089381, + "latitude": 28.54075963352, + "height": -1063.0224849918 + }, + { + "longitude": 127.99831970891, + "latitude": 23.81306022325, + "height": -5900.62524601637 + }, + { + "longitude": 120.14866648107, + "latitude": 23.90272134296, + "height": -22.17614107099 + } + ], + "defZoneLocation": [] + } + ], + "Tasks": [ + { + "color": "rgb(220,39,39)", + "dataType": "taskPlane", + "drawName": "HQ-9发射车--3打击任务", + "groupType": "tasks", + "id": "41d3c5ce-3fda-4dee-a38f-fd3ae7e15e69", + "idKey": "id", + "isSelected": false, + "name": "HQ-9发射车--3打击任务", + "parentId": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831_taskPlane", + "permission": [ + "14bc8ff9-3c93-4218-b01a-e144add196f9", + "56a96b1b-14a8-4daf-a2d0-47c7faa4b831" + ], + "show": true, + "side": "红方", + "sort": 1774271151846, + "task": { + "at_time": "180", + "attackId": "267c5d87-bf36-42f2-b0a8-09c608d94b62", + "color": "rgb(220,39,39)", + "departureAirport": "", + "execute": [ + { + "targetList": [ + { + "ID": "fbddc49c-f7b0-4d07-8000-1b5a2633c461", + "arrayPositionId": 4, + "arrayPositionIdBak": "", + "at_time": { + "timeUp": true, + "value": 180 + }, + "attackType": "", + "boost": "", + "bootTime": "", + "companion": "", + "cruiseRouteId": "routeLine_f01be820-33ca-4843-8354-3f93a9986fe3", + "cruiseRouteOffset": [ + { + "UpOrDown": true, + "value": 0 + }, + { + "BeforeOrAfter": true, + "value": 0 + }, + { + "LeftOrRight": true, + "value": 0 + } + ], + + "fireType": "absolute", + "strategy": [], + "targetId": "dd20d7d9-7ce8-4531-aa0c-0b2056e7fbcd", + "times_interval": 1, + "weaponId": "HQ-9", + "weaponRelease": "", + "weaponType": "", + "weaponUseCount": 3 + } + ], + "type": "assault" + } + ], + "landAirport": "", + "missionList": [ + { + "label": "HQ-9(4)", + "launcherType": "sam", + "number": 4, + "value": "HQ-9" + } + ], + "name": "HQ-9发射车--3打击任务", + "side": "红方", + "sideId": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831", + "speed": 600, + "type": "assault", + "weaponId": "" + } + } +], +"Groups": [ + { + "allAngle": 0, + "drawName": "J15编组1", + "editPermission": [], + "groupType": "addGroup", + "id": "5dea9ff7-5e45-4f8e-a67f-3ff4187c39ed", + "idKey": "id", + "isSelected": false, + "isShow": false, + "leader": "7a16c098-ceec-4c4d-8a24-8f44976a90ca", + "name": "addGroup", + "parentId": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831_batFormation", + "permission": [ + "14bc8ff9-3c93-4218-b01a-e144add196f9" + ], + "show": false, + "sort": 1774271497726, + "wingmanData": [ + { + "alt": 40, + "angle": "50", + "distance": 100, + "key": 0, + "name": "9aa9e5aa-9273-4c27-88e1-e582ff561685" + } + ] + } +], + "redWeapons": [ + { + "SupportType": "car", + "TroopsDetail": {}, + "Platform_type": "HQ-9发射车", + "isStrikeTarget": false, + "isReconTarget": false, + "isInterferenceTarget": false, + "isDefendImportantPlace": false, + "groupType": "equipment", + "EquipmentID": "b3c6de29-2b27-4500-a9ce-95d0bebc5cb9", + "Name": "HQ-9发射车--5", + "OwnerForceSide": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831", + "PlatID": "56a96b1b-14a8-4daf-a2d0-47c7faa4b831_equipmentPlane", + "SubComponents": { + "communication": [ + { + "ObjectHandle": "3ed86636-a33a-41ac-aff2-0627eb1f9b28", + "arithmetic": { + "id": "66cd29fdce08f520f1d9bf0e", + "name": "短波电台" + }, + "device": { + "id": "5e0e5b064f1fae2ee9fa1000", + "name": "3MHZ通信电台", + "refId": "5e0e5b064f1fae2ee9fa1000" + }, + "deviceId": "0b700ca6-290e-422a-b4e3-73b222809938", + "deviceName": "3MHZ通信电台", + "employLabel": false, + "facilityName": "3MHZ通信电台", + "soleId": "3ed86636-a33a-41ac-aff2-0627eb1f9b28", + "twiceModified": {}, + "zLists": [], + "ParentPlat": "04a96dbc-837a-48be-8c04-e4f53586c47e" + } + ], + "motorized_assembly": [ + { + "ObjectHandle": "83ea5d33-90f4-4fd8-9121-eced6c7a44ea", + "arithmetic": {}, + "device": {}, + "deviceId": "55915bc4-c825-43c4-93eb-6a76ab345e42", + "deviceName": "", + "soleId": "83ea5d33-90f4-4fd8-9121-eced6c7a44ea", + "ParentPlat": "04a96dbc-837a-48be-8c04-e4f53586c47e" + } + ], + "platform": [ + { + "ObjectHandle": "04a96dbc-837a-48be-8c04-e4f53586c47e", + "arithmetic": { + "id": "68876d0fd41989f086e905b8", + "name": "通用发射车算法" + }, + "device": { + "id": "68876d2dd41989f086e905b9", + "name": "发射车平台", + "refId": "68876d2dd41989f086e905b9" + }, + "deviceId": "164c75ec-6db5-48f9-8285-a7e726fd2a11", + "deviceName": "发射车平台", + "employLabel": false, + "facilityName": "发射车平台", + "soleId": "87905d6b-857a-4c5a-a922-5e2d191588d4", + "twiceModified": {}, + "zLists": [], + "TrackParamId": "", + "positions": [ + 119.28585462691, + 25.67974332623, + 34.28352933882 + ] + } + ], + "weapon": [ + { + "ObjectHandle": "4e575d4d-2f36-436b-9939-0b96cde96b89", + "arithmetic": { + "id": "669dd6356bc286bd64e5d66c", + "name": "发射架算法" + }, + "codedQueue": "weapon.launcher", + "configuration": { + "classifyName": "导弹平台", + "isMount": 1, + "mountedWeapon": { + "_id": "68f681794ddde62a52c7e569", + "name": "HQ-9" + }, + "number": 4 + }, + "device": { + "id": "67ff279dafa7ea5aaa3a1236", + "name": "通用发射架", + "refId": "b9665788-9437-40fb-8a24-eff3d4d3b529" + }, + "deviceId": "4e910ccf-d937-4a93-9b68-116167eda202", + "deviceName": "通用发射架", + "employLabel": false, + "facilityName": "通用发射架", + "serialNumber": "launcher", + "soleId": "4e575d4d-2f36-436b-9939-0b96cde96b89", + "twiceModified": { + "launcherType": "sam" + }, + "zLists": [], + "ParentPlat": "04a96dbc-837a-48be-8c04-e4f53586c47e" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/auto-solution-rule/src/main/resources/rules/README.md b/auto-solution-rule/src/main/resources/rules/README.md index 5414aab..656e1ea 100644 --- a/auto-solution-rule/src/main/resources/rules/README.md +++ b/auto-solution-rule/src/main/resources/rules/README.md @@ -15,6 +15,33 @@ ## 2. 参数-效果对照(给业务人员) +### 2.0 规则过程总览(先看这一条) + +引擎按 **salience 从高到低** 依次执行(同一 `agenda-group` 内数字越大越先跑)。主链路可以理解为:**先定武器与目标关系 → 再补组件与数量 → 再定任务名与空间位置 → 最后射程与航迹**。 + +```mermaid +flowchart TD + step1["红方武器自适应装配 55"] + step2["导弹联动增强 54"] + step3["导弹组件数量匹配 53"] + step4["命中率动态数量与offset 52"] + step5["任务自动匹配 50"] + step6["阵位区域解析 49"] + step7["阵位武器部署赋位 48"] + step8["射程合理性校验 47"] + step9["航迹生成 46"] + step1 --> step2 --> step3 --> step4 --> step5 --> step6 --> step7 --> step8 --> step9 +``` + +**两种装配主路径(由 `enableRuleScoring` 决定)** + +| 模式 | 开关 | 武器从哪来 | `targetId` 怎么定 | +|------|------|------------|---------------------| +| 过程驱动整数加分 | `enableRuleScoring=true`(默认) | 对每个蓝方目标抽特征 → 红方候选池打分 → **并列最高分全选** → 按需求数克隆武器 | 生成红方武器时 **直接写入** 对应该蓝目标的 `equipmentId`(见 `allocateRedWeaponsForBlueTarget`) | +| 传统白名单映射 | `enableRuleScoring=false` | 按 `enableAirRule` 等开关 + `map_*_targets` 映射追加武器 | 装配结束后调用 **`bindTargetIdsForRedWeapons`**:可行性过滤 + **五维加权分** + 边际递减 + 比例/回退 | + +**调参时的阅读顺序建议**:先确认当前是过程驱动还是白名单 → 再改对应段落里的开关与阈值 → 最后看下游规则(命中率改数量、阵位改坐标)是否仍符合预期。 + ### 2.1 武器名称映射(改名字,不改逻辑) - `redStrikeDroneName`:空中反制组中的无人机名称。 - `redArmedHelicopterName`:空中反制组中的武装直升机名称。 @@ -26,17 +53,20 @@ - `redMissileVehicleName`:导弹补充组中的导弹发射车名称。 ### 2.2 白名单开关(决定“是否匹配”) -- `enableAirRule`:`true` 时,蓝方空中目标会触发红方空中反制组;`false` 时该组永不触发。 -- `enableGroundRule`:`true` 时,蓝方地面目标会触发红方炮类反制组;`false` 时不触发。 -- `enableArmorRule`:`true` 时,蓝方装甲目标会触发红方反坦克组;`false` 时不触发。 -- `enableMissileVehicleRule`:`true` 时,蓝方有导弹能力可追加导弹发射车;`false` 时不追加。 -- `enableMissileLinkage`:`true` 时开启导弹数量/范围联动;`false` 时不做导弹联动增强。 -- `allowMultiGroup`: - - `true`:同一批输入可命中多组策略并叠加武器; - - `false`:只命中第一组,后续组不再生效(更“死规则”)。 -- `enableArmedHelicopterOnAir`:空中组中是否包含武装直升机。 -### 2.3 蓝方类型到红方方案映射(核心,可多选) +> **仅当 `enableRuleScoring=false` 时**,下列开关与映射主导装配;`enableRuleScoring=true` 时走过程驱动,本节开关对**是否出武器**基本不生效(仍可能影响你后续阅读的兼容映射字段)。 + +| 参数 | 作用 | 影响什么 | 怎么调整 | +|------|------|----------|----------| +| `enableAirRule` | 是否允许“蓝方有空中目标”时走空中反制映射 | `false` 时空中组整条链路不追加武器 | 不需要空中反制时置 `false`,减少误匹配 | +| `enableGroundRule` | 是否允许蓝方地面目标触发炮类反制组 | 影响迫榴炮/车载迫击炮等是否出现 | 只想反装甲不上炮时置 `false` | +| `enableArmorRule` | 是否允许装甲目标触发反坦克组 | 影响反坦克火箭/导弹系统等是否出现 | 关闭后装甲线完全不走映射 | +| `enableMissileVehicleRule` | 蓝方有导弹能力时是否追加导弹发射车 | 控制是否额外生成 `map_missile_targets` 对应武器 | 默认多为 `false`,需导弹车时再开 | +| `enableMissileLinkage` | 导弹联动增强(`applyMissileLinkage`)是否执行 | 为 `true` 且满足门槛时,给红方**空中**武器补/增强导弹组件数量与射程 | 不想自动改导弹数量与范围时置 `false` | +| `allowMultiGroup` | 多组策略是否可叠加 | `true`:空中/地面/装甲等多条可同时命中;`false`:**只命中第一组**,后续不再加武器 | 想规则更“硬”、少组合时置 `false` | +| `enableArmedHelicopterOnAir` | 空中映射中是否包含武装直升机 | 影响空中组是否出 `redArmedHelicopterName` 对应装备 | 只要无人机不要武直时置 `false` | + +### 2.3 蓝方类型到红方方案映射(兼容项,评分模式下弱化) 先解释你提到的 “k”: - 这里的 `k` 就是 **key(键名)**,例如 `map_armor_targets`。 @@ -51,68 +81,173 @@ - `map_missile_targets`:蓝方有导弹能力时,红方使用哪些武器。 映射规则说明: -- 值必须是红方武器库内合法名称,否则该项会被忽略。 -- 为空时视为该组不配置,允许不命中。 -- 示例:`map_armor_targets=反坦克火箭,反坦克导弹系统` 表示坦克可同时触发两种红方反制武器。 +- 当 `enableScoringSelection=true` 时,本组配置不再主导最终结果,仅作为兼容保留。 +- 当前主决策由“过程驱动评分引擎”完成(类型/距离/命中率/成本综合评分)。 -### 2.4 数量和阈值(决定“匹配后给多少”) -- `defaultAirNum`:空中组默认数量。 -- `defaultGroundNum`:地面/装甲组默认数量。 -- `defaultMissileVehicleNum`:导弹发射车默认数量。 -- `shellRangeDefault`:炮类组件参数值,单位固定 `范围米`。 -- `missileCountOffset`:红方导弹数量 = 蓝方导弹数量 + 偏移量。 -- `missileRangeOffset`:红方导弹范围 = 蓝方导弹范围 + 偏移量(单位 `破坏范围米`)。 -- `blueMissileRangeDefault`:蓝方导弹范围缺失时采用的默认值。 -- `minBlueMissileCountForLinkage`:蓝方导弹数量达到该值才触发联动增强。 +### 2.4 数量与参数(匹配后给多少、导弹与炮类参数) -### 2.5 全组件数量匹配参数(新增) -- `enableComponentQuantityMatch`:是否启用全组件数量覆盖(非导弹组件)。 -- `componentDeviceNameMappingCsv`:组件名映射表(逗号分隔),格式 `蓝组件deviceName->红组件deviceName`;为空则默认 `deviceName` 直匹配。 -- `skipMissileComponentsByNameContains`:跳过覆盖关键词:蓝方组件 `deviceName` 包含该关键词则不覆盖;默认 `导弹`(用于保留导弹联动偏移)。 +| 参数 | 作用 | 影响什么 | 怎么调整 | +|------|------|----------|----------| +| `defaultAirNum` | 白名单路径下空中映射默认实例数量 | 每轮 `map_air_targets` 展开时基数 | 需要更多同型机时调大 | +| `defaultGroundNum` | 地面/装甲/炮类映射默认数量 | 非空中组映射展开基数 | 同上 | +| `defaultMissileVehicleNum` | 导弹发射车映射默认数量 | `enableMissileVehicleRule=true` 时追加数量 | 导弹车要多台时调大 | +| `shellRangeDefault` | 炮类武器“炮弹”组件射程字符串 | `limitRedArtilleryToShellOnly` 写入的默认 `范围米` | 想压远/压近打击时改数值 | +| `missileCountOffset` | 联动时红方导弹数量相对蓝方的增量 | `applyMissileLinkage` 中目标导弹发数 | 希望红方导弹明显多于蓝方时调大 | +| `missileRangeOffset` | 联动时红方导弹射程在蓝方基础上的增量 | 红方空中武器导弹组件首参数(破坏范围米) | 需要更远覆盖时调大 | +| `blueMissileRangeDefault` | 蓝方未给导弹射程时的假定值 | 联动计算 `redRangeTarget` 的输入 | 输入不全时兜底,按典型弹种改 | +| `minBlueMissileCountForLinkage` | 联动触发门槛(蓝方导弹总数) | 低于则 `applyMissileLinkage` 直接返回 | 想更难触发联动时调大(如 2、3) | -### 2.6 targetId 自动绑定参数(新增) -- `enableTargetAutoBind`:是否自动给红方武器写入 `targetId`。 -- `minTargetBindRatio`:最低绑定比例(例如 `0.7` 表示至少 70% 红方武器有目标)。 -- `allowReserveWithoutTarget`: - - `true`:允许少量红方武器 `targetId` 为空(火力冗余)。 - - `false`:尽量给每个红方武器分配目标。 +### 2.5 仅导弹组件匹配(`导弹组件数量匹配规则`,salience 53) -绑定规则说明(固定,不需要业务改代码): -- 绑定来源是蓝方武器 `equipmentId`。 -- 匹配优先级按武器类型: - - 防空类红方武器优先绑定蓝方空中目标 - - 反装甲类红方武器优先绑定蓝方装甲目标 - - 炮类红方武器优先绑定蓝方炮类/地面目标 - - 导弹发射车优先绑定蓝方导弹能力目标 -- 当优先池不足时自动回退到地面池/全目标池,保证大部分武器有目标。 +**规则过程**:在红方武器已有 `targetId` 的前提下,按蓝方对应装备的**导弹类组件**去对齐红方导弹组件的**数量**与**首参数**;其它组件不动。 -### 2.7 命中率与动态火力参数(新增) -- `hitRateCsv`:业务可配置红方武器命中率,格式 `武器名=0.72,武器名2=0.55`。 -- `defaultHitRateFallback`:未命中 `hitRateCsv` 且武器未携带 `hitRate` 时的兜底命中率。 -- `desiredKillProbability`:目标毁伤置信度(例如 `0.9`)。 -- `offsetCsvByWeapon`:显式 offset(最高优先级),格式 `武器名=1,武器名2=2`。 -- `enableDynamicMultiRedPerBlue`:是否按命中率动态决定“一个蓝方目标需要几把红方武器”。 -- `minRedWeaponsPerBlueTarget` / `maxRedWeaponsPerBlueTargetCap`:每个蓝方目标的最小/最大红方分配数。 +| 参数 | 作用 | 影响什么 | 怎么调整 | +|------|------|----------|----------| +| `enableComponentQuantityMatch` | 总开关 | `false` 时本规则不改写任何组件 | 完全手工控组件时关闭 | +| `componentDeviceNameMappingCsv` | 蓝/红导弹组件 `deviceName` 映射 | 解决双方组件命名不一致导致对不上 | 填 `机载导弹->某某导弹` 形式,逗号分隔多对 | +| `missileComponentNameContains` | 判定“导弹组件”的关键词 | 只有 `deviceName` 含该词的组件参与匹配 | 若业务用“火箭弹”等命名,可改关键词 | -优先级说明(重要): -- 若 `offsetCsvByWeapon` 命中某武器,则直接使用显式 offset,不再使用命中率推导 offset。 -- 未配置显式 offset 时,规则按命中率与 `desiredKillProbability` 自动推导所需火力数量。 -- 同一蓝方 `equipmentId` 可能被多个红方武器绑定(不是固定 2 把),数量由命中率动态计算并受 cap 限制。 +**输出影响**:仅改导弹组件 `componentParams[0].number` 与 `componentParams[0].attDefaultValue`(破坏范围等),非导弹组件保持红方原值。 -### 2.8 阵位规则参数(新增) -- `enablePositionRules`:阵位规则总开关。 -- 阵位输入来源:`blueTask.warZoneLocation` 与 `blueTask.defZoneLocation`(各 4 个经纬点)。 -- `fireUnitSpacingMeters`:防区/作战区点位间距(米),例如 `100` 代表约每 100 米一个火力单元。 -- `airDeployZonePreference`:飞机优先部署区域(`combat` 或 `defense`)。 -- `defensePriorityWeapons`:优先部署在防区的武器名单(逗号分隔)。 -- `groundDeployHeight` / `airDeployHeight`:地面/空中武器部署高度。 +### 2.6 targetId 自动绑定(仅传统白名单路径) -阵位规则效果: -- 新增两条规则: - - `阵位规则-区域解析与点位生成` - - `阵位规则-武器部署赋位` -- 飞机可在任意区(按偏好区优先);反坦克等重火力优先防区。 -- 在多边形区域内按间距生成候选点,并给红方武器写入 `weapon.coordinate`。 +> **`enableRuleScoring=true`(过程驱动)时**:装配阶段已在 `allocateRedWeaponsForBlueTarget` 里把 `targetId` 设为对应蓝方 `equipmentId`,**不会**再走下表的五维分配逻辑。下表仅在 **`enableRuleScoring=false`** 且 `configureRedWeaponsByBlue` 末尾调用 `bindTargetIdsForRedWeapons` 时生效。 + +| 参数 | 作用 | 影响什么 | 怎么调整 | +|------|------|----------|----------| +| `enableTargetAutoBind` | 是否执行绑定函数 | `false` 时红方 `targetId` 保持入参/空 | 全手工指定目标时关闭 | +| `minTargetBindRatio` | 最低绑定比例下限 | 绑定数/红武器总数低于该比例时,尝试用 `pickAnyBlueId` 补足 | 想“几乎全要有目标”时调高(如 0.9);允许大量空则调低 | +| `allowReserveWithoutTarget` | 是否允许故意留空 | `true`:允许火力冗余;`false`:尽量全部塞满目标 | 与 `minTargetBindRatio` 配合使用 | +| `w_target_type` | 类型匹配在总分中的权重 | 越大越强调类型对口 | 类型错配不可接受时略调高 | +| `w_target_dist` | 距离权重 | 越大越偏向更近、易打击的蓝目标 | 强调近战压制时调高 | +| `w_target_height` | 高度差权重 | 越大越惩罚高度层不匹配 | 空地混合作战可略调高 | +| `w_target_hit` | 命中率权重 | 越大高 `hitRate` 武器越易抢到好目标 | 强调效费比时调高 | +| `w_target_threat` | 蓝方威胁权重 | 越大越优先高威胁蓝目标 | 要“先打高威胁”时调高 | +| `maxEffectiveDistance` | 配对最大有效距离 | 超过则 `isPairFeasible` 直接否 | 只允许远程交火时调大;强制近战调小 | +| `maxHeightGap` | 允许的最大高度差 | 超过则候选失效 | 高原/低空混编时适度调大 | +| `targetDecayAlpha` | 边际递减系数 | `marginal = score / (1 + alpha*k)`,`k` 为该蓝目标已分配数;**越大越不扎堆** | 避免多机打同一目标时调大 | +| `fallbackToNearestTarget` | 无可行候选时是否按距离回退 | `true` 时尽量给一个最近蓝 `equipmentId` | 不想硬塞错误目标时改 `false` 并配合 `allowUnassignedRedWeapon` | +| `allowUnassignedRedWeapon` | 仍无可行时是否允许 `targetId` 为空 | `true` 允许空;`false` 会再 `pickAnyBlueId` 硬填 | 与火力冗余策略一致即可 | + +**绑定过程(白名单路径,固定实现)**:绑定键为蓝方 `equipmentId` → 逐把红武器在可行集合上算分 → 取边际最高 → 更新已分配计数 → 不足比例再补 → 仍不行则按上表回退或留空。 + +### 2.7 过程驱动整数加分参数(核心,`enableRuleScoring=true`) + +**规则过程**(函数 `runProcessDrivenSelection`):对每个带 `equipmentId` 的蓝方武器抽一条特征 → 在配置给出的红方武器池里用 `scoreRedWeaponByRules` 打**整数分** → 去掉低于 `minScoreToAssign` 的 → **并列最高分全部保留** → 按 `computeNeededRedCountFromFeature` 得到 `needCount`,再被 `minRedWeaponsPerBlueTarget`~`maxRedWeaponsPerBlueTargetCap` 钳位 → 轮询 `topNames` 克隆武器并 `setTargetId(blueId)`。 + +| 参数 | 作用 | 影响什么 | 怎么调整 | +|------|------|----------|----------| +| `enableRuleScoring` | 是否走过程驱动主路径 | `true` 走本节;`false` 回退 2.2 白名单 + 2.6 绑定 | 与旧映射二选一 | +| `minScoreToAssign` | 候选武器最低整数分 | 低于则该红方武器名不参与本蓝目标的并列候选 | 想更少种类参与时调大;想多并列时调小 | +| `nearDefenseDistanceMeters` | 蓝方到防区距离“近”的米阈值 | 与 `score_nearDefense_artillery` 联动:近且炮名命中则加分 | 防区尺度变化时同步改 | +| `highThreatLevel` | 任务威胁等级算“高威胁”的整数阈值 | 与 `score_highThreat_missile` 等联动 | 想更容易触发“高威胁分支”时调低 | +| `highTargetCount` | 蓝方该目标 `number` 的“数量高”阈值 | 与 `score_highCount_artillery` 联动 | 集群目标多时调低 | +| `score_type_antiArmor` | 装甲特征下反坦克类名加分 | 含“反坦克”的红方名更容易进并列最高 | 强化反坦时调大 | +| `score_nearDefense_artillery` | 近防区 + 迫榴/迫击炮名加分 | 近防区时更倾向出炮 | 强调要炮时调大 | +| `score_highThreat_missile` | 高威胁或空中场景下导弹/防空类加分(实现中与多项条件叠加) | 高威胁/空中时防空、导弹类名分更高 | 防空压制想加重时调大 | +| `score_highCount_artillery` | 蓝方数量高时炮类加分 | 多目标时更倾向炮 | 炮火优先时调大 | +| `score_hasMissile_airDefence` | 蓝方有导弹组件时防空/导弹发射车类加分 | 有导弹威胁时更倾向防空与导弹车 | 导弹对抗想加重时调大 | + +**并列全选机制**: + +- 对同一蓝方 `equipmentId`,所有红方候选算分后取 `maxScore`,**凡 `score == maxScore` 的武器名称都进入 `topNames`**。 +- 需要数量大于名称种数时,会在 `allocateRedWeaponsForBlueTarget` 里**轮询** `topNames` 重复添加,故同名武器可出现多把。 +- 另:实现里若估算射程覆盖蓝方距离(含安全裕量)还会 **+1 分**,便于优先能打的装备。 + +### 2.8 命中率与动态火力(`命中率规则-动态数量与offset`,salience 52) + +**规则过程**:在装配之后,对**已有**红方武器的 `number` 做增量:优先读 `offsetCsvByWeapon` 显式 offset;否则用命中率与阈值的 **gap 阶梯** 加 `hitRateStep*Offset`。 + +| 参数 | 作用 | 影响什么 | 怎么调整 | +|------|------|----------|----------| +| `hitRateCsv` | 按武器名配置命中率 | `applyDefaultHitRateIfAbsent` 与 gap 计算 | 调整个体武器假定命中能力 | +| `defaultHitRateFallback` | 未配置且武器无 `hitRate` 时 | gap 计算用的默认 p | 整体偏乐观/悲观时改 | +| `desiredKillProbability` | 过程驱动里毁伤置信目标 | `computeNeededRedCountFromFeature` 中 `computeRequiredShots` 的输入 | 想“打得更保险”时调高(需求发数可能变多) | +| `offsetCsvByWeapon` | **最高优先级**数量偏移 | 命中武器名则 **忽略** gap 阶梯,直接 `base + offset` | 某武器必须固定加 N 发时配置 | +| `hitRateThreshold` | 命中率门槛 | `gap = threshold - hitRate`,决定进哪一档增量 | 门槛越高,低命中武器越易吃到正 offset | +| `hitRateGapStep1Max` / `hitRateGapStep2Max` | gap 的一、二档上界 | 分档加 `hitRateStep1Offset` 或 `hitRateStep2Offset` | 拉宽档位可减少落到高档 | +| `hitRateStep1Offset` / `hitRateStep2Offset` | 各档对 `number` 的加量 | 直接增加 `weapon.number` | 低命中时想多压弹药则调大 | +| `minRedWeaponsPerBlueTarget` | 每蓝目标至少分配红武器数下限 | 过程驱动里与 `needCount` 取大 | 强制“至少 N 把伺候一个目标” | +| `maxRedWeaponsPerBlueTargetCap` | 每蓝目标分配上限 | 与 `needCount` 取小,防止无限膨胀 | 控制火力上限 | +| `enableDynamicMultiRedPerBlue` | 配置项(当前实现**未读取**) | 预留;**改之暂无效果**,以 `desiredKillProbability` + min/max 钳位为准 | 待代码接入后再用 | + +**阶梯优先级(与代码一致)**: + +1. `offsetCsvByWeapon` 命中 → 只用显式 offset。 +2. 否则 `gap <= 0` → `+0`。 +3. `0 < gap <= hitRateGapStep1Max` → `+hitRateStep1Offset`。 +4. `hitRateGapStep1Max < gap <= hitRateGapStep2Max` → `+hitRateStep2Offset`。 +5. `gap > hitRateGapStep2Max` → 仍按 **第二档 offset**(避免数量无上限膨胀)。 + +### 2.9 阵位规则参数(`阵位规则` 49→48) + +**规则过程**:`prepareDeploymentPools` 用作战区/防区多边形生成候选点网格;`applyWeaponDeployment` 用蓝方航迹与防区关系得到 **部署模式** `deployMode`(近快/远快/近慢/默认)→ 选锚点 → `resolveFormationType`(人工阵型 / 自动阵型 / 默认)→ `buildFormationOffsets` 展开队形 → 写入 `weapon.coordinate`;地面高度固定、空中可走自动生成高度。 + +**输入依赖**:`blueTask.warZoneLocation`、`blueTask.defZoneLocation`(各 4 点经纬);部署模式还依赖 `trackPoints`(算速度与距防区)。 + +#### 2.9.1 总开关与点位 + +| 参数 | 作用 | 影响什么 | 怎么调整 | +|------|------|----------|----------| +| `enablePositionRules` | 阵位总开关 | `false` 时跳过点位池与赋位 | 仅要装配不要坐标时关闭 | +| `fireUnitSpacingMeters` | 作战区/防区内网格点间距(米) | 点密度:越小点越多、部署越密 | 地形细粒度要求高调小 | +| `airDeployZonePreference` | 空中锚点优先作战区还是防区 | `combat`/`defense` 影响 `pickAnchorByMode` 选取 | 想飞机靠前压则偏 `combat` | +| `defensePriorityWeapons` | 名单内武器优先取防区点 | 名单武器在无锚点回退时优先 `defense` 池 | 要扼守防区的装备写入 | + +#### 2.9.2 部署模式(与 `speedFastThreshold`、距离阈值) + +| 参数 | 作用 | 影响什么 | 怎么调整 | +|------|------|----------|----------| +| `speedFastThreshold` | 蓝方平均速度是否算“快” | 与 `distanceNearDefenseThresholdMeters`、`distanceFarDefenseThresholdMeters` 组合成 **near_fast / far_fast / near_slow / default** | 高速标准提高则更少判成“快” | +| `distanceNearDefenseThresholdMeters` | 蓝方航迹末端距防区“近”的米阈值 | 参与部署模式与锚点策略 | 防区尺度大时略调大 | +| `distanceFarDefenseThresholdMeters` | “远”阈值 | 同上 | 与上配合拉开近/远带 | + +#### 2.9.3 高度(地面固定 / 空中可自动) + +| 参数 | 作用 | 影响什么 | 怎么调整 | +|------|------|----------|----------| +| `groundDeployHeight` | 地面武器统一高度 | 地面编队 `moveCoordinateByMeters` / 克隆点高度 | 地表 DEM 约定高度 | +| `airDeployHeight` | 关闭自动或兜底时用 | 过程驱动关闭自动高度时的固定空高 | 与 `airHeightFallback` 一般同量级 | +| `enableAutoAirDeployHeight` | 空中是否按态势算高 | `false` 时空中全程用 `airDeployHeight` | 想完全手工高时关 | +| `airHeightFallback` | 无航迹且无蓝方武器高度时的基准 | `resolveBlueBaseHeight` 最后兜底 | 与默认战术层一致即可 | +| `airHeightMin` / `airHeightMax` | 自动高度钳位 | 防止算出过矮/过高 | 任务高度包线 | +| `airHeightSpeedThreshold` / `airHeightAdjustFast` | 快于阈值则加高度 | 快目标对应更高拦截层 | 拦截想更猛则加 `AdjustFast` | +| `airHeightNearDefenseDistance` / `airHeightFarDefenseDistance` | 距防区近/远分界 | 近用 `airHeightAdjustNear`(多为负)、远用 `airHeightAdjustFar` | 调整防区内外高度差 | +| `airHeightAdjustMain` / `airHeightAdjustWing` | 主僚高度差 | 主机略高、僚机略低 | 战术队形垂直疏开 | + +#### 2.9.4 阵型类型(人工 / 自动 / 默认) + +**说明(与部署模式共用阈值)**:`resolveDeployModeByBlueState` 用 **`speedFastThreshold`**、`distanceNearDefenseThresholdMeters`、`distanceFarDefenseThresholdMeters` 得到 `near_fast / far_fast / near_slow / default`,`autoSelectFormationType` 直接消费该 `deployMode` 字符串选 `formationRule_*`。配置里的 **`formationFastSpeedThreshold`、`formationNearDefenseDistance`、`formationFarDefenseDistance` 当前未参与运算**,调整无效;若要与部署模式解耦,需开发在代码中改为读取这三项。 + +| 参数 | 作用 | 影响什么 | 怎么调整 | +|------|------|----------|----------| +| `formationDefaultType` | 阵型名兜底 | 非法或未选时 `normalizeFormationType` 回退 | 填 `TRIANGLE` 等合法枚举 | +| `enableAutoFormationSelect` | 是否 `autoSelectFormationType` | `false` 时只用 Task 指定或默认 | 想完全人工阵型时关 | +| `formationHighThreatLevel` | 威胁≥该值走 `formationRule_high_threat` | **优先于**速度距离分支 | 想更早用“高威胁阵型”则调低 | +| `formationLargeGroupCount` | **大编队**阈值 | 比较 **`redTask.taskWeapons.size()`(红方武器条数)** ≥ 阈值则走 `formationRule_large_group`(不是蓝方数量) | 想更早切换“大编队阵型”则调小 | +| `formationRule_near_fast` 等 | 各分支对应阵型名 | 决策序:**高威胁 > 近快/远快/近慢 > 大编队(红方条数)> 空中占比≥0.5 > default** | 改字符串即改映射 | +| `formationRule_air_majority` | 红方空中占比高时阵型 | `computeRedAirRatio` ≥ 0.5 | 无人机群战术时调整 | + +#### 2.9.5 间距、主僚距、朝向(自动 + 兜底) + +| 参数 | 作用 | 影响什么 | 怎么调整 | +|------|------|----------|----------| +| `formationDefaultSpacingMeters` | 阵型间距兜底 | 自动间距失败时用 | 与最小安全间距匹配 | +| `formationSpacingMinMeters` / `formationSpacingMaxMeters` | 自动间距上下界 | 限制 `resolveFormationSpacing` 输出 | 地形窄/宽时收放 | +| `defenseScaleMinMeters` / `defenseScaleMaxMeters` | 防区尺度映射到间距的范围 | 防区越大间距可越大 | 与防区实际大小一致 | +| `mainWingDistanceDefaultMeters` | 主僚距兜底 | 自动主僚距失败时 | 编队纵向尺度 | +| `mainWingDistanceMinMeters` / `mainWingDistanceMaxMeters` | 主僚距钳位 | 限制自动主僚距 | 避免过近相撞 | +| `mainWingDistanceModeFactor_near_fast` 等 | 部署模式对主僚距的乘子 | 近快更紧、远快更疏等 | 战术疏密 | +| `formationHeadingDefaultDeg` | 朝向兜底 | 无航迹、无 Task 朝向时 | 默认机头方向 | + +**实体覆盖优先级(与代码一致)**: + +- 阵型类型:`Task.formationType`(非空)> 自动选阵型 > `formationDefaultType`。 +- 间距:`Task.formationSpacingMeters` > 防区尺度自动 > `formationDefaultSpacingMeters`。 +- 主僚距:`Weapon.wingRelativeDistanceMeters` > `Task.mainWingDistanceMeters` > 自动 > `mainWingDistanceDefaultMeters`。 +- 朝向:`Task.formationHeadingDeg` > 航迹末段方向 > `formationHeadingDefaultDeg`。 + +**实体字段建议**:`Task`:`formationType`、`formationSpacingMeters`、`mainWingDistanceMeters`、`formationHeadingDeg`;`Weapon`:`formationRole`、`wingRelativeDistanceMeters`、`wingRelativeBearingDeg`。 阵位输入示例(仅经纬度,4点): @@ -133,7 +268,7 @@ } ``` -### 2.9 航迹规则参数(新增) +### 2.10 航迹规则参数(新增) - `enableTrajectoryRules`:航迹规则总开关。 - `strategyMode`:`auto/shortest/flank/interfere`。 - `auto`:智能选择策略。 @@ -159,38 +294,40 @@ ## 3. 当前规则行为(简版) - `装备组件匹配`、`组件参数匹配`:已作为 `legacy` 占位,不承担当前业务决策。 -- 主决策在 `红方武器自适应装配规则`:调用 `configureRedWeaponsByBlue(...)`,按“映射配置”添加武器。 +- 主决策在 `红方武器自适应装配规则`:调用 `configureRedWeaponsByBlue(...)`,默认走“过程驱动整数加分选武器”。 +- 过程驱动链路:蓝方特征提取 -> 候选整数加分 -> 最高分并列全选 -> 动态分配数量与 targetId。 - 导弹增强在 `导弹联动增强规则`:调用 `applyMissileLinkage(...)`,受开关和阈值控制。 -- 全组件数量匹配在 `全组件数量匹配规则`:按红方 `targetId` 绑定蓝方装备,覆盖非导弹组件 `componentParams[0].number`;找不到组件/targetId 允许跳过。 -- 命中率驱动数量在 `命中率规则-动态数量与offset`:按 `hitRate` 与目标毁伤概率推导火力数量;显式 offset 配置优先。 +- `全组件数量匹配规则` 当前语义已收敛为“仅导弹组件匹配”:只改导弹组件数量与首参数,非导弹不改。 +- 命中率驱动数量在 `命中率规则-动态数量与offset`:按 `hitRateThreshold` 与 `gap` 阶梯增量计算数量;显式 offset 配置优先。 - 任务命名在 `任务自动匹配规则`:调用 `assignTaskNameByRedWeapons(...)`,按红方最终武器自动生成任务名和 `dataType`。 - 炮类约束:命中炮类条件时,炮类武器只保留 `炮弹` 组件,单位 `范围米`。 -- `targetId` 绑定:在装配后自动执行,按命中率动态给蓝目标分配多个红方武器(受上下限约束),允许少量空值冗余。 -- 阵位部署:按多边形区域和武器类型自动赋位,保证防区火力覆盖。 +- `targetId`:**过程驱动**下在装配时直接写入对应蓝方 `equipmentId`;**白名单路径**下用“可行性 + 五维评分 + 边际递减”分配(见 2.6)。 +- 阵位部署:按“速度距离驱动锚点 + 阵型偏移 + 主僚机相对位移”自动赋位,最终输出经纬高。 - 射程合理性在 `射程合理性校验规则`:基于蓝/红武器坐标计算距离,自动避免“射程不足却打击”的不合理情况(可自动调参)。 - 航迹生成:根据蓝方 `trackPoints` 生成红方 `trackPoints`,点数与蓝方一致,支持三套策略和智能选择。 -## 3.1 任务名称自动匹配(新增) +## 3.1 任务名称自动匹配(`任务自动匹配规则`,salience 50) -任务命名依据:**红方最终武器**(不是蓝方任务名关键字)。 +**规则过程**:在武器与数量基本落定之后,按 **红方最终 `taskWeapons` 列表** 做分类(不看蓝方 `drawName` 关键字),写入 `redTask.drawName` 与 `redTask.dataType`。 -当前分类优先级: -- 导弹突击(导弹发射车) -- 防空压制(防空导弹武器/火力打击无人机/武装直升机) -- 反装甲打击(反坦克火箭/反坦克导弹系统) -- 炮火压制(迫榴炮/车载迫击炮) -- 通用打击(兜底) +**分类优先级(先命中先生效)**: -业务可调模板(在 `buildBusinessConfig()`): -- `taskName_missile_strike` / `taskDataType_missile_strike` -- `taskName_air_defence` / `taskDataType_air_defence` -- `taskName_anti_armor` / `taskDataType_anti_armor` -- `taskName_artillery` / `taskDataType_artillery` -- `taskName_general` / `taskDataType_general` +1. 导弹突击:含导弹发射车类武器名 +2. 防空压制:防空导弹 / 火力打击无人机 / 武装直升机 +3. 反装甲打击:反坦克火箭 / 反坦克导弹系统 +4. 炮火压制:迫榴炮 / 车载迫击炮 +5. 通用打击:兜底 -效果说明: -- 只改这些模板文字,不改函数,也能改变最终任务展示名。 -- 若分类与武器不一致,会自动回落到 `通用打击任务`,避免“任务名和武器不符”。 +| 参数 | 作用 | 影响什么 | 怎么调整 | +|------|------|----------|----------| +| `taskName_missile_strike` | 导弹突击类展示名 | `drawName` 当命中导弹突击分支时 | 改中文任务标题 | +| `taskDataType_missile_strike` | 导弹突击类业务类型 | `dataType` 同步写入 | 与前端/报表枚举对齐 | +| `taskName_air_defence` / `taskDataType_air_defence` | 防空压制类 | 同上 | 同上 | +| `taskName_anti_armor` / `taskDataType_anti_armor` | 反装甲类 | 同上 | 同上 | +| `taskName_artillery` / `taskDataType_artillery` | 炮火类 | 同上 | 同上 | +| `taskName_general` / `taskDataType_general` | 兜底类 | 无上述特征或冲突回落时使用 | 默认任务名与类型 | + +**效果说明**:只改模板字符串即可改展示;若红方武器组合与期望分类不一致,会落到 `taskName_general`,避免“名不副实”。 ## 4. 快速修改示例(业务常用) @@ -330,7 +467,7 @@ - **联动门控**:`applyMissileLinkage(...)` 必须同时满足: - `enableMissileLinkage=true` - 蓝方导弹数量 `>= minBlueMissileCountForLinkage` -- **目标绑定**:`bindTargetIdsForRedWeapons(...)` 基于蓝方 `equipmentId` 分配 `targetId`,支持“优先匹配 + 绑定率阈值 + 冗余空目标”。 +- **目标绑定**:`bindTargetIdsForRedWeapons(...)` 基于蓝方 `equipmentId` 执行“可行性过滤 + 打分 + 边际收益递减分配 + 回退兜底”。 - **阵位部署**:`prepareDeploymentPools(...)` + `applyWeaponDeployment(...)` 负责区域解析、点位生成与部署赋位。 - **航迹生成**:`applyTrajectoryGeneration(...)` + `chooseTrajectoryStrategy(...)` + `generateRedTrackPoints(...)` 负责红方航迹策略生成。 - **任务命名**:`assignTaskNameByRedWeapons(...)` 仅基于红方最终武器,避免旧版按蓝方 `drawName` 关键字造成误判。 diff --git a/auto-solution-rule/src/main/resources/rules/fire-rule.drl b/auto-solution-rule/src/main/resources/rules/fire-rule.drl index c12e774..af8804a 100644 --- a/auto-solution-rule/src/main/resources/rules/fire-rule.drl +++ b/auto-solution-rule/src/main/resources/rules/fire-rule.drl @@ -130,14 +130,17 @@ function Map buildBusinessConfig() { cfg.put("allowMultiGroup", Boolean.TRUE); // true=允许多组叠加,false=命中首组即停止 cfg.put("enableArmedHelicopterOnAir", Boolean.TRUE); - // ---------- 蓝方类型 -> 红方武器方案(多选映射,可改) ---------- - // 逗号分隔,示例:反坦克火箭,反坦克导弹系统 - // 若为空或写了非法武器名,则该组不触发(允许不命中) - cfg.put("map_air_targets", "防空导弹武器,火力打击无人机,武装直升机"); - cfg.put("map_ground_targets", "迫榴炮,车载迫击炮"); - cfg.put("map_armor_targets", "反坦克火箭,反坦克导弹系统"); - cfg.put("map_artillery_targets", "迫榴炮,车载迫击炮"); - cfg.put("map_missile_targets", "导弹发射车"); + // ---------- 过程驱动整数加分引擎(可改) ---------- + cfg.put("enableRuleScoring", Boolean.TRUE); // true=过程驱动整数加分;false=回退旧白名单 + cfg.put("minScoreToAssign", 1); // 最低入选分(整数) + cfg.put("nearDefenseDistanceMeters", 500); // 近防区距离阈值 + cfg.put("highThreatLevel", 3); // 高威胁阈值 + cfg.put("highTargetCount", 2); // 蓝方数量阈值 + cfg.put("score_type_antiArmor", 1); // 蓝方装甲 -> 反坦克类加分 + cfg.put("score_nearDefense_artillery", 1); // 近防区 -> 迫榴炮类加分 + cfg.put("score_highThreat_missile", 1); // 高威胁 -> 导弹/防空类加分 + cfg.put("score_highCount_artillery", 1); // 蓝方数量高 -> 炮类加分 + cfg.put("score_hasMissile_airDefence", 1); // 蓝方有导弹 -> 防空类加分 // ---------- 数量与参数(可改) ---------- cfg.put("defaultAirNum", 1); @@ -149,25 +152,32 @@ function Map buildBusinessConfig() { cfg.put("blueMissileRangeDefault", 220); // 蓝方导弹范围默认值 cfg.put("minBlueMissileCountForLinkage", 1); // 联动触发门槛 - // ---------- 命中率与动态火力(可改) ---------- + + // ---------- 命中率与动态火力(可改) ---------- cfg.put("hitRateCsv", "防空导弹武器=0.72,火力打击无人机=0.62,武装直升机=0.68,反坦克火箭=0.55,反坦克导弹系统=0.78,迫榴炮=0.45,车载迫击炮=0.43,导弹发射车=0.82"); cfg.put("defaultHitRateFallback", "0.6"); // 兜底命中率 cfg.put("desiredKillProbability", "0.9"); // 期望毁伤置信度 cfg.put("offsetCsvByWeapon", ""); // 显式offset(武器=offset)优先,例:反坦克火箭=1,武装直升机=2 + cfg.put("hitRateThreshold", "0.7"); // 命中率阈值:低于该值触发阶梯增量 + cfg.put("hitRateGapStep1Max", "0.5"); // gap第一档上界(gap=threshold-hitRate) + cfg.put("hitRateGapStep2Max", "1.0"); // gap第二档上界 + cfg.put("hitRateStep1Offset", 1); // 第一档固定增量 + cfg.put("hitRateStep2Offset", 2); // 第二档固定增量 cfg.put("enableDynamicMultiRedPerBlue", Boolean.TRUE); // 按命中率动态决定每个蓝目标需要几个红武器 cfg.put("minRedWeaponsPerBlueTarget", 1); // 每个蓝目标最少分配红武器数量 cfg.put("maxRedWeaponsPerBlueTargetCap", 3); // 每个蓝目标最多分配红武器数量上限 cfg.put("enableRangeSanityCheck", Boolean.TRUE); // 距离-射程合理性校验开关 cfg.put("enableAutoRangeRecommend", Boolean.TRUE); // 射程不足时自动调参 - cfg.put("rangeSafetyMarginMeters", 50); // 射程安全裕量 + cfg.put("rangeSafetyMarginMeters", 50); // 射程安全余量 + //还是简单 - // ---------- 全组件数量匹配(可改) ---------- - // 逻辑:按红方武器 targetId 找到对应蓝方装备(equipmentId),然后覆盖所有“非导弹组件”的数量 + // ---------- 仅导弹组件匹配(可改) ---------- + // 逻辑:按红方武器 targetId 找到对应蓝方装备(equipmentId),仅覆盖“导弹组件”的数量与首参数 cfg.put("enableComponentQuantityMatch", Boolean.TRUE); // 格式示例:蓝穿甲弹->红穿甲弹,蓝火控雷达->红火控雷达;为空则默认 deviceName 一致匹配 cfg.put("componentDeviceNameMappingCsv", ""); - // 跳过覆盖:蓝方组件 deviceName 包含该关键词则不覆盖(默认用来保留导弹联动偏移) - cfg.put("skipMissileComponentsByNameContains", "导弹"); + // 仅匹配:蓝方组件 deviceName 包含该关键词时才匹配 + cfg.put("missileComponentNameContains", "导弹"); // ---------- 任务自动命名模板(可改) ---------- // 任务分类优先级:导弹突击 > 防空压制 > 反装甲打击 > 炮火压制 > 通用打击 @@ -186,6 +196,17 @@ function Map buildBusinessConfig() { cfg.put("enableTargetAutoBind", Boolean.TRUE); // 是否自动给红方武器绑定蓝方目标 cfg.put("minTargetBindRatio", "0.7"); // 最低绑定比例(大部分有目标) cfg.put("allowReserveWithoutTarget", Boolean.TRUE); // 允许少量武器 targetId 为空(火力冗余) + cfg.put("w_target_type", "0.30"); // target分配:类型匹配权重 w_target_type 越大,规则越“看重类型对口”。 + cfg.put("w_target_dist", "0.25"); // target分配:距离权重 w_target_dist 越大,系统越偏向“更近/更容易打到”的目标 射程不足会被直接淘汰,不参与加权计算 + cfg.put("w_target_height", "0.10"); // target分配:高度差权重 作用:w_target_height 越大,系统越偏向“高度层更匹配”的目标(例如空中平台对空中目标、地面平台对地面目标更自然) + cfg.put("w_target_hit", "0.20"); // target分配:命中率权重 w_target_hit 越大,高命中率武器越容易拿到更高分,从而优先绑定目标。 + cfg.put("w_target_threat", "0.15"); // target分配:蓝目标威胁权重 作用:w_target_threat 越大,红武器会更倾向优先打“高威胁蓝目标”,即使距离稍远或类型略次优也可能被拉高排名 + cfg.put("maxEffectiveDistance", 3000); // 超过该距离候选失效 + cfg.put("maxHeightGap", 800); // 超过该高度差候选失效 + cfg.put("targetDecayAlpha", "0.35"); // 边际收益递减系数 + cfg.put("fallbackToNearestTarget", Boolean.TRUE); // 候选失效时回退最近目标 + cfg.put("allowUnassignedRedWeapon", Boolean.TRUE); // 无有效候选时允许红方targetId为空 + //权重怎么用的算法要研究 // ---------- 阵位规则参数(可改) ---------- cfg.put("enablePositionRules", Boolean.TRUE); // 阵位规则总开关 @@ -194,12 +215,54 @@ function Map buildBusinessConfig() { cfg.put("airDeployZonePreference", "combat"); // 飞机优先部署区:combat/defense cfg.put("defensePriorityWeapons", "反坦克导弹系统,反坦克火箭,车载迫击炮,迫榴炮"); // 优先部署防区武器 cfg.put("groundDeployHeight", 20); // 地面武器部署高度 - cfg.put("airDeployHeight", 300); // 空中武器部署高度 + cfg.put("airDeployHeight", 300); // 空中武器部署高度(自动高度关闭或兜底时使用) + cfg.put("enableAutoAirDeployHeight", Boolean.TRUE); // 空中部署高度自动生成开关 + cfg.put("airHeightMin", 50); // 自动高度下限 + cfg.put("airHeightMax", 20000); // 自动高度上限 + cfg.put("airHeightFallback", 300); // 无航迹/无蓝方武器高度时的兜底高度 + cfg.put("airHeightSpeedThreshold", 180); // 速度修正:平均速度≥此值视为“快” + cfg.put("airHeightAdjustFast", 40); // 速度快时额外抬高(米) + cfg.put("airHeightNearDefenseDistance", 800); // 距防区“近”阈值(米) + cfg.put("airHeightFarDefenseDistance", 2500); // 距防区“远”阈值(米) + cfg.put("airHeightAdjustNear", -30); // 近防区高度修正(通常为负,压低) + cfg.put("airHeightAdjustFar", 40); // 远防区高度修正(抬高) + cfg.put("airHeightAdjustMain", 20); // 主机相对基准额外高度 + cfg.put("airHeightAdjustWing", -10); // 僚机相对基准额外高度 + cfg.put("formationDefaultType", "TRIANGLE"); // 默认阵型:TRIANGLE/DIAMOND/LINE/COLUMN/WEDGE + cfg.put("enableAutoFormationSelect", Boolean.TRUE); // 阵型自动选择开关 + cfg.put("formationFastSpeedThreshold", 180); // 自动选阵型:高速阈值 + cfg.put("formationNearDefenseDistance", 800); // 自动选阵型:近防区阈值 + cfg.put("formationFarDefenseDistance", 2500); // 自动选阵型:远防区阈值 + cfg.put("formationHighThreatLevel", 3); // 自动选阵型:高威胁阈值 + cfg.put("formationLargeGroupCount", 6); // 自动选阵型:大编队阈值 + cfg.put("formationRule_near_fast", "WEDGE"); // 近快态势阵型 + cfg.put("formationRule_far_fast", "LINE"); // 远快态势阵型 + cfg.put("formationRule_near_slow", "DIAMOND"); // 近慢态势阵型 + cfg.put("formationRule_high_threat", "DIAMOND"); // 高威胁优先阵型 + cfg.put("formationRule_large_group", "COLUMN"); // 大编队优先阵型 + cfg.put("formationRule_air_majority", "LINE"); // 空中占比高阵型 + cfg.put("formationRule_default", "TRIANGLE"); // 自动选择默认阵型 + cfg.put("formationDefaultSpacingMeters", 120); // 阵型间距兜底(自动计算失败时) + cfg.put("mainWingDistanceDefaultMeters", 100); // 主僚距离兜底(自动计算失败时) + cfg.put("formationHeadingDefaultDeg", 0); // 默认阵型朝向 + cfg.put("formationSpacingMinMeters", 60); // 自动间距下限 + cfg.put("formationSpacingMaxMeters", 220); // 自动间距上限 + cfg.put("defenseScaleMinMeters", 300); // 防区尺度映射下限 + cfg.put("defenseScaleMaxMeters", 3000); // 防区尺度映射上限 + cfg.put("mainWingDistanceMinMeters", 60); // 自动主僚距离下限 + cfg.put("mainWingDistanceMaxMeters", 260); // 自动主僚距离上限 + cfg.put("mainWingDistanceModeFactor_near_fast", "0.90"); // 近快:更紧凑 + cfg.put("mainWingDistanceModeFactor_far_fast", "1.15"); // 远快:更疏开 + cfg.put("mainWingDistanceModeFactor_near_slow", "0.95"); // 近慢:略紧凑 + cfg.put("mainWingDistanceModeFactor_default", "1.00"); // 默认系数 + cfg.put("speedFastThreshold", 180); // 蓝方高速阈值 + cfg.put("distanceNearDefenseThresholdMeters", 800); // 近防区阈值 + cfg.put("distanceFarDefenseThresholdMeters", 2500); // 远防区阈值 // ---------- 航迹规则参数(可改) ---------- cfg.put("enableTrajectoryRules", Boolean.TRUE); // 航迹规则总开关 // 智能 最短的 侧面 干扰 - cfg.put("strategyMode", "auto"); // auto/shortest/flank/interfere + cfg.put("strategyMode", "shortest"); // auto/shortest/flank/interfere cfg.put("enableShortest", Boolean.TRUE); cfg.put("enableFlank", Boolean.TRUE); cfg.put("enableInterfere", Boolean.TRUE); @@ -240,8 +303,8 @@ then end //------------------------------------------------------------------------------- -// 全组件数量匹配:按 redWeapon.targetId 对应蓝方装备,覆盖“非导弹组件”的数量 -rule "全组件数量匹配规则" +// 仅导弹组件匹配:按 redWeapon.targetId 对应蓝方装备,仅覆盖导弹组件的数量与首参数 +rule "导弹组件数量匹配规则" agenda-group "打击任务" salience 53 when @@ -345,6 +408,12 @@ function void configureRedWeaponsByBlue( // 蓝方组件模板(用于测试和参数联动):若蓝方没填组件,给一个合理默认值 buildBlueTestComponents(blueWeapons); + // 过程驱动模式:根据蓝方类型/数量/位置等态势特征评分选武器 + if (readBooleanCfg(cfg, "enableRuleScoring", true)) { + runProcessDrivenSelection(fact, cfg); + return; + } + boolean hasBlueAir = false; boolean hasBlueGround = false; boolean hasBlueArtillery = false; @@ -432,6 +501,329 @@ function void configureRedWeaponsByBlue( bindTargetIdsForRedWeapons(redWeapons, blueWeapons, cfg); } +//------------------------------------------------------------------------------- +// 过程驱动主流程:特征提取 -> 评分选武器 -> 动态分配 +function void runProcessDrivenSelection(FactTask fact, Map cfg) { + if (fact == null || fact.getBlueTask() == null || fact.getRedTask() == null) { + return; + } + Task blueTask = fact.getBlueTask(); + Task redTask = fact.getRedTask(); + List blueWeapons = blueTask.getTaskWeapons(); + if (blueWeapons == null || blueWeapons.isEmpty()) { + return; + } + List redWeapons = redTask.getTaskWeapons(); + if (redWeapons == null) { + redWeapons = new ArrayList(); + redTask.setTaskWeapons(redWeapons); + } else { + redWeapons.clear(); + } + + List blueFeatures = extractBlueTargetFeatures(blueTask, cfg); + if (blueFeatures == null || blueFeatures.isEmpty()) { + return; + } + List redPool = getRedWeaponPoolByConfig(cfg); + if (redPool == null || redPool.isEmpty()) { + return; + } + + double desiredKill = normalizeProbability(readDoubleCfg(cfg, "desiredKillProbability", 0.9d), 0.9d); + int minAssign = readIntCfg(cfg, "minRedWeaponsPerBlueTarget", 1); + int maxAssign = readIntCfg(cfg, "maxRedWeaponsPerBlueTargetCap", 3); + if (maxAssign <= 0) { + maxAssign = 1; + } + if (minAssign < 0) { + minAssign = 0; + } + if (minAssign > maxAssign) { + minAssign = maxAssign; + } + int minScore = readIntCfg(cfg, "minScoreToAssign", 1); + + for (Object featObj : blueFeatures) { + Map feat = (Map) featObj; + String blueId = String.valueOf(feat.get("blueId")); + Weapon blueWeapon = (Weapon) feat.get("blueWeapon"); + if (blueWeapon == null || isBlank(blueId)) { + continue; + } + + List topNames = pickTopScoredWeapons(feat, redPool, cfg, minScore); + if (topNames == null || topNames.isEmpty()) { + continue; + } + int needCount = computeNeededRedCountFromFeature(feat, cfg, desiredKill); + allocateRedWeaponsForBlueTarget(topNames, needCount, minAssign, maxAssign, blueId, redWeapons, cfg); + } + + if (!redWeapons.isEmpty()) { + applyDefaultHitRateIfAbsent(redWeapons, cfg); + if (containsBlueArtillery(blueWeapons)) { + limitRedArtilleryToShellOnly(redWeapons, (String) cfg.get("shellRangeDefault")); + } + } +} + +function List extractBlueTargetFeatures(Task blueTask, Map cfg) { + List result = new ArrayList(); + if (blueTask == null || blueTask.getTaskWeapons() == null) { + return result; + } + int threat = parseIntSafe(blueTask.getThreatLevel(), 1); + for (Object obj : blueTask.getTaskWeapons()) { + Weapon blue = (Weapon) obj; + if (blue == null || isBlank(blue.getEquipmentId())) { + continue; + } + Map feature = new java.util.HashMap(); + feature.put("blueId", blue.getEquipmentId()); + feature.put("blueWeapon", blue); + feature.put("isAir", Boolean.valueOf(isAirWeapon(blue))); + feature.put("isArmor", Boolean.valueOf(isArmorWeapon(blue))); + feature.put("isArtillery", Boolean.valueOf(isArtilleryWeapon(blue))); + feature.put("isGround", Boolean.valueOf(isGroundWeapon(blue))); + feature.put("hasMissile", Boolean.valueOf(hasMissileComponent(blue))); + int blueNum = blue.getNumber() == null || blue.getNumber() <= 0 ? 1 : blue.getNumber().intValue(); + feature.put("blueNum", Integer.valueOf(blueNum)); + feature.put("threat", Integer.valueOf(threat)); + feature.put("distance", Double.valueOf(computeBlueDistanceToDefenseMeters(blue, blueTask))); + result.add(feature); + } + return result; +} + +function List pickTopScoredWeapons(Map feature, List redPool, Map cfg, int minScoreToAssign) { + List result = new ArrayList(); + if (feature == null || redPool == null || redPool.isEmpty()) { + return result; + } + int maxScore = Integer.MIN_VALUE; + for (Object obj : redPool) { + String redName = String.valueOf(obj); + if (isBlank(redName)) { + continue; + } + int s = scoreRedWeaponByRules(feature, redName, cfg); + if (s > maxScore) { + maxScore = s; + } + } + if (maxScore < minScoreToAssign) { + return result; + } + for (Object obj : redPool) { + String redName = String.valueOf(obj); + if (isBlank(redName)) { + continue; + } + int s = scoreRedWeaponByRules(feature, redName, cfg); + if (s == maxScore) { + addUnique(result, redName); + } + } + return result; +} + +function int scoreRedWeaponByRules(Map feature, String redName, Map cfg) { + if (feature == null || redName == null) { + return 0; + } + int score = 0; + boolean isAir = ((Boolean) feature.get("isAir")).booleanValue(); + boolean isArmor = ((Boolean) feature.get("isArmor")).booleanValue(); + boolean isArtillery = ((Boolean) feature.get("isArtillery")).booleanValue(); + boolean hasMissile = ((Boolean) feature.get("hasMissile")).booleanValue(); + int blueNum = ((Integer) feature.get("blueNum")).intValue(); + int threat = ((Integer) feature.get("threat")).intValue(); + double distance = ((Double) feature.get("distance")).doubleValue(); + + if (isArmor && redName.contains("反坦克")) { + score += readIntCfg(cfg, "score_type_antiArmor", 1); + } + if (isAir && (redName.contains("防空导弹") || redName.contains("无人机") || redName.contains("直升机"))) { + score += readIntCfg(cfg, "score_highThreat_missile", 1); + } + if (isArtillery && (redName.contains("迫榴炮") || redName.contains("迫击炮"))) { + score += 1; + } + if (distance <= readIntCfg(cfg, "nearDefenseDistanceMeters", 500) && redName.contains("迫榴炮")) { + score += readIntCfg(cfg, "score_nearDefense_artillery", 1); + } + if (threat >= readIntCfg(cfg, "highThreatLevel", 3) && (redName.contains("导弹") || redName.contains("防空"))) { + score += readIntCfg(cfg, "score_highThreat_missile", 1); + } + if (blueNum >= readIntCfg(cfg, "highTargetCount", 2) && (redName.contains("迫榴炮") || redName.contains("迫击炮"))) { + score += readIntCfg(cfg, "score_highCount_artillery", 1); + } + if (hasMissile && (redName.contains("防空导弹") || redName.contains("导弹发射车"))) { + score += readIntCfg(cfg, "score_hasMissile_airDefence", 1); + } + + int estRange = estimateRangeByRedWeaponName(redName); + int margin = readIntCfg(cfg, "rangeSafetyMarginMeters", 50); + if (distance < Double.MAX_VALUE / 2.0d && estRange >= (int) Math.ceil(distance) + margin) { + score += 1; + } + return score; +} + +function void allocateRedWeaponsForBlueTarget(List topNames, int needCount, int minAssign, int maxAssign, String blueId, List redWeapons, Map cfg) { + if (topNames == null || topNames.isEmpty() || isBlank(blueId) || redWeapons == null) { + return; + } + int finalNeed = needCount; + if (finalNeed < minAssign) { + finalNeed = minAssign; + } + if (finalNeed > maxAssign) { + finalNeed = maxAssign; + } + int assigned = 0; + while (assigned < finalNeed) { + for (Object obj : topNames) { + if (assigned >= finalNeed) { + break; + } + String redName = String.valueOf(obj); + Weapon red = createRedWeaponInstance(redName, inferSupportTypeByWeaponName(redName), 1); + red.setTargetId(blueId); + ensureBasicRedComponents(red); + red.setHitRate(Double.valueOf(resolveHitRateByName(redName, cfg))); + redWeapons.add(red); + assigned++; + } + } +} + +function int computeNeededRedCountFromFeature(Map feature, Map cfg, double desiredKill) { + if (feature == null) { + return 1; + } + int blueNum = ((Integer) feature.get("blueNum")).intValue(); + int threat = ((Integer) feature.get("threat")).intValue(); + double distance = ((Double) feature.get("distance")).doubleValue(); + double pBase = normalizeProbability(readDoubleCfg(cfg, "defaultHitRateFallback", 0.6d), 0.6d); + if (((Boolean) feature.get("isArmor")).booleanValue()) { + pBase = pBase + 0.05d; + } + if (((Boolean) feature.get("isAir")).booleanValue()) { + pBase = pBase - 0.05d; + } + if (distance > 1500.0d) { + pBase = pBase - 0.06d; + } + pBase = normalizeProbability(pBase, 0.6d); + int oneTargetNeed = computeRequiredShots(pBase, desiredKill, 1); + int count = oneTargetNeed + Math.max(0, blueNum - 1); + if (threat >= 3) { + count++; + } + return Math.max(1, count); +} + +function double computeBlueDistanceToDefenseMeters(Weapon blueWeapon, Task blueTask) { + if (blueWeapon == null || blueWeapon.getCoordinate() == null || blueTask == null || blueTask.getDefZoneLocation() == null) { + return Double.MAX_VALUE; + } + Coordinate b = blueWeapon.getCoordinate(); + if (b.getLongitude() == null || b.getLatitude() == null) { + return Double.MAX_VALUE; + } + double best = Double.MAX_VALUE; + for (Object obj : blueTask.getDefZoneLocation()) { + Coordinate c = (Coordinate) obj; + if (c == null || c.getLongitude() == null || c.getLatitude() == null) { + continue; + } + double d = approxDistanceMeters( + b.getLongitude().doubleValue(), + b.getLatitude().doubleValue(), + c.getLongitude().doubleValue(), + c.getLatitude().doubleValue() + ); + if (d < best) { + best = d; + } + } + return best; +} + +function int estimateRangeByRedWeaponName(String redName) { + if (redName == null) { + return 800; + } + if (redName.contains("反坦克导弹系统")) { + return 1800; + } + if (redName.contains("导弹发射车")) { + return 2500; + } + if (redName.contains("防空导弹")) { + return 2200; + } + if (redName.contains("武装直升机")) { + return 1500; + } + if (redName.contains("无人机")) { + return 1200; + } + if (redName.contains("迫榴炮") || redName.contains("迫击炮")) { + return 1600; + } + if (redName.contains("反坦克火箭")) { + return 1000; + } + return 900; +} + +function double resolveHitRateByName(String redName, Map cfg) { + Map hitRateMap = parseNameDoubleCsv((String) cfg.get("hitRateCsv")); + Double p = (Double) hitRateMap.get(redName); + if (p == null) { + p = Double.valueOf(readDoubleCfg(cfg, "defaultHitRateFallback", 0.6d)); + } + return normalizeProbability(p.doubleValue(), 0.6d); +} + +function List getRedWeaponPoolByConfig(Map cfg) { + List pool = new ArrayList(); + addUnique(pool, (String) cfg.get("redStrikeDroneName")); + addUnique(pool, (String) cfg.get("redArmedHelicopterName")); + addUnique(pool, (String) cfg.get("redHowitzerName")); + addUnique(pool, (String) cfg.get("redVehicleMortarName")); + addUnique(pool, (String) cfg.get("redAaWeaponName")); + addUnique(pool, (String) cfg.get("redAtRocketName")); + addUnique(pool, (String) cfg.get("redAtMissileSystemName")); + addUnique(pool, (String) cfg.get("redMissileVehicleName")); + return pool; +} + +function Weapon createRedWeaponInstance(String name, String supportType, int number) { + Weapon w = new Weapon(); + w.setName(name); + w.setSupportType(supportType); + w.setNumber(number); + w.setComponents(new ArrayList()); + return w; +} + +function boolean containsBlueArtillery(List blueWeapons) { + if (blueWeapons == null) { + return false; + } + for (Object obj : blueWeapons) { + Weapon w = (Weapon) obj; + if (w != null && isArtilleryWeapon(w)) { + return true; + } + } + return false; +} + function void applyMappedWeapons(List redWeapons, Map cfg, String mapKey, int defaultNum, boolean allowArmedHelicopter) { List mappedNames = parseMappedWeaponNames(cfg, mapKey); if (mappedNames == null || mappedNames.isEmpty()) { @@ -577,130 +969,86 @@ function void bindTargetIdsForRedWeapons(List redWeapons, List blueWeapons, Map if (redWeapons == null || redWeapons.isEmpty() || blueWeapons == null || blueWeapons.isEmpty()) { return; } - - Map pools = extractBlueTargetPools(blueWeapons); Map blueById = indexBlueWeaponsById(blueWeapons); Map assignedCountByBlueId = new java.util.HashMap(); - Map survivalByBlueId = initBlueSurvivalMap(blueById); - double desiredKill = normalizeProbability(readDoubleCfg(cfg, "desiredKillProbability", 0.9d), 0.9d); - int cap = readIntCfg(cfg, "maxRedWeaponsPerBlueTargetCap", 3); - int minPerBlue = readIntCfg(cfg, "minRedWeaponsPerBlueTarget", 1); - if (cap <= 0) { - cap = 1; - } - if (minPerBlue < 0) { - minPerBlue = 0; - } - if (minPerBlue > cap) { - minPerBlue = cap; - } - + double decayAlpha = readDoubleCfg(cfg, "targetDecayAlpha", 0.35d); + boolean fallbackToNearest = readBooleanCfg(cfg, "fallbackToNearestTarget", true); + boolean allowUnassigned = readBooleanCfg(cfg, "allowUnassignedRedWeapon", true); int total = redWeapons.size(); int bound = 0; - // 第一轮:先满足每个蓝目标的最小分配(在cap内) - if (minPerBlue > 0) { - List allBlueIds = (List) pools.get("all"); - if (allBlueIds != null && !allBlueIds.isEmpty()) { - for (Object idObj : allBlueIds) { - String blueId = String.valueOf(idObj); - int need = minPerBlue; - while (need > 0) { - Weapon candidate = pickUnboundRedWeaponForBlue(blueId, redWeapons); - if (candidate == null) { - break; - } - candidate.setTargetId(blueId); - bound++; - incrementAssignedCount(assignedCountByBlueId, blueId); - double p = resolveWeaponHitRate(candidate, cfg); - updateBlueSurvival(survivalByBlueId, blueId, p); - need--; - } - } - } - } - - // 第二轮:按命中率动态分配,优先填补“尚未达到期望毁伤概率”的蓝目标 - for (Object obj : redWeapons) { - Weapon redWeapon = (Weapon) obj; + // 核心:每把红武器选择“当前边际收益最高”的蓝目标 + for (Object objR : redWeapons) { + Weapon redWeapon = (Weapon) objR; if (redWeapon == null) { continue; } - if (!isBlank(redWeapon.getTargetId())) { - continue; + String bestBlueId = null; + double bestMarginal = -1.0d; + for (Object objB : blueWeapons) { + Weapon blueWeapon = (Weapon) objB; + if (blueWeapon == null || isBlank(blueWeapon.getEquipmentId())) { + continue; + } + if (!isPairFeasible(redWeapon, blueWeapon, cfg)) { + continue; + } + double baseScore = computePairScore(redWeapon, blueWeapon, cfg); + Integer countObj = (Integer) assignedCountByBlueId.get(blueWeapon.getEquipmentId()); + int k = countObj == null ? 0 : countObj.intValue(); + double decay = 1.0d / (1.0d + decayAlpha * k); + double marginal = baseScore * decay; + if (marginal > bestMarginal) { + bestMarginal = marginal; + bestBlueId = blueWeapon.getEquipmentId(); + } } - String poolKey = inferBluePoolKeyForRedWeapon(redWeapon); - String targetId = pickBlueTargetByNeed( - pools, - poolKey, - assignedCountByBlueId, - survivalByBlueId, - desiredKill, - cap - ); - if (!isBlank(targetId)) { - redWeapon.setTargetId(targetId); + if (isBlank(bestBlueId) && fallbackToNearest) { + bestBlueId = pickNearestBlueId(redWeapon, blueWeapons); + } + if (isBlank(bestBlueId)) { + if (!allowUnassigned) { + bestBlueId = pickAnyBlueId(blueWeapons); + } + } + if (!isBlank(bestBlueId)) { + redWeapon.setTargetId(bestBlueId); bound++; - incrementAssignedCount(assignedCountByBlueId, targetId); - double p = resolveWeaponHitRate(redWeapon, cfg); - updateBlueSurvival(survivalByBlueId, targetId, p); + incrementAssignedCount(assignedCountByBlueId, bestBlueId); } } double minRatio = readDoubleCfg(cfg, "minTargetBindRatio", 0.7d); boolean allowReserveWithoutTarget = readBooleanCfg(cfg, "allowReserveWithoutTarget", true); - double currentRatio = total <= 0 ? 1.0d : ((double) bound / (double) total); - if (currentRatio >= minRatio && allowReserveWithoutTarget) { - return; - } - - // 第三轮:若绑定率不足,回退全目标池补齐(仍受cap约束) - for (Object obj : redWeapons) { - if (total > 0 && ((double) bound / (double) total) >= minRatio) { - break; - } - Weapon redWeapon = (Weapon) obj; - if (redWeapon == null || !isBlank(redWeapon.getTargetId())) { - continue; - } - String targetId = pickBlueTargetByNeed( - pools, - "all", - assignedCountByBlueId, - survivalByBlueId, - desiredKill, - cap - ); - if (!isBlank(targetId)) { - redWeapon.setTargetId(targetId); - bound++; - incrementAssignedCount(assignedCountByBlueId, targetId); - double p = resolveWeaponHitRate(redWeapon, cfg); - updateBlueSurvival(survivalByBlueId, targetId, p); - } - } - - // 第四轮:若不允许空 targetId,最后尽力补齐 if (!allowReserveWithoutTarget) { for (Object obj : redWeapons) { Weapon redWeapon = (Weapon) obj; if (redWeapon == null || !isBlank(redWeapon.getTargetId())) { continue; } - String targetId = pickBlueTargetByNeed( - pools, - "all", - assignedCountByBlueId, - survivalByBlueId, - desiredKill, - cap - ); - if (!isBlank(targetId)) { - redWeapon.setTargetId(targetId); - incrementAssignedCount(assignedCountByBlueId, targetId); - double p = resolveWeaponHitRate(redWeapon, cfg); - updateBlueSurvival(survivalByBlueId, targetId, p); + String any = pickAnyBlueId(blueWeapons); + if (!isBlank(any)) { + redWeapon.setTargetId(any); + bound++; + } + } + return; + } + // 允许冗余时,只保证最低绑定比例 + if (total > 0 && ((double) bound / (double) total) < minRatio) { + for (Object obj : redWeapons) { + Weapon redWeapon = (Weapon) obj; + if (redWeapon == null || !isBlank(redWeapon.getTargetId())) { + continue; + } + String any = pickAnyBlueId(blueWeapons); + if (isBlank(any)) { + continue; + } + redWeapon.setTargetId(any); + bound++; + if (((double) bound / (double) total) >= minRatio) { + break; } } } @@ -716,7 +1064,23 @@ function void applyHitRateDrivenOffsets(FactTask fact, Map cfg) { } applyDefaultHitRateIfAbsent(redWeapons, cfg); Map explicitOffset = parseNameIntCsv((String) cfg.get("offsetCsvByWeapon")); - double desiredKill = normalizeProbability(readDoubleCfg(cfg, "desiredKillProbability", 0.9d), 0.9d); + double threshold = normalizeProbability(readDoubleCfg(cfg, "hitRateThreshold", 0.7d), 0.7d); + double step1Max = readDoubleCfg(cfg, "hitRateGapStep1Max", 0.5d); + double step2Max = readDoubleCfg(cfg, "hitRateGapStep2Max", 1.0d); + int step1Offset = readIntCfg(cfg, "hitRateStep1Offset", 1); + int step2Offset = readIntCfg(cfg, "hitRateStep2Offset", 2); + if (step1Max < 0.0d) { + step1Max = 0.0d; + } + if (step2Max < step1Max) { + step2Max = step1Max; + } + if (step1Offset < 0) { + step1Offset = 0; + } + if (step2Offset < step1Offset) { + step2Offset = step1Offset; + } for (Object obj : redWeapons) { Weapon redWeapon = (Weapon) obj; if (redWeapon == null) { @@ -729,8 +1093,17 @@ function void applyHitRateDrivenOffsets(FactTask fact, Map cfg) { offset = explicit.intValue(); } else { double pHit = resolveWeaponHitRate(redWeapon, cfg); - int required = computeRequiredShots(pHit, desiredKill, base); - offset = required - base; + double gap = threshold - pHit; + if (gap <= 0.0d) { + offset = 0; + } else if (gap <= step1Max) { + offset = step1Offset; + } else if (gap <= step2Max) { + offset = step2Offset; + } else { + // 超出第二档时按第二档处理,避免火力数量无上限膨胀 + offset = step2Offset; + } } if (offset < 0) { offset = 0; @@ -782,12 +1155,42 @@ function void applyRangeSanityAndRecommend(FactTask fact, Map cfg) { if (currentRange >= minRequired) { continue; } + if (tryReplaceWithBetterRangeWeapon(redWeapon, minRequired, cfg)) { + continue; + } if (autoAdjust) { setWeaponFirstRangeParamAtLeast(redWeapon, minRequired); } } } +function boolean tryReplaceWithBetterRangeWeapon(Weapon redWeapon, int minRequired, Map cfg) { + if (redWeapon == null || redWeapon.getName() == null) { + return false; + } + String current = redWeapon.getName(); + String replacement = null; + if (current.contains("反坦克火箭")) { + replacement = "反坦克导弹系统"; + } else if (current.contains("迫击炮")) { + replacement = "迫榴炮"; + } else if (current.contains("无人机")) { + replacement = "武装直升机"; + } + if (isBlank(replacement)) { + return false; + } + if (estimateRangeByRedWeaponName(replacement) < minRequired) { + return false; + } + redWeapon.setName(replacement); + redWeapon.setSupportType(inferSupportTypeByWeaponName(replacement)); + redWeapon.setComponents(new ArrayList()); + ensureBasicRedComponents(redWeapon); + redWeapon.setHitRate(Double.valueOf(resolveHitRateByName(replacement, cfg))); + return true; +} + function void applyDefaultHitRateIfAbsent(List redWeapons, Map cfg) { if (redWeapons == null || redWeapons.isEmpty()) { return; @@ -967,6 +1370,194 @@ function void incrementAssignedCount(Map assignedCountByBlueId, String blueId) { assignedCountByBlueId.put(blueId, Integer.valueOf(now)); } +function boolean isPairFeasible(Weapon redWeapon, Weapon blueWeapon, Map cfg) { + if (redWeapon == null || blueWeapon == null || isBlank(blueWeapon.getEquipmentId())) { + return false; + } + // 类型禁配(最基础约束) + String pool = inferBluePoolKeyForRedWeapon(redWeapon); + if ("air".equals(pool) && !isAirWeapon(blueWeapon)) { + return false; + } + if ("armor".equals(pool) && !isArmorWeapon(blueWeapon)) { + return false; + } + if ("artillery".equals(pool) && !isArtilleryWeapon(blueWeapon) && !isGroundWeapon(blueWeapon)) { + return false; + } + // 距离/高度约束(有坐标时启用) + Coordinate rc = redWeapon.getCoordinate(); + Coordinate bc = blueWeapon.getCoordinate(); + if (isCoordinateUsable(rc) && isCoordinateUsable(bc)) { + double d = approxDistanceMeters( + rc.getLongitude().doubleValue(), + rc.getLatitude().doubleValue(), + bc.getLongitude().doubleValue(), + bc.getLatitude().doubleValue() + ); + int maxDist = readIntCfg(cfg, "maxEffectiveDistance", 3000); + if (d > maxDist) { + return false; + } + int rh = rc.getHeight() == null ? 0 : rc.getHeight().intValue(); + int bh = bc.getHeight() == null ? 0 : bc.getHeight().intValue(); + int maxGap = readIntCfg(cfg, "maxHeightGap", 800); + if (Math.abs(rh - bh) > maxGap) { + return false; + } + int estRange = estimateRangeByRedWeaponName(redWeapon.getName()); + int margin = readIntCfg(cfg, "rangeSafetyMarginMeters", 50); + if (estRange < (int) Math.ceil(d) + margin) { + return false; + } + } + return true; +} + +function double computePairScore(Weapon redWeapon, Weapon blueWeapon, Map cfg) { + double wType = readDoubleCfg(cfg, "w_target_type", 0.30d); + double wDist = readDoubleCfg(cfg, "w_target_dist", 0.25d); + double wHeight = readDoubleCfg(cfg, "w_target_height", 0.10d); + double wHit = readDoubleCfg(cfg, "w_target_hit", 0.20d); + double wThreat = readDoubleCfg(cfg, "w_target_threat", 0.15d); + double typeFit = computeTypeFit(redWeapon, blueWeapon); + double distanceFit = computeDistanceFit(redWeapon, blueWeapon, cfg); + double heightFit = computeHeightFit(redWeapon, blueWeapon, cfg); + double hitContribution = resolveWeaponHitRate(redWeapon, cfg); + double threatWeight = computeBlueThreatWeight(blueWeapon); + return (wType * typeFit) + (wDist * distanceFit) + (wHeight * heightFit) + (wHit * hitContribution) + (wThreat * threatWeight); +} + +function double computeTypeFit(Weapon redWeapon, Weapon blueWeapon) { + String pool = inferBluePoolKeyForRedWeapon(redWeapon); + if ("air".equals(pool) && isAirWeapon(blueWeapon)) { + return 1.0d; + } + if ("armor".equals(pool) && isArmorWeapon(blueWeapon)) { + return 1.0d; + } + if ("artillery".equals(pool) && isArtilleryWeapon(blueWeapon)) { + return 0.9d; + } + if ("missile".equals(pool) && hasMissileComponent(blueWeapon)) { + return 0.9d; + } + if ("ground".equals(pool) && isGroundWeapon(blueWeapon)) { + return 0.7d; + } + return 0.2d; +} + +function double computeDistanceFit(Weapon redWeapon, Weapon blueWeapon, Map cfg) { + Coordinate rc = redWeapon == null ? null : redWeapon.getCoordinate(); + Coordinate bc = blueWeapon == null ? null : blueWeapon.getCoordinate(); + if (!isCoordinateUsable(rc) || !isCoordinateUsable(bc)) { + return 0.5d; + } + double d = approxDistanceMeters( + rc.getLongitude().doubleValue(), + rc.getLatitude().doubleValue(), + bc.getLongitude().doubleValue(), + bc.getLatitude().doubleValue() + ); + int maxDist = readIntCfg(cfg, "maxEffectiveDistance", 3000); + if (maxDist <= 0) { + maxDist = 3000; + } + double ratio = d / (double) maxDist; + if (ratio <= 0.3d) { + return 1.0d; + } + if (ratio <= 0.6d) { + return 0.8d; + } + if (ratio <= 1.0d) { + return 0.5d; + } + return 0.0d; +} + +function double computeHeightFit(Weapon redWeapon, Weapon blueWeapon, Map cfg) { + Coordinate rc = redWeapon == null ? null : redWeapon.getCoordinate(); + Coordinate bc = blueWeapon == null ? null : blueWeapon.getCoordinate(); + if (rc == null || bc == null) { + return 0.6d; + } + int rh = rc.getHeight() == null ? 0 : rc.getHeight().intValue(); + int bh = bc.getHeight() == null ? 0 : bc.getHeight().intValue(); + int gap = Math.abs(rh - bh); + int maxGap = readIntCfg(cfg, "maxHeightGap", 800); + if (maxGap <= 0) { + maxGap = 800; + } + if (gap >= maxGap) { + return 0.0d; + } + double ratio = (double) gap / (double) maxGap; + return 1.0d - ratio; +} + +function double computeBlueThreatWeight(Weapon blueWeapon) { + if (blueWeapon == null) { + return 0.5d; + } + double t = 0.4d; + if (isAirWeapon(blueWeapon)) { + t += 0.3d; + } + if (isArmorWeapon(blueWeapon)) { + t += 0.2d; + } + if (hasMissileComponent(blueWeapon)) { + t += 0.2d; + } + if (blueWeapon.getNumber() != null && blueWeapon.getNumber().intValue() >= 2) { + t += 0.1d; + } + if (t > 1.0d) { + t = 1.0d; + } + return t; +} + +function String pickNearestBlueId(Weapon redWeapon, List blueWeapons) { + if (redWeapon == null || redWeapon.getCoordinate() == null || !isCoordinateUsable(redWeapon.getCoordinate()) || blueWeapons == null) { + return null; + } + String bestId = null; + double best = Double.MAX_VALUE; + for (Object obj : blueWeapons) { + Weapon b = (Weapon) obj; + if (b == null || isBlank(b.getEquipmentId()) || !isCoordinateUsable(b.getCoordinate())) { + continue; + } + double d = approxDistanceMeters( + redWeapon.getCoordinate().getLongitude().doubleValue(), + redWeapon.getCoordinate().getLatitude().doubleValue(), + b.getCoordinate().getLongitude().doubleValue(), + b.getCoordinate().getLatitude().doubleValue() + ); + if (d < best) { + best = d; + bestId = b.getEquipmentId(); + } + } + return bestId; +} + +function String pickAnyBlueId(List blueWeapons) { + if (blueWeapons == null || blueWeapons.isEmpty()) { + return null; + } + for (Object obj : blueWeapons) { + Weapon b = (Weapon) obj; + if (b != null && !isBlank(b.getEquipmentId())) { + return b.getEquipmentId(); + } + } + return null; +} + function Weapon pickUnboundRedWeaponForBlue(String blueId, List redWeapons) { if (redWeapons == null || redWeapons.isEmpty()) { return null; @@ -1077,7 +1668,7 @@ function void setWeaponFirstRangeParamAtLeast(Weapon weapon, int minRange) { } //------------------------------------------------------------------------------- -// component 映射解析 + 全组件数量覆盖 +// component 映射解析 + 仅导弹组件数量/参数覆盖 function Map parseDeviceNameMapping(String csv) { Map result = new java.util.HashMap(); if (csv == null) { @@ -1129,12 +1720,11 @@ function void applyAllComponentQuantities(FactTask fact, Map cfg) { String mappingCsv = cfg == null ? null : (String) cfg.get("componentDeviceNameMappingCsv"); Map deviceNameMapping = parseDeviceNameMapping(mappingCsv); - String skipKeyword = cfg == null ? null : (String) cfg.get("skipMissileComponentsByNameContains"); - if (skipKeyword != null) { - skipKeyword = skipKeyword.trim(); - if (skipKeyword.equals("")) { - skipKeyword = null; - } + String missileKeyword = cfg == null ? null : (String) cfg.get("missileComponentNameContains"); + if (missileKeyword == null || missileKeyword.trim().equals("")) { + missileKeyword = "导弹"; + } else { + missileKeyword = missileKeyword.trim(); } // 遍历红方每个武器:用 targetId 找蓝方装备(equipmentId) @@ -1182,8 +1772,8 @@ function void applyAllComponentQuantities(FactTask fact, Map cfg) { continue; } - // 跳过导弹组件覆盖,避免覆盖导弹联动偏移逻辑 - if (skipKeyword != null && blueCompName.contains(skipKeyword)) { + // 仅导弹组件参与匹配,非导弹组件保持红方原值 + if (!blueCompName.contains(missileKeyword)) { continue; } @@ -1223,8 +1813,12 @@ function void applyAllComponentQuantities(FactTask fact, Map cfg) { continue; } - // 只覆盖数量(componentParams[0].number) + // 覆盖导弹组件数量(componentParams[0].number) redFirst.setNumber(blueFirst.getNumber()); + // 覆盖导弹组件首参数默认值(如范围米/破坏范围米) + if (!isBlank(blueFirst.getAttDefaultValue())) { + redFirst.setAttDefaultValue(blueFirst.getAttDefaultValue()); + } } } } @@ -1262,33 +1856,530 @@ function void applyWeaponDeployment(FactTask fact, Map cfg, Map runtime) { String airPref = String.valueOf(cfg.get("airDeployZonePreference")); int groundH = readIntCfg(cfg, "groundDeployHeight", 20); int airH = readIntCfg(cfg, "airDeployHeight", 300); + Task blueTask = fact.getBlueTask(); + Task redTask = fact.getRedTask(); + String deployMode = resolveDeployModeByBlueState(blueTask, cfg); + Coordinate groundAnchor = pickAnchorByMode(deployMode, combatPoints, defensePoints, cursor); + Coordinate airAnchor = pickAnchorByMode("defense".equalsIgnoreCase(airPref) ? "near_slow" : deployMode, combatPoints, defensePoints, cursor); + String formationType = resolveFormationType(redTask, blueTask, cfg, deployMode); + int spacing = resolveFormationSpacing(redTask, blueTask, cfg); + int headingDeg = resolveFormationHeading(redTask, blueTask, cfg); + int mainWingDistance = resolveMainWingDistance(redTask, blueTask, cfg, deployMode); - for (Object obj : fact.getRedTask().getTaskWeapons()) { + List groundWeapons = new ArrayList(); + List airWeapons = new ArrayList(); + for (Object obj : redTask.getTaskWeapons()) { Weapon redWeapon = (Weapon) obj; if (redWeapon == null) { continue; } - Coordinate selected = null; if (isRedAirWeapon(redWeapon)) { - selected = pickCoordinateByPreference(combatPoints, defensePoints, cursor, airPref); - if (selected != null) { - selected = cloneCoordinateWithHeight(selected, airH); - } - } else if (defensePriorityWeapons.contains(redWeapon.getName())) { - selected = pickCoordinateByPreference(defensePoints, combatPoints, cursor, "defense"); - if (selected != null) { - selected = cloneCoordinateWithHeight(selected, groundH); - } + airWeapons.add(redWeapon); } else { - selected = pickCoordinateByPreference(combatPoints, defensePoints, cursor, "combat"); - if (selected != null) { - selected = cloneCoordinateWithHeight(selected, groundH); - } - } - if (selected != null) { - redWeapon.setCoordinate(selected); + groundWeapons.add(redWeapon); } } + + applyFormationForWeaponGroup(groundWeapons, groundAnchor, formationType, spacing, headingDeg, mainWingDistance, groundH, defensePriorityWeapons, combatPoints, defensePoints, cursor, null, null, null, false); + applyFormationForWeaponGroup(airWeapons, airAnchor, formationType, spacing, headingDeg, mainWingDistance, airH, defensePriorityWeapons, combatPoints, defensePoints, cursor, blueTask, cfg, deployMode, true); +} + +function void applyFormationForWeaponGroup(List weapons, Coordinate anchor, String formationType, int spacing, int headingDeg, int mainWingDistance, int defaultHeight, List defensePriorityWeapons, List combatPoints, List defensePoints, Map cursor, Task blueTask, Map cfg, String deployMode, boolean isAirGroup) { + if (weapons == null || weapons.isEmpty()) { + return; + } + List sortedWeapons = sortWeaponsByFormationRole(weapons); + if (anchor == null) { + for (int i = 0; i < sortedWeapons.size(); i++) { + Weapon w = (Weapon) sortedWeapons.get(i); + if (w == null) { + continue; + } + int h = resolveWeaponDeployHeight(w, blueTask, cfg, deployMode, i, defaultHeight, isAirGroup); + Coordinate fallback = null; + if (defensePriorityWeapons != null && defensePriorityWeapons.contains(w.getName())) { + fallback = pickCoordinateByPreference(defensePoints, combatPoints, cursor, "defense"); + } else { + fallback = pickCoordinateByPreference(combatPoints, defensePoints, cursor, "combat"); + } + if (fallback != null) { + w.setCoordinate(cloneCoordinateWithHeight(fallback, h)); + } + } + return; + } + List offsets = buildFormationOffsets(formationType, sortedWeapons.size(), spacing); + for (int i = 0; i < sortedWeapons.size(); i++) { + Weapon w = (Weapon) sortedWeapons.get(i); + if (w == null) { + continue; + } + int h = resolveWeaponDeployHeight(w, blueTask, cfg, deployMode, i, defaultHeight, isAirGroup); + Map off = i < offsets.size() ? (Map) offsets.get(i) : null; + double dx = off == null || off.get("dx") == null ? 0.0d : ((Double) off.get("dx")).doubleValue(); + double dy = off == null || off.get("dy") == null ? 0.0d : ((Double) off.get("dy")).doubleValue(); + if (w.getFormationRole() != null && "WING".equalsIgnoreCase(w.getFormationRole()) && w.getWingRelativeDistanceMeters() != null && w.getWingRelativeDistanceMeters().intValue() > 0) { + int dist = w.getWingRelativeDistanceMeters().intValue(); + int bearing = w.getWingRelativeBearingDeg() == null ? (i % 2 == 0 ? 45 : -45) : w.getWingRelativeBearingDeg().intValue(); + dx = dist * Math.cos(Math.toRadians(bearing)); + dy = dist * Math.sin(Math.toRadians(bearing)); + } else if (w.getFormationRole() != null && "WING".equalsIgnoreCase(w.getFormationRole())) { + int dist = mainWingDistance; + int bearing = w.getWingRelativeBearingDeg() == null ? (i % 2 == 0 ? 45 : -45) : w.getWingRelativeBearingDeg().intValue(); + dx = dist * Math.cos(Math.toRadians(bearing)); + dy = dist * Math.sin(Math.toRadians(bearing)); + } + Coordinate c = moveCoordinateByMeters(anchor, dx, dy, headingDeg, h); + if (c != null) { + w.setCoordinate(c); + if (w.getFormationRole() == null || w.getFormationRole().trim().equals("")) { + w.setFormationRole(i == 0 ? "MAIN" : "WING"); + } + } + } +} + +function String resolveDeployModeByBlueState(Task blueTask, Map cfg) { + double d = computeDefMinDistanceMeters(blueTask); + int avgSpeed = computeAverageSpeed(blueTask == null ? null : blueTask.getTrackPoints()); + int fast = readIntCfg(cfg, "speedFastThreshold", 180); + int near = readIntCfg(cfg, "distanceNearDefenseThresholdMeters", 800); + int far = readIntCfg(cfg, "distanceFarDefenseThresholdMeters", 2500); + if (avgSpeed >= fast && d <= near) { + return "near_fast"; + } + if (avgSpeed >= fast && d >= far) { + return "far_fast"; + } + if (avgSpeed < fast && d <= near) { + return "near_slow"; + } + return "default"; +} + +function Coordinate pickAnchorByMode(String mode, List combatPoints, List defensePoints, Map cursor) { + if ("near_fast".equals(mode)) { + return pickCoordinateByPreference(defensePoints, combatPoints, cursor, "near_fast"); + } + if ("far_fast".equals(mode)) { + return pickCoordinateByPreference(combatPoints, defensePoints, cursor, "far_fast"); + } + if ("near_slow".equals(mode)) { + return pickCoordinateByPreference(defensePoints, combatPoints, cursor, "near_slow"); + } + return pickCoordinateByPreference(combatPoints, defensePoints, cursor, "default"); +} + +function String resolveFormationType(Task redTask, Task blueTask, Map cfg, String deployMode) { + String fromTask = redTask == null ? null : redTask.getFormationType(); + if (!isBlank(fromTask)) { + return normalizeFormationType(fromTask, cfg); + } + if (readBooleanCfg(cfg, "enableAutoFormationSelect", true)) { + String autoType = autoSelectFormationType(redTask, blueTask, cfg, deployMode); + if (!isBlank(autoType)) { + return normalizeFormationType(autoType, cfg); + } + } + String fromCfg = cfg == null ? null : String.valueOf(cfg.get("formationDefaultType")); + if (isBlank(fromCfg)) { + return "TRIANGLE"; + } + return normalizeFormationType(fromCfg, cfg); +} + +function String autoSelectFormationType(Task redTask, Task blueTask, Map cfg, String deployMode) { + int threat = parseIntSafe(blueTask == null ? null : blueTask.getThreatLevel(), 1); + int highThreat = readIntCfg(cfg, "formationHighThreatLevel", 3); + if (threat >= highThreat) { + return String.valueOf(cfg.get("formationRule_high_threat")); + } + if ("near_fast".equals(deployMode)) { + return String.valueOf(cfg.get("formationRule_near_fast")); + } + if ("far_fast".equals(deployMode)) { + return String.valueOf(cfg.get("formationRule_far_fast")); + } + if ("near_slow".equals(deployMode)) { + return String.valueOf(cfg.get("formationRule_near_slow")); + } + int largeGroupCount = readIntCfg(cfg, "formationLargeGroupCount", 6); + int weaponCount = redTask == null || redTask.getTaskWeapons() == null ? 0 : redTask.getTaskWeapons().size(); + if (weaponCount >= largeGroupCount) { + return String.valueOf(cfg.get("formationRule_large_group")); + } + double airRatio = computeRedAirRatio(redTask); + if (airRatio >= 0.5d) { + return String.valueOf(cfg.get("formationRule_air_majority")); + } + return String.valueOf(cfg.get("formationRule_default")); +} + +function double computeRedAirRatio(Task redTask) { + if (redTask == null || redTask.getTaskWeapons() == null || redTask.getTaskWeapons().isEmpty()) { + return 0.0d; + } + int total = 0; + int air = 0; + for (Object obj : redTask.getTaskWeapons()) { + Weapon w = (Weapon) obj; + if (w == null) { + continue; + } + total++; + if (isRedAirWeapon(w)) { + air++; + } + } + if (total <= 0) { + return 0.0d; + } + return (double) air / (double) total; +} + +function String normalizeFormationType(String input, Map cfg) { + String defaultType = cfg == null ? "TRIANGLE" : String.valueOf(cfg.get("formationDefaultType")); + if (isBlank(defaultType)) { + defaultType = "TRIANGLE"; + } + if (isBlank(input)) { + return defaultType.trim().toUpperCase(); + } + String t = input.trim().toUpperCase(); + if ("TRIANGLE".equals(t) || "DIAMOND".equals(t) || "LINE".equals(t) || "COLUMN".equals(t) || "WEDGE".equals(t)) { + return t; + } + return defaultType.trim().toUpperCase(); +} + +function int resolveFormationSpacing(Task redTask, Task blueTask, Map cfg) { + if (redTask != null && redTask.getFormationSpacingMeters() != null && redTask.getFormationSpacingMeters().intValue() > 0) { + return redTask.getFormationSpacingMeters().intValue(); + } + int fallback = readIntCfg(cfg, "formationDefaultSpacingMeters", 120); + double scale = computeDefenseZoneScaleMeters(blueTask); + if (scale <= 0.0d) { + return fallback; + } + int minSpacing = readIntCfg(cfg, "formationSpacingMinMeters", 60); + int maxSpacing = readIntCfg(cfg, "formationSpacingMaxMeters", 220); + int scaleMin = readIntCfg(cfg, "defenseScaleMinMeters", 300); + int scaleMax = readIntCfg(cfg, "defenseScaleMaxMeters", 3000); + double t = normalizeByRange(scale, (double) scaleMin, (double) scaleMax); + return clampInt((int) Math.round(minSpacing + (maxSpacing - minSpacing) * t), minSpacing, maxSpacing); +} + +function int resolveFormationHeading(Task redTask, Task blueTask, Map cfg) { + if (redTask != null && redTask.getFormationHeadingDeg() != null) { + return redTask.getFormationHeadingDeg().intValue(); + } + Integer headingFromTrack = computeHeadingFromBlueTrack(blueTask); + if (headingFromTrack != null) { + return headingFromTrack.intValue(); + } + return readIntCfg(cfg, "formationHeadingDefaultDeg", 0); +} + +function int resolveMainWingDistance(Task redTask, Task blueTask, Map cfg, String deployMode) { + if (redTask != null && redTask.getMainWingDistanceMeters() != null && redTask.getMainWingDistanceMeters().intValue() > 0) { + return redTask.getMainWingDistanceMeters().intValue(); + } + int fallback = readIntCfg(cfg, "mainWingDistanceDefaultMeters", 100); + double scale = computeDefenseZoneScaleMeters(blueTask); + if (scale <= 0.0d) { + return fallback; + } + int minD = readIntCfg(cfg, "mainWingDistanceMinMeters", 60); + int maxD = readIntCfg(cfg, "mainWingDistanceMaxMeters", 260); + int scaleMin = readIntCfg(cfg, "defenseScaleMinMeters", 300); + int scaleMax = readIntCfg(cfg, "defenseScaleMaxMeters", 3000); + double t = normalizeByRange(scale, (double) scaleMin, (double) scaleMax); + double base = minD + (maxD - minD) * t; + String key = "mainWingDistanceModeFactor_default"; + if ("near_fast".equals(deployMode)) { + key = "mainWingDistanceModeFactor_near_fast"; + } else if ("far_fast".equals(deployMode)) { + key = "mainWingDistanceModeFactor_far_fast"; + } else if ("near_slow".equals(deployMode)) { + key = "mainWingDistanceModeFactor_near_slow"; + } + double factor = readDoubleCfg(cfg, key, 1.0d); + int finalDist = (int) Math.round(base * factor); + return clampInt(finalDist, minD, maxD); +} + +function double computeDefenseZoneScaleMeters(Task blueTask) { + if (blueTask == null || blueTask.getDefZoneLocation() == null || blueTask.getDefZoneLocation().size() < 3) { + return -1.0d; + } + List points = blueTask.getDefZoneLocation(); + double maxD = -1.0d; + for (int i = 0; i < points.size(); i++) { + Coordinate a = (Coordinate) points.get(i); + if (a == null || a.getLongitude() == null || a.getLatitude() == null) { + continue; + } + for (int j = i + 1; j < points.size(); j++) { + Coordinate b = (Coordinate) points.get(j); + if (b == null || b.getLongitude() == null || b.getLatitude() == null) { + continue; + } + double d = approxDistanceMeters( + a.getLongitude().doubleValue(), + a.getLatitude().doubleValue(), + b.getLongitude().doubleValue(), + b.getLatitude().doubleValue() + ); + if (d > maxD) { + maxD = d; + } + } + } + return maxD; +} + +function double normalizeByRange(double value, double minV, double maxV) { + if (maxV <= minV) { + return 0.0d; + } + if (value <= minV) { + return 0.0d; + } + if (value >= maxV) { + return 1.0d; + } + return (value - minV) / (maxV - minV); +} + +function int clampInt(int value, int minV, int maxV) { + if (value < minV) { + return minV; + } + if (value > maxV) { + return maxV; + } + return value; +} + +/** 入参武器坐标高度>0 视为已指定部署高度 */ +function int readWeaponHeightIfValid(Weapon w) { + if (w == null || w.getCoordinate() == null || w.getCoordinate().getHeight() == null) { + return -1; + } + int h = w.getCoordinate().getHeight().intValue(); + return h > 0 ? h : -1; +} + +function int resolveBlueBaseHeight(Task blueTask, Map cfg) { + int fallback = readIntCfg(cfg, "airHeightFallback", readIntCfg(cfg, "airDeployHeight", 300)); + if (blueTask != null && blueTask.getTrackPoints() != null && !blueTask.getTrackPoints().isEmpty()) { + TrackPoints tail = (TrackPoints) blueTask.getTrackPoints().get(blueTask.getTrackPoints().size() - 1); + if (tail != null && tail.getHeight() != null && tail.getHeight().intValue() > 0) { + return tail.getHeight().intValue(); + } + } + if (blueTask != null && blueTask.getTaskWeapons() != null) { + int sum = 0; + int cnt = 0; + for (Object o : blueTask.getTaskWeapons()) { + Weapon bw = (Weapon) o; + if (bw == null || bw.getCoordinate() == null || bw.getCoordinate().getHeight() == null) { + continue; + } + int hh = bw.getCoordinate().getHeight().intValue(); + if (hh > 0) { + sum += hh; + cnt++; + } + } + if (cnt > 0) { + return sum / cnt; + } + } + return fallback; +} + +function int resolveRoleHeightAdjust(Weapon w, int indexInFormation, Map cfg) { + int mainAdj = readIntCfg(cfg, "airHeightAdjustMain", 20); + int wingAdj = readIntCfg(cfg, "airHeightAdjustWing", -10); + if (w != null && w.getFormationRole() != null && !w.getFormationRole().trim().equals("")) { + if ("MAIN".equalsIgnoreCase(w.getFormationRole())) { + return mainAdj; + } + if ("WING".equalsIgnoreCase(w.getFormationRole())) { + return wingAdj; + } + } + return indexInFormation == 0 ? mainAdj : wingAdj; +} + +function int computeAutoAirHeight(Weapon w, Task blueTask, Map cfg, int indexInFormation) { + int minH = readIntCfg(cfg, "airHeightMin", 50); + int maxH = readIntCfg(cfg, "airHeightMax", 20000); + int base = resolveBlueBaseHeight(blueTask, cfg); + int speedTh = readIntCfg(cfg, "airHeightSpeedThreshold", 180); + int fastAdj = readIntCfg(cfg, "airHeightAdjustFast", 40); + int avgSpeed = computeAverageSpeed(blueTask == null ? null : blueTask.getTrackPoints()); + int speedAdj = (avgSpeed >= speedTh) ? fastAdj : 0; + double d = computeDefMinDistanceMeters(blueTask); + int nearD = readIntCfg(cfg, "airHeightNearDefenseDistance", 800); + int farD = readIntCfg(cfg, "airHeightFarDefenseDistance", 2500); + int nearAdj = readIntCfg(cfg, "airHeightAdjustNear", -30); + int farAdj = readIntCfg(cfg, "airHeightAdjustFar", 40); + int distAdj = 0; + if (d < Double.MAX_VALUE / 4.0d) { + if (d <= (double) nearD) { + distAdj = nearAdj; + } else if (d >= (double) farD) { + distAdj = farAdj; + } + } + int roleAdj = resolveRoleHeightAdjust(w, indexInFormation, cfg); + int sum = base + speedAdj + distAdj + roleAdj; + return clampInt(sum, minH, maxH); +} + +/** + * 空中武器部署高度:1) 入参已给高度优先 2) 开启自动则混合计算 3) 否则固定 airDeployHeight + * 地面武器:始终使用 defaultHeight(isAirGroup=false) + */ +function int resolveWeaponDeployHeight(Weapon w, Task blueTask, Map cfg, String deployMode, int indexInFormation, int defaultHeight, boolean isAirGroup) { + if (!isAirGroup) { + return defaultHeight; + } + int given = readWeaponHeightIfValid(w); + if (given > 0) { + return given; + } + if (cfg == null || !readBooleanCfg(cfg, "enableAutoAirDeployHeight", true)) { + return defaultHeight; + } + return computeAutoAirHeight(w, blueTask, cfg, indexInFormation); +} + +function Integer computeHeadingFromBlueTrack(Task blueTask) { + if (blueTask == null || blueTask.getTrackPoints() == null || blueTask.getTrackPoints().size() < 2) { + return null; + } + List points = blueTask.getTrackPoints(); + TrackPoints p2 = (TrackPoints) points.get(points.size() - 1); + TrackPoints p1 = (TrackPoints) points.get(points.size() - 2); + if (p1 == null || p2 == null || p1.getLongitude() == null || p1.getLatitude() == null || p2.getLongitude() == null || p2.getLatitude() == null) { + return null; + } + double dLon = p2.getLongitude().doubleValue() - p1.getLongitude().doubleValue(); + double dLat = p2.getLatitude().doubleValue() - p1.getLatitude().doubleValue(); + if (Math.abs(dLon) < 1e-12 && Math.abs(dLat) < 1e-12) { + return null; + } + double deg = Math.toDegrees(Math.atan2(dLon, dLat)); + int heading = (int) Math.round(deg); + if (heading < 0) { + heading += 360; + } + if (heading >= 360) { + heading = heading % 360; + } + return Integer.valueOf(heading); +} + +function List sortWeaponsByFormationRole(List weapons) { + List mainList = new ArrayList(); + List wingList = new ArrayList(); + List unknownList = new ArrayList(); + for (Object obj : weapons) { + Weapon w = (Weapon) obj; + if (w == null) { + continue; + } + String role = w.getFormationRole(); + if (role != null && "MAIN".equalsIgnoreCase(role)) { + mainList.add(w); + } else if (role != null && "WING".equalsIgnoreCase(role)) { + wingList.add(w); + } else { + unknownList.add(w); + } + } + List result = new ArrayList(); + if (!mainList.isEmpty()) { + result.addAll(mainList); + result.addAll(wingList); + result.addAll(unknownList); + return result; + } + if (!unknownList.isEmpty()) { + result.add(unknownList.get(0)); + for (int i = 1; i < unknownList.size(); i++) { + result.add(unknownList.get(i)); + } + result.addAll(wingList); + return result; + } + result.addAll(wingList); + return result; +} + +function List buildFormationOffsets(String formationType, int size, int spacing) { + List result = new ArrayList(); + if (size <= 0) { + return result; + } + String type = formationType == null ? "TRIANGLE" : formationType.trim().toUpperCase(); + result.add(buildOffset(0.0d, 0.0d)); + for (int i = 1; i < size; i++) { + if ("DIAMOND".equals(type)) { + if (i == 1) { + result.add(buildOffset(-spacing, -spacing)); + } else if (i == 2) { + result.add(buildOffset(spacing, -spacing)); + } else if (i == 3) { + result.add(buildOffset(0.0d, -2.0d * spacing)); + } else { + int row = (i - 4) / 2 + 1; + int sign = (i % 2 == 0) ? 1 : -1; + result.add(buildOffset(sign * spacing * (row + 1), -spacing * (row + 2))); + } + } else if ("LINE".equals(type)) { + int sign = (i % 2 == 0) ? 1 : -1; + int k = (i + 1) / 2; + result.add(buildOffset(sign * k * spacing, 0.0d)); + } else if ("COLUMN".equals(type)) { + result.add(buildOffset(0.0d, -i * spacing)); + } else if ("WEDGE".equals(type)) { + int layer = (i + 1) / 2; + int sign = (i % 2 == 0) ? 1 : -1; + result.add(buildOffset(sign * layer * spacing, -layer * spacing)); + } else { // TRIANGLE/default + int layer = (i + 1) / 2; + int sign = (i % 2 == 0) ? 1 : -1; + result.add(buildOffset(sign * layer * spacing, -layer * spacing)); + } + } + return result; +} + +function Map buildOffset(double dx, double dy) { + Map m = new java.util.HashMap(); + m.put("dx", Double.valueOf(dx)); + m.put("dy", Double.valueOf(dy)); + return m; +} + +function Coordinate moveCoordinateByMeters(Coordinate anchor, double dxMeters, double dyMeters, int headingDeg, int height) { + if (anchor == null || anchor.getLongitude() == null || anchor.getLatitude() == null) { + return null; + } + double rad = Math.toRadians((double) headingDeg); + double rx = (dxMeters * Math.cos(rad)) - (dyMeters * Math.sin(rad)); + double ry = (dxMeters * Math.sin(rad)) + (dyMeters * Math.cos(rad)); + double lat = anchor.getLatitude().doubleValue() + metersToLatDeg(ry); + double lon = anchor.getLongitude().doubleValue() + metersToLonDeg(rx, anchor.getLatitude().doubleValue()); + Coordinate c = new Coordinate(); + c.setLongitude(new java.math.BigDecimal(String.valueOf(lon))); + c.setLatitude(new java.math.BigDecimal(String.valueOf(lat))); + c.setHeight(height); + return c; } function void applyTrajectoryGeneration(FactTask fact, Map cfg) { diff --git a/auto-solution-rule/src/main/resources/rules/rule.drl b/auto-solution-rule/src/main/resources/rules/rule.drl new file mode 100644 index 0000000..e69de29