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 com.solution.rule.domain.simplerulepojo.SubComponents; import com.solution.rule.domain.simplerulepojo.ComponentParam; import com.solution.rule.domain.simplerulepojo.Coordinate; import com.solution.rule.domain.simplerulepojo.TrackPoints; import java.util.List; import java.util.Map; import java.util.ArrayList; global java.util.Map globalParams; //------------------------------------------------------------------------------- rule "任务匹配1" agenda-group "打击任务" salience 100 when then // legacy 占位:旧的固定字符串匹配已停用,改由“任务自动匹配规则”统一处理。 end //------------------------------------------------------------------------------- rule "任务匹配2" agenda-group "打击任务" salience 100 when then // legacy 占位:旧的固定字符串匹配已停用,改由“任务自动匹配规则”统一处理。 end //------------------------------------------------------------------------------- rule "威胁等级规则" agenda-group "打击任务" salience 90 when //如果蓝方威胁等级大于等于3,则全局武器数量为3,添加插入导弹发射车辆和防空导弹武器 $task : FactTask(blueTask.side == "蓝方",blueTask.threatLevel >= "3") $redTask : FactTask(redTask.side == "红方") then //设置平台下组件的数量 globalParams.put("platNum",3); //威胁等级大于等于3固定插入导弹发射车辆 // ========== 调用函数 ========== threatLevels($redTask, globalParams); end //------------------------------------------------------------------------------- rule "地面类型匹配规则" agenda-group "打击任务" salience 80 when //如果蓝方武器为地面类型且高度不超过500米,则使用插入空中力量打击 $task : FactTask(blueTask.side == "蓝方") $weapons : List() from $task.blueTask.taskWeapons $weapon : Weapon(supportType == "ground") 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 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"); // ---------- targetId 自动绑定(可改) ---------- cfg.put("enableTargetAutoBind", Boolean.TRUE); // 是否自动给红方武器绑定蓝方目标 cfg.put("minTargetBindRatio", "0.7"); // 最低绑定比例(大部分有目标) cfg.put("allowReserveWithoutTarget", Boolean.TRUE); // 允许少量武器 targetId 为空(火力冗余) // ---------- 阵位规则参数(可改) ---------- cfg.put("enablePositionRules", Boolean.TRUE); // 阵位规则总开关 // 区域来源已切换到 Task 实体字段:warZoneLocation / defZoneLocation(4点经纬度) cfg.put("fireUnitSpacingMeters", 100); // 火力单元间距(米) cfg.put("airDeployZonePreference", "combat"); // 飞机优先部署区:combat/defense cfg.put("defensePriorityWeapons", "反坦克导弹系统,反坦克火箭,车载迫击炮,迫榴炮"); // 优先部署防区武器 cfg.put("groundDeployHeight", 20); // 地面武器部署高度 cfg.put("airDeployHeight", 300); // 空中武器部署高度 // ---------- 航迹规则参数(可改) ---------- cfg.put("enableTrajectoryRules", Boolean.TRUE); // 航迹规则总开关 cfg.put("strategyMode", "auto"); // auto/shortest/flank/interfere cfg.put("enableShortest", Boolean.TRUE); cfg.put("enableFlank", Boolean.TRUE); cfg.put("enableInterfere", Boolean.TRUE); cfg.put("nearDefDistanceMeters", 800); // 近防区阈值 cfg.put("farDefDistanceMeters", 2500); // 远防区阈值 cfg.put("fastSpeedThreshold", 180); // 快速阈值 cfg.put("flankOffsetMeters", 150); // 绕后偏移 cfg.put("interfereOffsetMeters", 120); // 干扰基础偏移 cfg.put("interfereZigzagAmplitude", 90); // 干扰锯齿幅度 cfg.put("keepBlueHeight", Boolean.TRUE); // true=沿用蓝方高度 cfg.put("redTrackHeightOverride", 200); // keepBlueHeight=false 时生效 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 //------------------------------------------------------------------------------- rule "阵位规则-区域解析与点位生成" agenda-group "打击任务" salience 49 when $fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方") then Map cfg = buildBusinessConfig(); prepareDeploymentPools($fact, cfg, globalParams); end //------------------------------------------------------------------------------- rule "阵位规则-武器部署赋位" agenda-group "打击任务" salience 48 when $fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方") then Map cfg = buildBusinessConfig(); applyWeaponDeployment($fact, cfg, globalParams); end //------------------------------------------------------------------------------- rule "航迹规则-生成红方航迹" agenda-group "打击任务" salience 47 when // 根据蓝方 trackPoints 生成红方 trackPoints,点数保持一致 $fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方") then Map cfg = buildBusinessConfig(); applyTrajectoryGeneration($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")); } // 自动绑定红方武器 targetId(来源:蓝方 equipmentId) bindTargetIdsForRedWeapons(redWeapons, blueWeapons, cfg); } 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 List parseCsvList(String text) { List result = new ArrayList(); 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 item = one.trim(); if (!item.equals("") && !result.contains(item)) { result.add(item); } } 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 bindTargetIdsForRedWeapons(List redWeapons, List blueWeapons, Map cfg) { if (!readBooleanCfg(cfg, "enableTargetAutoBind", true)) { return; } if (redWeapons == null || redWeapons.isEmpty() || blueWeapons == null || blueWeapons.isEmpty()) { return; } Map pools = extractBlueTargetPools(blueWeapons); Map cursor = new java.util.HashMap(); int total = redWeapons.size(); int bound = 0; // 第一轮:按武器类别优先匹配 for (Object obj : redWeapons) { Weapon redWeapon = (Weapon) obj; if (redWeapon == null) { continue; } if (!isBlank(redWeapon.getTargetId())) { bound++; continue; } String poolKey = inferBluePoolKeyForRedWeapon(redWeapon); String targetId = pickTargetIdFromPools(pools, cursor, poolKey); if (!isBlank(targetId)) { redWeapon.setTargetId(targetId); bound++; } } 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; } // 第二轮:若绑定率不足,回退到全目标池尽量补齐(仍允许复用目标) 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 = pickTargetIdFromPools(pools, cursor, "all"); if (!isBlank(targetId)) { redWeapon.setTargetId(targetId); bound++; } } // 第三轮:若不允许空 targetId,最后强制从 all 池补齐(尽力而为) if (!allowReserveWithoutTarget) { for (Object obj : redWeapons) { Weapon redWeapon = (Weapon) obj; if (redWeapon == null || !isBlank(redWeapon.getTargetId())) { continue; } String targetId = pickTargetIdFromPools(pools, cursor, "all"); if (!isBlank(targetId)) { redWeapon.setTargetId(targetId); } } } } function void prepareDeploymentPools(FactTask fact, Map cfg, Map runtime) { if (!readBooleanCfg(cfg, "enablePositionRules", true)) { return; } if (fact == null || runtime == null) { return; } List combatPolygon = extractZonePolygonFromTask(fact, true); List defensePolygon = extractZonePolygonFromTask(fact, false); int spacing = readIntCfg(cfg, "fireUnitSpacingMeters", 100); List combatPoints = buildGridPointsInPolygon(combatPolygon, spacing, readIntCfg(cfg, "groundDeployHeight", 20)); List defensePoints = buildGridPointsInPolygon(defensePolygon, spacing, readIntCfg(cfg, "groundDeployHeight", 20)); runtime.put("deploymentCombatPoints", combatPoints); runtime.put("deploymentDefensePoints", defensePoints); } function void applyWeaponDeployment(FactTask fact, Map cfg, Map runtime) { if (!readBooleanCfg(cfg, "enablePositionRules", true)) { return; } if (fact == null || fact.getRedTask() == null || fact.getRedTask().getTaskWeapons() == null || runtime == null) { return; } List combatPoints = (List) runtime.get("deploymentCombatPoints"); List defensePoints = (List) runtime.get("deploymentDefensePoints"); if ((combatPoints == null || combatPoints.isEmpty()) && (defensePoints == null || defensePoints.isEmpty())) { return; } Map cursor = new java.util.HashMap(); List defensePriorityWeapons = parseCsvList((String) cfg.get("defensePriorityWeapons")); String airPref = String.valueOf(cfg.get("airDeployZonePreference")); int groundH = readIntCfg(cfg, "groundDeployHeight", 20); int airH = readIntCfg(cfg, "airDeployHeight", 300); for (Object obj : fact.getRedTask().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); } } else { selected = pickCoordinateByPreference(combatPoints, defensePoints, cursor, "combat"); if (selected != null) { selected = cloneCoordinateWithHeight(selected, groundH); } } if (selected != null) { redWeapon.setCoordinate(selected); } } } function void applyTrajectoryGeneration(FactTask fact, Map cfg) { if (!readBooleanCfg(cfg, "enableTrajectoryRules", true)) { return; } if (fact == null || fact.getBlueTask() == null || fact.getRedTask() == null) { return; } Task blueTask = fact.getBlueTask(); Task redTask = fact.getRedTask(); List blueTrack = blueTask.getTrackPoints(); if (blueTrack == null || blueTrack.isEmpty()) { return; } List defZone = blueTask.getDefZoneLocation(); if (defZone == null || defZone.size() < 3) { return; } String strategy = chooseTrajectoryStrategy(blueTask, cfg); Coordinate endPoint = findNearestDefPointToBlueTail(blueTask); List redTrack = generateRedTrackPoints(blueTrack, strategy, cfg, endPoint); if (redTrack != null && !redTrack.isEmpty()) { redTask.setTrackPoints(redTrack); } } function String chooseTrajectoryStrategy(Task blueTask, Map cfg) { String mode = String.valueOf(cfg.get("strategyMode")); if (mode == null || mode.trim().equals("")) { mode = "auto"; } mode = mode.trim().toLowerCase(); if (!mode.equals("auto")) { return fallbackToEnabledStrategy(mode, cfg); } double defMinDistance = computeDefMinDistanceMeters(blueTask); int avgSpeed = computeAverageSpeed(blueTask.getTrackPoints()); int near = readIntCfg(cfg, "nearDefDistanceMeters", 800); int far = readIntCfg(cfg, "farDefDistanceMeters", 2500); int fast = readIntCfg(cfg, "fastSpeedThreshold", 180); String selected = "flank"; if (avgSpeed >= fast && defMinDistance <= near) { selected = "shortest"; } else if (avgSpeed >= fast && defMinDistance >= far) { selected = "interfere"; } return fallbackToEnabledStrategy(selected, cfg); } function String fallbackToEnabledStrategy(String preferred, Map cfg) { if (isStrategyEnabled(preferred, cfg)) { return preferred; } if (isStrategyEnabled("shortest", cfg)) { return "shortest"; } if (isStrategyEnabled("flank", cfg)) { return "flank"; } if (isStrategyEnabled("interfere", cfg)) { return "interfere"; } return "shortest"; } function boolean isStrategyEnabled(String strategy, Map cfg) { if (strategy == null) { return false; } if (strategy.equals("shortest")) { return readBooleanCfg(cfg, "enableShortest", true); } if (strategy.equals("flank")) { return readBooleanCfg(cfg, "enableFlank", true); } if (strategy.equals("interfere")) { return readBooleanCfg(cfg, "enableInterfere", true); } return false; } function Coordinate findNearestDefPointToBlueTail(Task blueTask) { if (blueTask == null || blueTask.getTrackPoints() == null || blueTask.getTrackPoints().isEmpty() || blueTask.getDefZoneLocation() == null || blueTask.getDefZoneLocation().isEmpty()) { return null; } TrackPoints tail = (TrackPoints) blueTask.getTrackPoints().get(blueTask.getTrackPoints().size() - 1); if (tail == null || tail.getLongitude() == null || tail.getLatitude() == null) { return null; } Coordinate nearest = null; 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(tail.getLongitude().doubleValue(), tail.getLatitude().doubleValue(), c.getLongitude().doubleValue(), c.getLatitude().doubleValue()); if (d < best) { best = d; nearest = c; } } return nearest; } function double computeDefMinDistanceMeters(Task blueTask) { Coordinate nearest = findNearestDefPointToBlueTail(blueTask); if (nearest == null || blueTask == null || blueTask.getTrackPoints() == null || blueTask.getTrackPoints().isEmpty()) { return Double.MAX_VALUE; } TrackPoints tail = (TrackPoints) blueTask.getTrackPoints().get(blueTask.getTrackPoints().size() - 1); return approxDistanceMeters( tail.getLongitude().doubleValue(), tail.getLatitude().doubleValue(), nearest.getLongitude().doubleValue(), nearest.getLatitude().doubleValue() ); } function int computeAverageSpeed(List trackPoints) { if (trackPoints == null || trackPoints.isEmpty()) { return 0; } int total = 0; int count = 0; for (Object obj : trackPoints) { TrackPoints p = (TrackPoints) obj; if (p == null || p.getSpeed() == null) { continue; } total += p.getSpeed(); count++; } if (count <= 0) { return 0; } return total / count; } function List generateRedTrackPoints(List blueTrackPoints, String strategy, Map cfg, Coordinate endPoint) { List result = new ArrayList(); if (blueTrackPoints == null || blueTrackPoints.isEmpty()) { return result; } TrackPoints start = (TrackPoints) blueTrackPoints.get(0); TrackPoints tail = (TrackPoints) blueTrackPoints.get(blueTrackPoints.size() - 1); if (start == null || start.getLongitude() == null || start.getLatitude() == null) { return result; } double sLon = start.getLongitude().doubleValue(); double sLat = start.getLatitude().doubleValue(); double eLon = (endPoint != null && endPoint.getLongitude() != null) ? endPoint.getLongitude().doubleValue() : (tail == null || tail.getLongitude() == null ? sLon : tail.getLongitude().doubleValue()); double eLat = (endPoint != null && endPoint.getLatitude() != null) ? endPoint.getLatitude().doubleValue() : (tail == null || tail.getLatitude() == null ? sLat : tail.getLatitude().doubleValue()); double dx = eLon - sLon; double dy = eLat - sLat; int n = blueTrackPoints.size(); int flankOffset = readIntCfg(cfg, "flankOffsetMeters", 150); int intBase = readIntCfg(cfg, "interfereOffsetMeters", 120); int intAmp = readIntCfg(cfg, "interfereZigzagAmplitude", 90); boolean keepBlueHeight = readBooleanCfg(cfg, "keepBlueHeight", true); int redH = readIntCfg(cfg, "redTrackHeightOverride", 200); for (int i = 0; i < n; i++) { double t = (n <= 1) ? 1.0d : ((double) i / (double) (n - 1)); double baseLon = sLon + dx * t; double baseLat = sLat + dy * t; double offMeters = 0.0d; if ("flank".equals(strategy)) { offMeters = flankOffset * Math.sin(Math.PI * t); } else if ("interfere".equals(strategy)) { double zig = (i % 2 == 0 ? 1.0d : -1.0d) * intAmp; offMeters = intBase * Math.sin(2.0d * Math.PI * t) + zig; } double latDeg = metersToLatDeg(offMeters); double lonDeg = metersToLonDeg(offMeters, baseLat); double norm = Math.sqrt(dx * dx + dy * dy); if (norm < 1e-10) { norm = 1e-10; } double nx = -dy / norm; double ny = dx / norm; double finalLon = baseLon + nx * lonDeg; double finalLat = baseLat + ny * latDeg; TrackPoints blueP = (TrackPoints) blueTrackPoints.get(i); TrackPoints redP = new TrackPoints(); redP.setIndex(i); redP.setLongitude(new java.math.BigDecimal(String.valueOf(finalLon))); redP.setLatitude(new java.math.BigDecimal(String.valueOf(finalLat))); redP.setSpeed(blueP == null || blueP.getSpeed() == null ? 0 : blueP.getSpeed()); if (keepBlueHeight) { redP.setHeight(blueP == null || blueP.getHeight() == null ? redH : blueP.getHeight()); } else { redP.setHeight(redH); } result.add(redP); } return result; } function double metersToLatDeg(double meters) { return meters / 111000.0d; } function double metersToLonDeg(double meters, double latitudeDeg) { double cos = Math.cos(Math.toRadians(latitudeDeg)); if (Math.abs(cos) < 1e-6) { cos = 1e-6; } return meters / (111000.0d * cos); } function double approxDistanceMeters(double lon1, double lat1, double lon2, double lat2) { double dx = (lon2 - lon1) * 111000.0d * Math.cos(Math.toRadians((lat1 + lat2) / 2.0d)); double dy = (lat2 - lat1) * 111000.0d; return Math.sqrt(dx * dx + dy * dy); } function List extractZonePolygonFromTask(FactTask fact, boolean isCombat) { // 输入约定:Task.warZoneLocation / defZoneLocation 传入 4 个经纬点(高度可空) List result = new ArrayList(); Task blueTask = fact == null ? null : fact.getBlueTask(); if (blueTask == null) { return result; } List source = isCombat ? blueTask.getWarZoneLocation() : blueTask.getDefZoneLocation(); if (source == null || source.isEmpty()) { return result; } for (Object oneObj : source) { Coordinate one = (Coordinate) oneObj; if (one == null || one.getLongitude() == null || one.getLatitude() == null) { continue; } Coordinate c = new Coordinate(); c.setLongitude(one.getLongitude()); c.setLatitude(one.getLatitude()); c.setHeight(one.getHeight()); result.add(c); } return result; } function List normalizeToCoordinateList(Object raw) { List result = new ArrayList(); if (raw == null) { return result; } if (!(raw instanceof List)) { return result; } List values = (List) raw; for (Object obj : values) { Coordinate c = toCoordinate(obj); if (c != null) { result.add(c); } } return result; } function Coordinate toCoordinate(Object obj) { if (obj == null) { return null; } if (obj instanceof Coordinate) { return (Coordinate) obj; } if (obj instanceof Map) { Map m = (Map) obj; Object lon = m.get("longitude"); Object lat = m.get("latitude"); Object h = m.get("height"); if (lon == null || lat == null) { return null; } Coordinate c = new Coordinate(); try { c.setLongitude(new java.math.BigDecimal(String.valueOf(lon))); c.setLatitude(new java.math.BigDecimal(String.valueOf(lat))); c.setHeight(h == null ? 0 : parseIntSafe(String.valueOf(h), 0)); return c; } catch (Exception ex) { return null; } } return null; } function List buildGridPointsInPolygon(List polygon, int spacingMeters, int defaultHeight) { List points = new ArrayList(); if (polygon == null || polygon.size() < 3) { return points; } double step = ((double) spacingMeters) / 111000.0d; if (step <= 0) { step = 0.0009d; } double minLon = 180.0d; double maxLon = -180.0d; double minLat = 90.0d; double maxLat = -90.0d; for (Object cObj : polygon) { Coordinate c = (Coordinate) cObj; if (c == null || c.getLongitude() == null || c.getLatitude() == null) { continue; } double lon = c.getLongitude().doubleValue(); double lat = c.getLatitude().doubleValue(); if (lon < minLon) minLon = lon; if (lon > maxLon) maxLon = lon; if (lat < minLat) minLat = lat; if (lat > maxLat) maxLat = lat; } for (double lon = minLon; lon <= maxLon; lon += step) { for (double lat = minLat; lat <= maxLat; lat += step) { if (isPointInsidePolygon(lon, lat, polygon)) { Coordinate c = new Coordinate(); c.setLongitude(new java.math.BigDecimal(String.valueOf(lon))); c.setLatitude(new java.math.BigDecimal(String.valueOf(lat))); c.setHeight(defaultHeight); points.add(c); } } } return points; } function boolean isPointInsidePolygon(double x, double y, List polygon) { if (polygon == null || polygon.size() < 3) { return false; } boolean inside = false; int n = polygon.size(); int j = n - 1; for (int i = 0; i < n; i++) { Coordinate pi = (Coordinate) polygon.get(i); Coordinate pj = (Coordinate) polygon.get(j); if (pi == null || pj == null || pi.getLongitude() == null || pi.getLatitude() == null || pj.getLongitude() == null || pj.getLatitude() == null) { j = i; continue; } double xi = pi.getLongitude().doubleValue(); double yi = pi.getLatitude().doubleValue(); double xj = pj.getLongitude().doubleValue(); double yj = pj.getLatitude().doubleValue(); boolean intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / ((yj - yi) == 0 ? 1e-12 : (yj - yi)) + xi); if (intersect) { inside = !inside; } j = i; } return inside; } function Coordinate pickCoordinateByPreference(List first, List second, Map cursor, String key) { Coordinate c1 = pickCoordinateRoundRobin(first, cursor, "first_" + key); if (c1 != null) { return c1; } return pickCoordinateRoundRobin(second, cursor, "second_" + key); } function Coordinate pickCoordinateRoundRobin(List values, Map cursor, String key) { if (values == null || values.isEmpty()) { return null; } Integer idxObj = (Integer) cursor.get(key); int idx = idxObj == null ? 0 : idxObj.intValue(); Coordinate value = (Coordinate) values.get(idx % values.size()); cursor.put(key, idx + 1); return value; } function Coordinate cloneCoordinateWithHeight(Coordinate source, int height) { if (source == null) { return null; } Coordinate c = new Coordinate(); c.setLongitude(source.getLongitude()); c.setLatitude(source.getLatitude()); c.setHeight(height); return c; } function Map extractBlueTargetPools(List blueWeapons) { Map pools = new java.util.HashMap(); pools.put("air", new ArrayList()); pools.put("armor", new ArrayList()); pools.put("artillery", new ArrayList()); pools.put("ground", new ArrayList()); pools.put("missile", new ArrayList()); pools.put("all", new ArrayList()); for (Object obj : blueWeapons) { Weapon blueWeapon = (Weapon) obj; if (blueWeapon == null) { continue; } String id = blueWeapon.getEquipmentId(); if (isBlank(id)) { continue; } addUnique((List) pools.get("all"), id); if (isAirWeapon(blueWeapon)) { addUnique((List) pools.get("air"), id); } if (isArmorWeapon(blueWeapon)) { addUnique((List) pools.get("armor"), id); } if (isArtilleryWeapon(blueWeapon)) { addUnique((List) pools.get("artillery"), id); } if (isGroundWeapon(blueWeapon)) { addUnique((List) pools.get("ground"), id); } if (hasMissileComponent(blueWeapon)) { addUnique((List) pools.get("missile"), id); } } return pools; } function String inferBluePoolKeyForRedWeapon(Weapon redWeapon) { if (redWeapon == null || redWeapon.getName() == null) { return "ground"; } String name = redWeapon.getName(); if (name.contains("反坦克")) { return "armor"; } if (name.contains("防空导弹") || name.contains("无人机") || name.contains("直升机")) { return "air"; } if (name.contains("迫榴炮") || name.contains("迫击炮")) { return "artillery"; } if (name.contains("导弹发射车")) { return "missile"; } return "ground"; } function String pickTargetIdFromPools(Map pools, Map cursor, String preferredKey) { String fromPreferred = pickFromSinglePool(pools, cursor, preferredKey); if (!isBlank(fromPreferred)) { return fromPreferred; } if (!"ground".equals(preferredKey)) { String fromGround = pickFromSinglePool(pools, cursor, "ground"); if (!isBlank(fromGround)) { return fromGround; } } return pickFromSinglePool(pools, cursor, "all"); } function String pickFromSinglePool(Map pools, Map cursor, String poolKey) { if (pools == null || cursor == null || poolKey == null) { return null; } List ids = (List) pools.get(poolKey); if (ids == null || ids.isEmpty()) { return null; } Integer idxObj = (Integer) cursor.get(poolKey); int idx = idxObj == null ? 0 : idxObj.intValue(); String id = (String) ids.get(idx % ids.size()); cursor.put(poolKey, idx + 1); return id; } function void addUnique(List values, String value) { if (values == null || isBlank(value)) { return; } if (!containsString(values, value)) { values.add(value); } } function boolean isBlank(String text) { return text == null || text.trim().equals(""); } 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 double readDoubleCfg(Map cfg, String key, double fallback) { if (cfg == null || key == null) { return fallback; } Object value = cfg.get(key); if (value == null) { return fallback; } try { return Double.parseDouble(String.valueOf(value).trim()); } catch (Exception ex) { return fallback; } } 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 threatLevels(FactTask redFact, Map params) { // 创建武器列表 List weapons = new ArrayList<>(); // 创建导弹发射车 Weapon weapon1 = new Weapon(); weapon1.setNumber((Integer) params.get("platNum")); weapon1.setSupportType("ground"); weapon1.setEquipmentId("1"); weapon1.setName("missile-launching-vehicle"); weapon1.setComponents(new ArrayList<>()); // 创建防空导弹武器 Weapon weapon2 = new Weapon(); weapon2.setNumber((Integer) params.get("platNum")); weapon2.setSupportType("antiaircraft"); weapon2.setEquipmentId("2"); weapon2.setName("Anti-aircraft-missile-weapon"); weapon2.setComponents(new ArrayList<>()); // 添加到列表 weapons.add(weapon1); weapons.add(weapon2); // 设置到红方任务 redFact.getRedTask().setTaskWeapons(weapons); }