From 9ff0e50bba48bd97a6f8ed9f2f41347de76ea516 Mon Sep 17 00:00:00 2001 From: MHW Date: Fri, 27 Mar 2026 16:56:07 +0800 Subject: [PATCH] =?UTF-8?q?=E7=81=AB=E5=8A=9B=E8=A7=84=E5=88=99=EF=BC=9A?= =?UTF-8?q?=E8=A3=85=E5=A4=87=E7=B1=BB=E5=9E=8B=E8=A7=84=E5=88=99=E3=80=81?= =?UTF-8?q?=E7=9B=AE=E6=A0=87=E8=A7=84=E5=88=99=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solution/rule/config/DroolsConfig.java | 81 +- .../rule/domain/request/RequestDTO.java | 2 +- .../domain/simplerulepojo/SubComponents.java | 1 + .../rule/domain/simplerulepojo/Task.java | 6 + .../simpstrategy/FireRuleStrategyFactory.java | 2 +- .../rule/simpstrategy/impl/BlowStrategy.java | 6 + .../src/main/resources/rules/fire-rule.drl | 978 +++++++++++++++++- 7 files changed, 1022 insertions(+), 54 deletions(-) diff --git a/auto-solution-rule/src/main/java/com/solution/rule/config/DroolsConfig.java b/auto-solution-rule/src/main/java/com/solution/rule/config/DroolsConfig.java index 1f1db0c..f2b74e1 100644 --- a/auto-solution-rule/src/main/java/com/solution/rule/config/DroolsConfig.java +++ b/auto-solution-rule/src/main/java/com/solution/rule/config/DroolsConfig.java @@ -5,14 +5,14 @@ import org.kie.api.KieServices; import org.kie.api.builder.KieBuilder; import org.kie.api.builder.KieFileSystem; import org.kie.api.builder.KieRepository; -import org.springframework.core.io.Resource; +import org.kie.api.builder.Message; import org.kie.api.runtime.KieContainer; +import org.kie.api.runtime.KieSession; import org.kie.internal.io.ResourceFactory; import org.kie.spring.KModuleBeanFactoryPostProcessor; -import org.kie.spring.annotations.KModuleAnnotationPostProcessor; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; @@ -21,48 +21,71 @@ import java.io.IOException; @Configuration public class DroolsConfig { - //规则文件存放目录 - private static final String RULE_PATH = "rules/"; + private static final String RULE_PATH = "classpath*:rules/**/*.drl"; private final KieServices kieServices = KieServices.Factory.get(); - //扫描规则文件 + /** + * 加载规则文件 + */ @Bean - @ConditionalOnMissingBean public KieFileSystem kieFileSystem() throws IOException { KieFileSystem kieFileSystem = kieServices.newKieFileSystem(); - ResourcePatternResolver resourcePatternResolver = - new PathMatchingResourcePatternResolver(); - Resource[] files = resourcePatternResolver.getResources("classpath*:" + RULE_PATH + "*.*"); - String path = null; - for (Resource file : files) { - path = RULE_PATH + file.getFilename(); - kieFileSystem.write(ResourceFactory.newClassPathResource(path, "UTF-8")); + + ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource[] resources = resolver.getResources(RULE_PATH); + + for (Resource resource : resources) { + kieFileSystem.write( + ResourceFactory.newInputStreamResource(resource.getInputStream()) + .setSourcePath("rules/" + resource.getFilename()) + ); } - return kieFileSystem; + + return kieFileSystem; } - //创建KieContainer + /** + * 构建 KieContainer + */ @Bean - @ConditionalOnMissingBean - public KieContainer kieContainer () throws IOException { - KieRepository kieRepository = kieServices.getRepository(); - kieRepository.addKieModule(kieRepository::getDefaultReleaseId); - KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem()); + public KieContainer kieContainer(KieFileSystem kieFileSystem) { + + KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem); kieBuilder.buildAll(); - return kieServices.newKieContainer(kieRepository.getDefaultReleaseId()); + + // 检查错误 + if (kieBuilder.getResults().hasMessages(Message.Level.ERROR)) { + throw new RuntimeException("Drools规则编译错误:\n" + + kieBuilder.getResults().toString()); + } + + KieRepository repository = kieServices.getRepository(); + + return kieServices.newKieContainer(repository.getDefaultReleaseId()); } + /** + * KieBase + */ @Bean - @ConditionalOnMissingBean - public KieBase kieBase() throws IOException { - return kieContainer().getKieBase(); + public KieBase kieBase(KieContainer kieContainer) { + return kieContainer.getKieBase(); } + /** + * KieSession(推荐加) + */ @Bean - @ConditionalOnMissingBean - public KModuleBeanFactoryPostProcessor kiePostProcessor() throws IOException { - return new KModuleAnnotationPostProcessor(); + public KieSession kieSession(KieContainer kieContainer) { + return kieContainer.newKieSession(); } -} + /** + * 必须是 static + */ + @Bean + public static KModuleBeanFactoryPostProcessor kiePostProcessor() { + return new KModuleBeanFactoryPostProcessor(); + } +} \ No newline at end of file diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/request/RequestDTO.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/request/RequestDTO.java index 6c0bff1..6444e3c 100644 --- a/auto-solution-rule/src/main/java/com/solution/rule/domain/request/RequestDTO.java +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/request/RequestDTO.java @@ -19,5 +19,5 @@ public class RequestDTO { //装备列表 private List equipments; - private List tasks; + //private List tasks; } diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/SubComponents.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/SubComponents.java index e26a241..05a6441 100644 --- a/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/SubComponents.java +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/SubComponents.java @@ -13,6 +13,7 @@ public class SubComponents { //设备名称 private String deviceName; + //设备组件参数 private List componentParams; } 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 df425d2..355c992 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 @@ -33,4 +33,10 @@ public class Task { //任务目标id private String targetId; + + //作战区经纬度 + private Coordinate warZoneLocation; + + //防区经纬度 + private Coordinate defZoneLocation; } diff --git a/auto-solution-rule/src/main/java/com/solution/rule/simpstrategy/FireRuleStrategyFactory.java b/auto-solution-rule/src/main/java/com/solution/rule/simpstrategy/FireRuleStrategyFactory.java index 61e36db..734419b 100644 --- a/auto-solution-rule/src/main/java/com/solution/rule/simpstrategy/FireRuleStrategyFactory.java +++ b/auto-solution-rule/src/main/java/com/solution/rule/simpstrategy/FireRuleStrategyFactory.java @@ -35,7 +35,7 @@ public class FireRuleStrategyFactory { * @throws IllegalArgumentException 如果编码无效或策略未注册 */ public FireRuleStrategy getStrategy(int code) { - SceneType type = SceneType.fromCode(code); + FireRUleType type = FireRUleType.fromCode(code); FireRuleStrategy strategy = strategyMap.get(type); if (strategy == null) { throw new IllegalArgumentException("未找到任务 " + code + " 对应的策略实现"); diff --git a/auto-solution-rule/src/main/java/com/solution/rule/simpstrategy/impl/BlowStrategy.java b/auto-solution-rule/src/main/java/com/solution/rule/simpstrategy/impl/BlowStrategy.java index 66a9763..377c6c0 100644 --- a/auto-solution-rule/src/main/java/com/solution/rule/simpstrategy/impl/BlowStrategy.java +++ b/auto-solution-rule/src/main/java/com/solution/rule/simpstrategy/impl/BlowStrategy.java @@ -9,6 +9,9 @@ import org.kie.api.runtime.KieSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import java.util.HashMap; +import java.util.Map; + @Component public class BlowStrategy implements FireRuleStrategy { @@ -24,6 +27,9 @@ public class BlowStrategy implements FireRuleStrategy { redTask.setTargetId(task.getId()); redTask.setSide("红方"); + Map globalParams = new HashMap<>(); + kieSession.setGlobal("globalParams", globalParams); + factTask.setRedTask(redTask); factTask.setBlueTask(task); kieSession.insert(factTask); 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 dc92461..b2e793b 100644 --- a/auto-solution-rule/src/main/resources/rules/fire-rule.drl +++ b/auto-solution-rule/src/main/resources/rules/fire-rule.drl @@ -3,53 +3,50 @@ package rules; import com.solution.rule.domain.simplerulepojo.fact.FactTask; import com.solution.rule.domain.simplerulepojo.Task; import com.solution.rule.domain.simplerulepojo.Weapon; -import java.util.List -import java.util.Map -import com.solution.rule.domain.simplerulepojo.Task -import java.util.ArrayList +import com.solution.rule.domain.simplerulepojo.SubComponents; +import com.solution.rule.domain.simplerulepojo.ComponentParam; + +import java.util.List; +import java.util.Map; +import java.util.ArrayList; global java.util.Map globalParams; +//------------------------------------------------------------------------------- rule "任务匹配1" agenda-group "打击任务" +salience 100 when - $task : FactTask(blueTask.side == "蓝方", blueTask.drawName contains "F-16--7") - $redTask : FactTask(redTask.side == "红方") then - // 直接修改红方任务对象 - Task redTask = $redTask.getRedTask(); - redTask.setDrawName("F-16打击任务"); - redTask.setDataType("plane"); + // legacy 占位:旧的固定字符串匹配已停用,改由“任务自动匹配规则”统一处理。 end //------------------------------------------------------------------------------- rule "任务匹配2" agenda-group "打击任务" +salience 100 when - $task : FactTask(blueTask.side == "蓝方", blueTask.drawName contains "驱逐舰任务") - $redTask : FactTask(redTask.side == "红方") then - // 直接修改红方任务对象 - Task redTask = $redTask.getRedTask(); - redTask.setDrawName("导弹发射车打击任务"); - redTask.setDataType("ground"); + // legacy 占位:旧的固定字符串匹配已停用,改由“任务自动匹配规则”统一处理。 end //------------------------------------------------------------------------------- rule "威胁等级规则" agenda-group "打击任务" +salience 90 when //如果蓝方威胁等级大于等于3,则全局武器数量为3,添加插入导弹发射车辆和防空导弹武器 - $task : FactTask(blueTask.side == "蓝方",blueTask.threatLevel >= 3) + $task : FactTask(blueTask.side == "蓝方",blueTask.threatLevel >= "3") $redTask : FactTask(redTask.side == "红方") then //设置平台下组件的数量 globalParams.put("platNum",3); //威胁等级大于等于3固定插入导弹发射车辆 // ========== 调用函数 ========== - threatLevel($redTask, globalParams); + threatLevels($redTask, globalParams); end //------------------------------------------------------------------------------- rule "地面类型匹配规则" agenda-group "打击任务" +salience 80 when //如果蓝方武器为地面类型且高度不超过500米,则使用插入空中力量打击 $task : FactTask(blueTask.side == "蓝方") @@ -65,15 +62,948 @@ then taskWeapons.add(weapon); end //------------------------------------------------------------------------------- -// kcontext.getKieRuntime().getAgenda() -// .getAgendaGroup("MATCH") -// .setFocus(); +rule "空中类型匹配规则" +agenda-group "打击任务" +salience 80 +when + //如果蓝方武器为地面类型且高度不超过500米,则使用插入空中力量打击 + $task : FactTask(blueTask.side == "蓝方") + $weapons : List() from $task.blueTask.taskWeapons + $weapon : Weapon(supportType == "overhead") from $weapons +then + Task redTask = $task.getRedTask(); + List taskWeapons = redTask.getTaskWeapons(); + Weapon weapon = new Weapon(); + weapon.setName("F-16"); + weapon.setNumber((Integer) globalParams.get("platNum")); + weapon.setSupportType("plane"); + taskWeapons.add(weapon); +end +//------------------------------------------------------------------------------- +rule "装备组件匹配" +agenda-group "打击任务" +salience 70 +when +then + // legacy 规则已停用:完整武器库逻辑已在 //TODO 下接管。 + // 保留该规则名称便于回滚和历史追踪,不再执行任何动作。 +end +//------------------------------------------------------------------------------- +rule "组件参数匹配" +agenda-group "打击任务" +salience 60 +when +then + // legacy 占位规则:参数处理已并入“红方武器自适应装配规则/导弹联动增强规则”。 +end +//------------------------------------------------------------------------------- +//TODO +//------------------------------------------------------------------------------- +// ========================= 业务可改区(只改这里) ========================= +// 说明: +// 1) 业务人员只改 buildBusinessConfig() 里的值,其他函数不要改。 +// 2) 规则是“严格白名单”,未命中条件时允许不匹配(不新增红方武器)。 + // 3) 可通过开关控制是否启用空中/地面/装甲/导弹联动策略。 + // 4) 业务可直接配置“蓝方类型 -> 红方方案(多选)”,例如坦克可选火箭或导弹系统。 +function Map buildBusinessConfig() { + Map cfg = new java.util.HashMap(); + // ---------- 红方完整武器库名称映射(可改) ---------- + cfg.put("redStrikeDroneName", "火力打击无人机"); + cfg.put("redArmedHelicopterName", "武装直升机"); + cfg.put("redHowitzerName", "迫榴炮"); + cfg.put("redVehicleMortarName", "车载迫击炮"); + cfg.put("redAaWeaponName", "防空导弹武器"); + cfg.put("redAtRocketName", "反坦克火箭"); + cfg.put("redAtMissileSystemName", "反坦克导弹系统"); + cfg.put("redMissileVehicleName", "导弹发射车"); + + // ---------- 白名单开关(可改) ---------- + cfg.put("enableAirRule", Boolean.TRUE); // 蓝方空中 -> 红方空中反制组 + cfg.put("enableGroundRule", Boolean.TRUE); // 蓝方地面 -> 红方炮类反制组 + cfg.put("enableArmorRule", Boolean.TRUE); // 蓝方装甲 -> 红方反坦克组 + cfg.put("enableMissileVehicleRule", Boolean.FALSE); // 蓝方导弹 -> 红方导弹发射车(默认关) + cfg.put("enableMissileLinkage", Boolean.TRUE); // 导弹参数联动开关 + 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("defaultAirNum", 1); + cfg.put("defaultGroundNum", 1); + cfg.put("defaultMissileVehicleNum", 1); + cfg.put("shellRangeDefault", "1500"); // 炮类单位固定:范围米 + cfg.put("missileCountOffset", 1); // 红方导弹数量 = 蓝方 + offset + cfg.put("missileRangeOffset", 80); // 红方导弹范围增量 + cfg.put("blueMissileRangeDefault", 220); // 蓝方导弹范围默认值 + cfg.put("minBlueMissileCountForLinkage", 1); // 联动触发门槛 + + // ---------- 任务自动命名模板(可改) ---------- + // 任务分类优先级:导弹突击 > 防空压制 > 反装甲打击 > 炮火压制 > 通用打击 + cfg.put("taskName_missile_strike", "导弹突击打击任务"); + cfg.put("taskName_air_defence", "防空压制打击任务"); + cfg.put("taskName_anti_armor", "反装甲打击任务"); + cfg.put("taskName_artillery", "炮火压制打击任务"); + cfg.put("taskName_general", "通用打击任务"); + cfg.put("taskDataType_missile_strike", "missile-strike"); + cfg.put("taskDataType_air_defence", "air-defence"); + cfg.put("taskDataType_anti_armor", "anti-armor"); + cfg.put("taskDataType_artillery", "artillery"); + cfg.put("taskDataType_general", "strike"); + + return cfg; +} + +//------------------------------------------------------------------------------- +rule "红方武器自适应装配规则" +agenda-group "打击任务" +salience 55 +when + // 蓝方与红方任务都存在时触发,做“武器+组件”的基础装配 + $fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方") +then + Map cfg = buildBusinessConfig(); + configureRedWeaponsByBlue($fact, cfg); +end + +//------------------------------------------------------------------------------- +rule "导弹联动增强规则" +agenda-group "打击任务" +salience 54 +when + // 在基础装配后执行:若蓝方挂载导弹,红方空中武器自动增强导弹能力 + $fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方") +then + Map cfg = buildBusinessConfig(); + applyMissileLinkage($fact, cfg); +end + +//------------------------------------------------------------------------------- +rule "任务自动匹配规则" +agenda-group "打击任务" +salience 50 +when + // 以红方最终武器为主自动生成任务名,保证任务名与武器一致 + $fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方") +then + Map cfg = buildBusinessConfig(); + assignTaskNameByRedWeapons($fact, cfg); +end + +//------------------------------------------------------------------------------- +// 说明:以下函数全部是 DRL function(不是 Java 类方法) +// 目标:让不懂代码的业务同事只改“可调整常量区”即可完成策略调整 + +// 根据蓝方武器结构,按业务映射装配红方武器并写入基础组件 +function void configureRedWeaponsByBlue( + FactTask fact, + Map cfg +) { + if (fact == null || fact.getBlueTask() == null || fact.getRedTask() == null) { + return; + } + List blueWeapons = fact.getBlueTask().getTaskWeapons(); + if (blueWeapons == null || blueWeapons.isEmpty()) { + return; + } + Task redTask = fact.getRedTask(); + List redWeapons = redTask.getTaskWeapons(); + if (redWeapons == null) { + redWeapons = new ArrayList<>(); + redTask.setTaskWeapons(redWeapons); + } + + // 蓝方组件模板(用于测试和参数联动):若蓝方没填组件,给一个合理默认值 + buildBlueTestComponents(blueWeapons); + + boolean hasBlueAir = false; + boolean hasBlueGround = false; + boolean hasBlueArtillery = false; + boolean hasBlueArmor = false; + boolean hasBlueMissile = false; + for (Object obj : blueWeapons) { + Weapon blueWeapon = (Weapon) obj; + if (blueWeapon == null) { + continue; + } + if (isAirWeapon(blueWeapon)) { + hasBlueAir = true; + } + if (isGroundWeapon(blueWeapon)) { + hasBlueGround = true; + } + if (isArtilleryWeapon(blueWeapon)) { + hasBlueArtillery = true; + } + if (isArmorWeapon(blueWeapon)) { + hasBlueArmor = true; + } + if (hasMissileComponent(blueWeapon)) { + hasBlueMissile = true; + } + } + + boolean allowMultiGroup = readBooleanCfg(cfg, "allowMultiGroup", true); + boolean matchedAny = false; + + // 严格白名单-1:空中目标反制组(由 map_air_targets 控制) + if (readBooleanCfg(cfg, "enableAirRule", true) && hasBlueAir && (!matchedAny || allowMultiGroup)) { + int before = redWeapons.size(); + applyMappedWeapons(redWeapons, cfg, "map_air_targets", readIntCfg(cfg, "defaultAirNum", 1), readBooleanCfg(cfg, "enableArmedHelicopterOnAir", true)); + if (redWeapons.size() > before) { + matchedAny = true; + } + } + + // 严格白名单-2:地面目标炮类组(由 map_ground_targets 控制) + if (readBooleanCfg(cfg, "enableGroundRule", true) && hasBlueGround && (!matchedAny || allowMultiGroup)) { + int before = redWeapons.size(); + applyMappedWeapons(redWeapons, cfg, "map_ground_targets", readIntCfg(cfg, "defaultGroundNum", 1), true); + if (redWeapons.size() > before) { + matchedAny = true; + } + } + + // 严格白名单-3:装甲目标反坦克组(由 map_armor_targets 控制) + if (readBooleanCfg(cfg, "enableArmorRule", true) && hasBlueArmor && (!matchedAny || allowMultiGroup)) { + int before = redWeapons.size(); + applyMappedWeapons(redWeapons, cfg, "map_armor_targets", readIntCfg(cfg, "defaultGroundNum", 1), true); + if (redWeapons.size() > before) { + matchedAny = true; + } + } + + // 严格白名单-4:炮类目标反制组(由 map_artillery_targets 控制) + if (hasBlueArtillery && (!matchedAny || allowMultiGroup)) { + int before = redWeapons.size(); + applyMappedWeapons(redWeapons, cfg, "map_artillery_targets", readIntCfg(cfg, "defaultGroundNum", 1), true); + if (redWeapons.size() > before) { + matchedAny = true; + } + } + + // 严格白名单-5:导弹补充组(默认关闭,由 map_missile_targets 控制) + if (readBooleanCfg(cfg, "enableMissileVehicleRule", false) && hasBlueMissile && (!matchedAny || allowMultiGroup)) { + int before = redWeapons.size(); + applyMappedWeapons(redWeapons, cfg, "map_missile_targets", readIntCfg(cfg, "defaultMissileVehicleNum", 1), true); + if (redWeapons.size() > before) { + matchedAny = true; + } + } + + // 炮类限制仅对已匹配策略生效;若本轮未命中白名单,不做任何新增/补齐 + if (matchedAny && hasBlueArtillery) { + limitRedArtilleryToShellOnly(redWeapons, (String) cfg.get("shellRangeDefault")); + } +} + +function void applyMappedWeapons(List redWeapons, Map cfg, String mapKey, int defaultNum, boolean allowArmedHelicopter) { + List mappedNames = parseMappedWeaponNames(cfg, mapKey); + if (mappedNames == null || mappedNames.isEmpty()) { + return; + } + for (Object obj : mappedNames) { + String weaponName = (String) obj; + if (!allowArmedHelicopter && weaponName != null && weaponName.equals((String) cfg.get("redArmedHelicopterName"))) { + continue; + } + String supportType = inferSupportTypeByWeaponName(weaponName); + Weapon redWeapon = ensureRedWeapon(redWeapons, weaponName, supportType, defaultNum); + ensureBasicRedComponents(redWeapon); + } +} + +function List parseMappedWeaponNames(Map cfg, String mapKey) { + List result = new ArrayList(); + if (cfg == null || mapKey == null) { + return result; + } + Object raw = cfg.get(mapKey); + if (raw == null) { + return result; + } + String text = String.valueOf(raw); + if (text == null || text.trim().equals("")) { + return result; + } + String[] parts = text.split(","); + for (int i = 0; i < parts.length; i++) { + String one = parts[i]; + if (one == null) { + continue; + } + String name = one.trim(); + if (name.equals("")) { + continue; + } + if (isValidRedWeaponNameByConfig(cfg, name) && !containsString(result, name)) { + result.add(name); + } + } + return result; +} + +function boolean isValidRedWeaponNameByConfig(Map cfg, String weaponName) { + if (cfg == null || weaponName == null || weaponName.equals("")) { + return false; + } + return weaponName.equals((String) cfg.get("redStrikeDroneName")) + || weaponName.equals((String) cfg.get("redArmedHelicopterName")) + || weaponName.equals((String) cfg.get("redHowitzerName")) + || weaponName.equals((String) cfg.get("redVehicleMortarName")) + || weaponName.equals((String) cfg.get("redAaWeaponName")) + || weaponName.equals((String) cfg.get("redAtRocketName")) + || weaponName.equals((String) cfg.get("redAtMissileSystemName")) + || weaponName.equals((String) cfg.get("redMissileVehicleName")); +} + +function boolean containsString(List values, String target) { + if (values == null || target == null) { + return false; + } + for (Object obj : values) { + if (obj != null && target.equals(String.valueOf(obj))) { + return true; + } + } + return false; +} + +function String inferSupportTypeByWeaponName(String weaponName) { + if (weaponName == null) { + return "ground"; + } + if (weaponName.contains("无人机") || weaponName.contains("直升机")) { + return "overhead"; + } + if (weaponName.contains("防空导弹")) { + return "antiaircraft"; + } + return "ground"; +} + +// 蓝方若有导弹,红方空中武器补导弹:数量 = 蓝方 + offset,范围略高 +function void applyMissileLinkage( + FactTask fact, + Map cfg +) { + if (fact == null || fact.getBlueTask() == null || fact.getRedTask() == null) { + return; + } + List blueWeapons = fact.getBlueTask().getTaskWeapons(); + List redWeapons = fact.getRedTask().getTaskWeapons(); + if (blueWeapons == null || blueWeapons.isEmpty() || redWeapons == null || redWeapons.isEmpty()) { + return; + } + + if (!readBooleanCfg(cfg, "enableMissileLinkage", true)) { + return; + } + int blueMissileCount = countBlueMissileNumber(blueWeapons); + if (blueMissileCount < readIntCfg(cfg, "minBlueMissileCountForLinkage", 1)) { + return; + } + int blueMissileRange = readBlueMissileRange(blueWeapons, readIntCfg(cfg, "blueMissileRangeDefault", 220)); + int redMissileTarget = blueMissileCount + readIntCfg(cfg, "missileCountOffset", 1); + int redRangeTarget = blueMissileRange + readIntCfg(cfg, "missileRangeOffset", 80); + + for (Object obj : redWeapons) { + Weapon redWeapon = (Weapon) obj; + if (redWeapon == null || !isRedAirWeapon(redWeapon)) { + continue; + } + ensureMissileComponentForRedAirWeapon(redWeapon, redMissileTarget, redRangeTarget); + } +} + +function void assignTaskNameByRedWeapons(FactTask fact, Map cfg) { + if (fact == null || fact.getRedTask() == null) { + return; + } + Task redTask = fact.getRedTask(); + List redWeapons = redTask.getTaskWeapons(); + String category = classifyTaskByRedWeapons(redWeapons); + + // 一致性校验:分类与武器不一致则回落通用打击 + if (!isTaskCategoryConsistent(category, redWeapons)) { + category = "general"; + } + + redTask.setDrawName(resolveTaskNameByCategory(cfg, category)); + redTask.setDataType(resolveTaskDataTypeByCategory(cfg, category)); +} + +function String classifyTaskByRedWeapons(List redWeapons) { + if (redWeapons == null || redWeapons.isEmpty()) { + return "general"; + } + // 符合实际的优先级:导弹突击 > 防空压制 > 反装甲 > 炮火压制 > 通用 + if (hasRedWeaponName(redWeapons, "导弹发射车")) { + return "missile_strike"; + } + if (hasAnyRedWeaponName(redWeapons, "防空导弹武器,火力打击无人机,武装直升机")) { + return "air_defence"; + } + if (hasAnyRedWeaponName(redWeapons, "反坦克火箭,反坦克导弹系统")) { + return "anti_armor"; + } + if (hasAnyRedWeaponName(redWeapons, "迫榴炮,车载迫击炮")) { + return "artillery"; + } + return "general"; +} + +function boolean isTaskCategoryConsistent(String category, List redWeapons) { + if (category == null) { + return false; + } + if (category.equals("missile_strike")) { + return hasRedWeaponName(redWeapons, "导弹发射车"); + } + if (category.equals("air_defence")) { + return hasAnyRedWeaponName(redWeapons, "防空导弹武器,火力打击无人机,武装直升机"); + } + if (category.equals("anti_armor")) { + return hasAnyRedWeaponName(redWeapons, "反坦克火箭,反坦克导弹系统"); + } + if (category.equals("artillery")) { + return hasAnyRedWeaponName(redWeapons, "迫榴炮,车载迫击炮"); + } + return true; +} + +function String resolveTaskNameByCategory(Map cfg, String category) { + if (cfg == null || category == null) { + return "通用打击任务"; + } + if (category.equals("missile_strike")) { + return String.valueOf(cfg.get("taskName_missile_strike")); + } + if (category.equals("air_defence")) { + return String.valueOf(cfg.get("taskName_air_defence")); + } + if (category.equals("anti_armor")) { + return String.valueOf(cfg.get("taskName_anti_armor")); + } + if (category.equals("artillery")) { + return String.valueOf(cfg.get("taskName_artillery")); + } + return String.valueOf(cfg.get("taskName_general")); +} + +function String resolveTaskDataTypeByCategory(Map cfg, String category) { + if (cfg == null || category == null) { + return "strike"; + } + if (category.equals("missile_strike")) { + return String.valueOf(cfg.get("taskDataType_missile_strike")); + } + if (category.equals("air_defence")) { + return String.valueOf(cfg.get("taskDataType_air_defence")); + } + if (category.equals("anti_armor")) { + return String.valueOf(cfg.get("taskDataType_anti_armor")); + } + if (category.equals("artillery")) { + return String.valueOf(cfg.get("taskDataType_artillery")); + } + return String.valueOf(cfg.get("taskDataType_general")); +} + +function boolean hasAnyRedWeaponName(List redWeapons, String commaNames) { + if (redWeapons == null || redWeapons.isEmpty() || commaNames == null || commaNames.equals("")) { + return false; + } + String[] names = commaNames.split(","); + for (int i = 0; i < names.length; i++) { + String one = names[i]; + if (one == null) { + continue; + } + if (hasRedWeaponName(redWeapons, one.trim())) { + return true; + } + } + return false; +} + +function boolean hasRedWeaponName(List redWeapons, String weaponName) { + if (redWeapons == null || redWeapons.isEmpty() || weaponName == null || weaponName.equals("")) { + return false; + } + for (Object obj : redWeapons) { + Weapon w = (Weapon) obj; + if (w != null && w.getName() != null && w.getName().equals(weaponName)) { + return true; + } + } + return false; +} + +// 蓝方组件模板:仅在组件缺失时补齐,作为规则联动测试用 +function void buildBlueTestComponents(List weapons) { + if (weapons == null || weapons.isEmpty()) { + return; + } + for (Object obj : weapons) { + Weapon weapon = (Weapon) obj; + if (weapon == null) { + continue; + } + List components = weapon.getComponents(); + if (components == null) { + components = new ArrayList<>(); + weapon.setComponents(components); + } + if (!components.isEmpty()) { + continue; + } + + // 蓝方主要用于触发规则,模板尽量简洁 + if (isAirWeapon(weapon)) { + components.add(buildComponent("火控雷达", "220", "探测范围米", 1)); + components.add(buildComponent("空空导弹", "220", "破坏范围米", 1)); + } else if (isArtilleryWeapon(weapon)) { + components.add(buildComponent("炮弹", "1200", "范围米", 6)); + } else if (isGroundWeapon(weapon)) { + components.add(buildComponent("机枪", "600", "射程米", 1)); + } + } +} + +// 红方基础组件模板:便于业务人员看懂武器都带了哪些能力 +function void ensureBasicRedComponents(Weapon weapon) { + if (weapon == null) { + return; + } + String name = weapon.getName(); + if (name == null) { + name = ""; + } + if (name.contains("防空导弹")) { + ensureComponent(weapon, "搜索雷达", "260", "探测范围米", 1); + ensureComponent(weapon, "防空导弹", "300", "破坏范围米", 1); + } else if (name.contains("无人机")) { + ensureComponent(weapon, "光电吊舱", "180", "识别范围米", 1); + ensureComponent(weapon, "空地导弹", "260", "破坏范围米", 1); + } else if (name.contains("武装直升机")) { + ensureComponent(weapon, "火控雷达", "220", "探测范围米", 1); + ensureComponent(weapon, "机载导弹", "280", "破坏范围米", 2); + } else if (name.contains("反坦克火箭")) { + ensureComponent(weapon, "火箭弹", "200", "破坏范围米", 4); + } else if (name.contains("反坦克导弹系统")) { + ensureComponent(weapon, "反坦克导弹", "320", "破坏范围米", 2); + ensureComponent(weapon, "激光测距", "180", "测距米", 1); + } else if (name.contains("迫榴炮") || name.contains("迫击炮")) { + ensureComponent(weapon, "炮弹", "1500", "范围米", 8); + } else if (name.contains("导弹发射车")) { + ensureComponent(weapon, "导弹发射架", "260", "破坏范围米", 1); + ensureComponent(weapon, "制导雷达", "240", "探测范围米", 1); + } else { + // 兜底组件,避免出现完全无组件的武器 + ensureComponent(weapon, "火控系统", "100", "作用范围米", 1); + } +} + +// 炮类限制:武器组件只能保留“炮弹”,并固定参数单位“范围米” +function void limitRedArtilleryToShellOnly(List redWeapons, String shellRangeDefault) { + if (redWeapons == null || redWeapons.isEmpty()) { + return; + } + for (Object obj : redWeapons) { + Weapon redWeapon = (Weapon) obj; + if (redWeapon == null || !isArtilleryWeapon(redWeapon)) { + continue; + } + List onlyShell = new ArrayList<>(); + onlyShell.add(buildComponent("炮弹", shellRangeDefault, "范围米", 8)); + redWeapon.setComponents(onlyShell); + } +} + +function Weapon ensureRedWeapon(List redWeapons, String name, String supportType, int number) { + for (Object obj : redWeapons) { + Weapon w = (Weapon) obj; + if (w != null && w.getName() != null && w.getName().equals(name)) { + if (w.getSupportType() == null || w.getSupportType().equals("")) { + w.setSupportType(supportType); + } + if (w.getNumber() == null || w.getNumber() <= 0) { + w.setNumber(number); + } + if (w.getComponents() == null) { + w.setComponents(new ArrayList<>()); + } + return w; + } + } + Weapon w = new Weapon(); + w.setName(name); + w.setSupportType(supportType); + w.setNumber(number); + w.setComponents(new ArrayList<>()); + redWeapons.add(w); + return w; +} + +function void ensureMissileComponentForRedAirWeapon(Weapon redWeapon, int missileNumber, int missileRange) { + List components = redWeapon.getComponents(); + if (components == null) { + components = new ArrayList<>(); + redWeapon.setComponents(components); + } + for (SubComponents c : components) { + if (c != null && c.getDeviceName() != null && c.getDeviceName().contains("导弹")) { + ensureOrUpdateParam(c, String.valueOf(missileRange), "破坏范围米", missileNumber); + return; + } + } + components.add(buildComponent("联动导弹", String.valueOf(missileRange), "破坏范围米", missileNumber)); +} + +function void ensureComponent(Weapon weapon, String deviceName, String value, String unit, int number) { + List components = weapon.getComponents(); + if (components == null) { + components = new ArrayList<>(); + weapon.setComponents(components); + } + for (SubComponents c : components) { + if (c != null && c.getDeviceName() != null && c.getDeviceName().equals(deviceName)) { + ensureOrUpdateParam(c, value, unit, number); + return; + } + } + components.add(buildComponent(deviceName, value, unit, number)); +} + +function SubComponents buildComponent(String deviceName, String value, String unit, int number) { + SubComponents component = new SubComponents(); + component.setDeviceName(deviceName); + List params = new ArrayList<>(); + ComponentParam param = new ComponentParam(); + param.setAttDefaultValue(value); + param.setAttExplain(unit); + param.setNumber(number); + params.add(param); + component.setComponentParams(params); + return component; +} + +function void ensureOrUpdateParam(SubComponents component, String value, String unit, int number) { + List params = component.getComponentParams(); + if (params == null) { + params = new ArrayList<>(); + component.setComponentParams(params); + } + if (params.isEmpty()) { + ComponentParam param = new ComponentParam(); + param.setAttDefaultValue(value); + param.setAttExplain(unit); + param.setNumber(number); + params.add(param); + return; + } + ComponentParam first = params.get(0); + first.setAttDefaultValue(value); + first.setAttExplain(unit); + first.setNumber(number); +} + +function int countBlueMissileNumber(List weapons) { + int total = 0; + for (Object obj : weapons) { + Weapon w = (Weapon) obj; + if (w == null || w.getComponents() == null) { + continue; + } + for (SubComponents c : w.getComponents()) { + if (c == null || c.getDeviceName() == null || !c.getDeviceName().contains("导弹")) { + continue; + } + int n = 1; + if (c.getComponentParams() != null && !c.getComponentParams().isEmpty() && c.getComponentParams().get(0) != null && c.getComponentParams().get(0).getNumber() != null) { + n = c.getComponentParams().get(0).getNumber(); + } + total = total + n; + } + } + return total; +} + +function int readBlueMissileRange(List weapons, int fallback) { + int best = 0; + for (Object obj : weapons) { + Weapon w = (Weapon) obj; + if (w == null || w.getComponents() == null) { + continue; + } + for (SubComponents c : w.getComponents()) { + if (c == null || c.getDeviceName() == null || !c.getDeviceName().contains("导弹")) { + continue; + } + if (c.getComponentParams() == null || c.getComponentParams().isEmpty() || c.getComponentParams().get(0) == null) { + continue; + } + String value = c.getComponentParams().get(0).getAttDefaultValue(); + int parsed = parseIntSafe(value, fallback); + if (parsed > best) { + best = parsed; + } + } + } + if (best <= 0) { + return fallback; + } + return best; +} + +function int parseIntSafe(String text, int fallback) { + if (text == null || text.equals("")) { + return fallback; + } + try { + return Integer.parseInt(text.trim()); + } catch (Exception ex) { + return fallback; + } +} + +function int readIntCfg(Map cfg, String key, int fallback) { + if (cfg == null || key == null) { + return fallback; + } + Object value = cfg.get(key); + if (value == null) { + return fallback; + } + if (value instanceof Integer) { + return ((Integer) value).intValue(); + } + return parseIntSafe(String.valueOf(value), fallback); +} + +function boolean readBooleanCfg(Map cfg, String key, boolean fallback) { + if (cfg == null || key == null) { + return fallback; + } + Object value = cfg.get(key); + if (value == null) { + return fallback; + } + if (value instanceof Boolean) { + return ((Boolean) value).booleanValue(); + } + String text = String.valueOf(value); + if (text == null) { + return fallback; + } + return "true".equalsIgnoreCase(text.trim()); +} + +function boolean isRedAirWeapon(Weapon weapon) { + if (weapon == null) { + return false; + } + String supportType = weapon.getSupportType(); + String name = weapon.getName(); + return (supportType != null && (supportType.equals("overhead") || supportType.equals("plane"))) + || (name != null && (name.contains("无人机") || name.contains("直升机"))); +} + +function boolean isAirWeapon(Weapon weapon) { + if (weapon == null) { + return false; + } + String supportType = weapon.getSupportType(); + String name = weapon.getName(); + return (supportType != null && (supportType.equals("overhead") || supportType.equals("plane"))) + || (name != null && ( + name.contains("直升机") + || name.contains("地空导弹") + || name.contains("单兵防空导弹") + || name.contains("制导导弹") + || name.contains("无人机") + )); +} + +function boolean isGroundWeapon(Weapon weapon) { + if (weapon == null) { + return false; + } + String supportType = weapon.getSupportType(); + String name = weapon.getName(); + return (supportType != null && supportType.equals("ground")) + || (name != null && ( + name.contains("坦克") + || name.contains("装甲车") + || name.contains("迫击炮") + || name.contains("迫榴炮") + || name.contains("车载迫击炮") + || name.contains("导弹发射车") + || name.contains("反坦克") + )); +} + +function boolean isArtilleryWeapon(Weapon weapon) { + if (weapon == null || weapon.getName() == null) { + return false; + } + String name = weapon.getName(); + return name.contains("迫榴炮") + || name.contains("迫击炮") + || name.contains("车载迫击炮") + || name.contains("120mm"); +} + +function boolean isArmorWeapon(Weapon weapon) { + if (weapon == null || weapon.getName() == null) { + return false; + } + String name = weapon.getName(); + return name.contains("主战坦克") + || name.contains("坦克") + || name.contains("装甲车"); +} + +function boolean hasMissileComponent(Weapon weapon) { + if (weapon == null || weapon.getComponents() == null) { + return false; + } + for (SubComponents c : weapon.getComponents()) { + if (c != null && c.getDeviceName() != null && c.getDeviceName().contains("导弹")) { + return true; + } + } + return false; +} + +// ========== legacy 函数区(保留仅供回滚,不参与当前业务规则) ========== +function void matchLauncherComponents( + FactTask blueFact, + FactTask redFact, + String launcherName, + String redPlaneSupportType, + String redMissileVehicleKeyword, + String redMissileVehicleEnKeyword, + int redMoreThanBlueOffset, + int triggerBlueLauncherCount +) { + Task blueTask = blueFact.getBlueTask(); + Task redTask = redFact.getRedTask(); + if (blueTask == null || redTask == null) { + return; + } + + List blueWeapons = blueTask.getTaskWeapons(); + List redWeapons = redTask.getTaskWeapons(); + if (blueWeapons == null || redWeapons == null || redWeapons.isEmpty()) { + return; + } + + int blueLauncherCount = countLauncherComponents(blueWeapons, launcherName); + if (blueLauncherCount <= 0) { + return; + } + + // 规则1:红方若存在 plane 或导弹发射车,则这些武器都需要具备发射架 + List candidateRedWeapons = new ArrayList<>(); + for (Weapon redWeapon : redWeapons) { + if (isRedWeaponNeedLauncher(redWeapon, redPlaneSupportType, redMissileVehicleKeyword, redMissileVehicleEnKeyword)) { + candidateRedWeapons.add(redWeapon); + ensureWeaponHasLauncher(redWeapon, launcherName); + } + } + if (candidateRedWeapons.isEmpty()) { + return; + } + + // 规则2:当蓝方发射架数量达到触发值时,红方发射架数量 = 蓝方 + 可配置偏移量 + if (blueLauncherCount == triggerBlueLauncherCount) { + int targetRedLauncherCount = blueLauncherCount + redMoreThanBlueOffset; + int currentRedLauncherCount = countLauncherComponents(redWeapons, launcherName); + int needAdd = targetRedLauncherCount - currentRedLauncherCount; + if (needAdd > 0) { + Weapon fallbackWeapon = candidateRedWeapons.get(0); + for (int i = 0; i < needAdd; i++) { + addLauncherToWeapon(fallbackWeapon, launcherName); + } + } + } +} + +function boolean isRedWeaponNeedLauncher( + Weapon weapon, + String redPlaneSupportType, + String redMissileVehicleKeyword, + String redMissileVehicleEnKeyword +) { + if (weapon == null) { + return false; + } + String supportType = weapon.getSupportType(); + String weaponName = weapon.getName(); + return (supportType != null && supportType.equals(redPlaneSupportType)) + || (weaponName != null && (weaponName.contains(redMissileVehicleKeyword) || weaponName.contains(redMissileVehicleEnKeyword))); +} + +function void ensureWeaponHasLauncher(Weapon weapon, String launcherName) { + if (weapon == null) { + return; + } + List components = weapon.getComponents(); + if (components == null) { + components = new ArrayList<>(); + weapon.setComponents(components); + } + for (SubComponents component : components) { + if (component != null && component.getDeviceName() != null && component.getDeviceName().contains(launcherName)) { + return; + } + } + addLauncherToWeapon(weapon, launcherName); +} + +function void addLauncherToWeapon(Weapon weapon, String launcherName) { + List components = weapon.getComponents(); + if (components == null) { + components = new ArrayList<>(); + weapon.setComponents(components); + } + SubComponents launcher = new SubComponents(); + launcher.setDeviceName(launcherName); + components.add(launcher); +} + +function int countLauncherComponents(List weapons, String launcherName) { + if (weapons == null || weapons.isEmpty()) { + return 0; + } + int count = 0; + for (Object weaponObj : weapons) { + Weapon weapon = (Weapon) weaponObj; + if (weapon == null || weapon.getComponents() == null) { + continue; + } + for (SubComponents component : weapon.getComponents()) { + if (component != null && component.getDeviceName() != null && component.getDeviceName().contains(launcherName)) { + count++; + } + } + } + return count; +} -// ========== 定义函数 ========== //威胁等级添加武器函数 -function void threatLevel(FactTask redFact, Map params) { +function void threatLevels(FactTask redFact, Map params) { // 创建武器列表 List weapons = new ArrayList<>(); @@ -83,6 +1013,7 @@ function void threatLevel(FactTask redFact, Map params) { weapon1.setSupportType("ground"); weapon1.setEquipmentId("1"); weapon1.setName("missile-launching-vehicle"); + weapon1.setComponents(new ArrayList<>()); // 创建防空导弹武器 Weapon weapon2 = new Weapon(); @@ -90,6 +1021,7 @@ function void threatLevel(FactTask redFact, Map params) { weapon2.setSupportType("antiaircraft"); weapon2.setEquipmentId("2"); weapon2.setName("Anti-aircraft-missile-weapon"); + weapon2.setComponents(new ArrayList<>()); // 添加到列表 weapons.add(weapon1);