diff --git a/auto-solution-rule/src/main/resources/rules/README.md b/auto-solution-rule/src/main/resources/rules/README.md index e250f85..99a1354 100644 --- a/auto-solution-rule/src/main/resources/rules/README.md +++ b/auto-solution-rule/src/main/resources/rules/README.md @@ -63,7 +63,12 @@ - `blueMissileRangeDefault`:蓝方导弹范围缺失时采用的默认值。 - `minBlueMissileCountForLinkage`:蓝方导弹数量达到该值才触发联动增强。 -### 2.5 targetId 自动绑定参数(新增) +### 2.5 全组件数量匹配参数(新增) +- `enableComponentQuantityMatch`:是否启用全组件数量覆盖(非导弹组件)。 +- `componentDeviceNameMappingCsv`:组件名映射表(逗号分隔),格式 `蓝组件deviceName->红组件deviceName`;为空则默认 `deviceName` 直匹配。 +- `skipMissileComponentsByNameContains`:跳过覆盖关键词:蓝方组件 `deviceName` 包含该关键词则不覆盖;默认 `导弹`(用于保留导弹联动偏移)。 + +### 2.6 targetId 自动绑定参数(新增) - `enableTargetAutoBind`:是否自动给红方武器写入 `targetId`。 - `minTargetBindRatio`:最低绑定比例(例如 `0.7` 表示至少 70% 红方武器有目标)。 - `allowReserveWithoutTarget`: @@ -79,7 +84,7 @@ - 导弹发射车优先绑定蓝方导弹能力目标 - 当优先池不足时自动回退到地面池/全目标池,保证大部分武器有目标。 -### 2.6 阵位规则参数(新增) +### 2.7 阵位规则参数(新增) - `enablePositionRules`:阵位规则总开关。 - 阵位输入来源:`blueTask.warZoneLocation` 与 `blueTask.defZoneLocation`(各 4 个经纬点)。 - `fireUnitSpacingMeters`:防区/作战区点位间距(米),例如 `100` 代表约每 100 米一个火力单元。 @@ -113,7 +118,7 @@ } ``` -### 2.7 航迹规则参数(新增) +### 2.8 航迹规则参数(新增) - `enableTrajectoryRules`:航迹规则总开关。 - `strategyMode`:`auto/shortest/flank/interfere`。 - `auto`:智能选择策略。 @@ -141,6 +146,7 @@ - `装备组件匹配`、`组件参数匹配`:已作为 `legacy` 占位,不承担当前业务决策。 - 主决策在 `红方武器自适应装配规则`:调用 `configureRedWeaponsByBlue(...)`,按“映射配置”添加武器。 - 导弹增强在 `导弹联动增强规则`:调用 `applyMissileLinkage(...)`,受开关和阈值控制。 +- 全组件数量匹配在 `全组件数量匹配规则`:按红方 `targetId` 绑定蓝方装备,覆盖非导弹组件 `componentParams[0].number`;找不到组件/targetId 允许跳过。 - 任务命名在 `任务自动匹配规则`:调用 `assignTaskNameByRedWeapons(...)`,按红方最终武器自动生成任务名和 `dataType`。 - 炮类约束:命中炮类条件时,炮类武器只保留 `炮弹` 组件,单位 `范围米`。 - `targetId` 绑定:在装配后自动执行,尽量为红方武器绑定蓝方 `equipmentId`,允许少量空值冗余。 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 cb79be7..f462e22 100644 --- a/auto-solution-rule/src/main/resources/rules/fire-rule.drl +++ b/auto-solution-rule/src/main/resources/rules/fire-rule.drl @@ -149,6 +149,14 @@ function Map buildBusinessConfig() { cfg.put("blueMissileRangeDefault", 220); // 蓝方导弹范围默认值 cfg.put("minBlueMissileCountForLinkage", 1); // 联动触发门槛 + // ---------- 全组件数量匹配(可改) ---------- + // 逻辑:按红方武器 targetId 找到对应蓝方装备(equipmentId),然后覆盖所有“非导弹组件”的数量 + cfg.put("enableComponentQuantityMatch", Boolean.TRUE); + // 格式示例:蓝穿甲弹->红穿甲弹,蓝火控雷达->红火控雷达;为空则默认 deviceName 一致匹配 + cfg.put("componentDeviceNameMappingCsv", ""); + // 跳过覆盖:蓝方组件 deviceName 包含该关键词则不覆盖(默认用来保留导弹联动偏移) + cfg.put("skipMissileComponentsByNameContains", "导弹"); + // ---------- 任务自动命名模板(可改) ---------- // 任务分类优先级:导弹突击 > 防空压制 > 反装甲打击 > 炮火压制 > 通用打击 cfg.put("taskName_missile_strike", "导弹突击打击任务"); @@ -218,6 +226,18 @@ then applyMissileLinkage($fact, cfg); end +//------------------------------------------------------------------------------- +// 全组件数量匹配:按 redWeapon.targetId 对应蓝方装备,覆盖“非导弹组件”的数量 +rule "全组件数量匹配规则" +agenda-group "打击任务" +salience 53 +when + $fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方") +then + Map cfg = buildBusinessConfig(); + applyAllComponentQuantities($fact, cfg); +end + //------------------------------------------------------------------------------- rule "任务自动匹配规则" agenda-group "打击任务" @@ -581,6 +601,159 @@ function void bindTargetIdsForRedWeapons(List redWeapons, List blueWeapons, Map } } +//------------------------------------------------------------------------------- +// component 映射解析 + 全组件数量覆盖 +function Map parseDeviceNameMapping(String csv) { + Map result = new java.util.HashMap(); + if (csv == null) { + return result; + } + String text = csv.trim(); + if (text.equals("")) { + return result; + } + String[] parts = text.split(","); + for (int i = 0; i < parts.length; i++) { + String one = parts[i]; + if (one == null) { + continue; + } + String item = one.trim(); + if (item.equals("")) { + continue; + } + int idx = item.indexOf("->"); + if (idx <= 0 || idx >= item.length() - 2) { + continue; + } + String left = item.substring(0, idx).trim(); + String right = item.substring(idx + 2).trim(); + if (left.equals("") || right.equals("")) { + continue; + } + result.put(left, right); + } + return result; +} + +function void applyAllComponentQuantities(FactTask fact, Map cfg) { + if (fact == null || fact.getBlueTask() == null || fact.getRedTask() == null) { + return; + } + if (!readBooleanCfg(cfg, "enableComponentQuantityMatch", true)) { + return; + } + Task blueTask = fact.getBlueTask(); + Task redTask = fact.getRedTask(); + List blueWeapons = blueTask.getTaskWeapons(); + List redWeapons = redTask.getTaskWeapons(); + if (blueWeapons == null || blueWeapons.isEmpty() || redWeapons == null || redWeapons.isEmpty()) { + return; + } + + 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; + } + } + + // 遍历红方每个武器:用 targetId 找蓝方装备(equipmentId) + for (Object objR : redWeapons) { + Weapon redWeapon = (Weapon) objR; + if (redWeapon == null) { + continue; + } + String targetId = redWeapon.getTargetId(); + if (isBlank(targetId)) { + // 允许不匹配:targetId 为空直接跳过 + continue; + } + + Weapon blueWeapon = null; + for (Object objB : blueWeapons) { + Weapon w = (Weapon) objB; + if (w == null) { + continue; + } + String eqId = w.getEquipmentId(); + if (eqId != null && eqId.equals(targetId)) { + blueWeapon = w; + break; + } + } + if (blueWeapon == null) { + continue; + } + + List blueComps = blueWeapon.getComponents(); + List redComps = redWeapon.getComponents(); + if (blueComps == null || blueComps.isEmpty() || redComps == null || redComps.isEmpty()) { + continue; + } + + // 用蓝方组件驱动覆盖红方组件数量 + for (Object objBC : blueComps) { + SubComponents blueComp = (SubComponents) objBC; + if (blueComp == null) { + continue; + } + String blueCompName = blueComp.getDeviceName(); + if (isBlank(blueCompName)) { + continue; + } + + // 跳过导弹组件覆盖,避免覆盖导弹联动偏移逻辑 + if (skipKeyword != null && blueCompName.contains(skipKeyword)) { + continue; + } + + String redCompName = (String) deviceNameMapping.get(blueCompName); + if (redCompName == null || redCompName.trim().equals("")) { + redCompName = blueCompName; + } + + SubComponents redComp = null; + for (Object objRC : redComps) { + SubComponents rc = (SubComponents) objRC; + if (rc == null) { + continue; + } + if (redCompName.equals(rc.getDeviceName())) { + redComp = rc; + break; + } + } + if (redComp == null) { + // 允许不匹配:红方下没有该组件则跳过 + continue; + } + + List blueParams = blueComp.getComponentParams(); + List redParams = redComp.getComponentParams(); + if (blueParams == null || blueParams.isEmpty() || redParams == null || redParams.isEmpty()) { + continue; + } + + ComponentParam blueFirst = (ComponentParam) blueParams.get(0); + ComponentParam redFirst = (ComponentParam) redParams.get(0); + if (blueFirst == null || redFirst == null) { + continue; + } + if (blueFirst.getNumber() == null) { + continue; + } + + // 只覆盖数量(componentParams[0].number) + redFirst.setNumber(blueFirst.getNumber()); + } + } +} + function void prepareDeploymentPools(FactTask fact, Map cfg, Map runtime) { if (!readBooleanCfg(cfg, "enablePositionRules", true)) { return;