package rules; import com.solution.rule.domain.ultimately.fact.DroolsFact; import java.util.Map; import static com.solution.rule.utils.RuleFunction.equipmentRule; import static com.solution.rule.utils.RuleFunction.target; import static com.solution.rule.utils.RuleFunction.position; import static com.solution.rule.utils.RuleFunction.trackRoute; import static com.solution.rule.utils.RuleFunction.groupFormation; global java.util.Map globalParams; /** * 构建装备匹配所需的全部可调参数(会 merge 进 globalParams,覆盖 Java 侧同名默认值)。 * * ========== 总体运算逻辑(与 RuleFunction.equipmentRule 一致)========== * 1)先拼「蓝方文本串」blueBlob:任务 drawName、dataType、taskWeapons 下各武器的 name/supportType/equipmentId、组件 deviceName。 * 2)对每个红方装备拼「红方文本串」redBlob:name、platform_type、SupportType。 * 3)每件红装得分 score = 规则槽得分(scoreRuleSlots) + 兼容层得分(scoreLegacyLayer),均为整数。 * - 规则槽:对 i=1..ruleSlotCount,若 blueBlob 命中 blueRuleKeywords_i 且 redBlob 命中 redRuleKeywords_i, * 则加上 ruleScore_i * weight。 * - 兼容层:多组「蓝关键词 + 红关键词 + 对应分数」,见下方各键说明;每组条件同时满足则加上 对应Score * weight。 * 4)在池中取 score 最大者;若多人并列,由 tieBreak 决定(见 tieBreak)。 * 5)若 maxScore < minSelectedScore,视为未匹配:不往 fireRuleInputs 追加行,redWeapons 输出仍为当前池。 * 6)若匹配成功:从池中 remove 该件;fireRuleInputs 追加一行,drawName 后接 outputDrawNameSuffix;taskWeapons 填选中红装映射。 * * 关键词格式:英文逗号分隔,子串包含即算命中(contains),不要求整词匹配。 */ function Map buildParam(){ Map param = new java.util.HashMap(); // ---------- 全局倍率与门槛 ---------- // weight:对上述所有「基础分数」的统一乘数(规则槽的 ruleScore_i、兼容层的 *Score 都会乘 weight)。 param.put("weight", 1); // minSelectedScore:单件红装总分达到该值及以上才会被选中并写入 fireRuleInputs;否则本任务视为未匹配到装备。 param.put("minSelectedScore", 1); // tieBreak:并列最高分时的决胜方式。当前实现仅支持 "equipmentId":装备 ID 字典序更小的优先。 param.put("tieBreak", "equipmentId"); // outputDrawNameSuffix:匹配成功写入 fireRuleInputs 时,在蓝方原 drawName 后面拼接的后缀(如「打击任务」)。 param.put("outputDrawNameSuffix", "打击任务"); // ---------- 规则槽(可配置条数,便于只改本文件而不改 Java)---------- // ruleSlotCount:启用几条槽规则;第 i 条使用 blueRuleKeywords_i、redRuleKeywords_i、ruleScore_i。 param.put("ruleSlotCount", 3); // 槽1:蓝方文本中出现任一子串 且 红方文本中出现任一子串 → 加 ruleScore_1 * weight。 param.put("blueRuleKeywords_1", "F-16,F-35"); param.put("redRuleKeywords_1", "防空,导弹,无人机"); param.put("ruleScore_1", 5); param.put("blueRuleKeywords_2", "坦克,装甲"); param.put("redRuleKeywords_2", "反坦克"); param.put("ruleScore_2", 4); param.put("blueRuleKeywords_3", "地面,突击"); param.put("redRuleKeywords_3", "远火,榴弹,炮"); param.put("ruleScore_3", 2); // ---------- 兼容层:按「场景关键词」配对加分(与 RuleFunction.scoreLegacyLayer 一一对应)---------- // 以下每组均为:蓝方文本命中第一列关键词 且 红方文本命中第二列关键词 → 加 第三列分数 * weight。 // // ① 空中平台类:蓝方像「对空/机型」且红方像对空装备 → + airScore * weight param.put("bluePlatformKeywords_air", "F-16,J-10,F-35"); param.put("redPreferredWhenBlueAir", "防空,导弹,无人机,直升机,空空"); param.put("airScore", 2); // ② 任务文案像空中任务 且 红方偏好对空 → + airTaskScore * weight(可与①叠加) param.put("airTaskKeywords", "空中,制空,拦截,空战"); param.put("airTaskScore", 10); // ③ 任务文案像地面任务 且 红方偏好地面火力 → + groundScore * weight param.put("groundTaskKeywords", "地面,突击,登陆"); param.put("redPreferredWhenGround", "远火,榴弹,炮,火箭"); param.put("groundScore", 1); // ④ 蓝方像坦克/装甲目标 且 红方文本命中 redMatchKeywords_tank → + tankScore * weight param.put("tankKeywords", "坦克,装甲"); param.put("redMatchKeywords_tank", "反坦克"); param.put("tankScore", 1); // ⑤ 蓝方像导弹类 且 红方文本命中 redMatchKeywords_missile → + missileScore * weight param.put("missileKeywords", "导弹,火箭弹,巡航"); param.put("redMatchKeywords_missile", "防空,导弹,导弹发射"); param.put("missileScore", 1); // ===================== 目标分配参数(写入 Tasks.task.execute) ===================== // executeTypeDefault:生成 execute[0] 的类型字段 取值:strike_test/assault param.put("executeTypeDefault", "assault"); // targetPickMode:roundRobin(稳定轮询) / random(伪随机但同输入稳定) param.put("targetPickMode", "roundRobin"); // minTargetsPerRed / maxTargetsPerRedCap:每个红方任务最少/最多分配的目标数 param.put("minTargetsPerRed", 1); param.put("maxTargetsPerRedCap", 3); // radToTargetsCsv:successTargetRad(命中率) -> 每红装目标数 的映射(阈值:目标数),按阈值从大到小匹配 // 例:0.8:1,0.5:2,0.2:3 表示 successTargetRad>=0.8 分1个;>=0.5 分2个;>=0.2 分3个 param.put("radToTargetsCsv", "0.8:1,0.5:2,0.2:3"); // rangeParseRegex:从 attDefaultValue/attExplain 中提取射程数值的正则(取第1个数字) param.put("rangeParseRegex", "(\\\\d+(?:\\\\.\\\\d+)?)"); // rangeUnit:提取数值的单位,km/m(二选一) param.put("rangeUnit", "km"); // minRangeToAllowAssignKm:若解析到的蓝方射程小于该值,则该蓝方装备不参与被分配(无法解析则忽略此过滤) param.put("minRangeToAllowAssignKm", 0); // ===================== 低命中率补拿装备参数 ===================== // redHitRateThreshold:红方装备命中率阈值(低于该值时触发补拿) param.put("redHitRateThreshold", 0.6); // maxExtraWeaponsPerTask:每条蓝方任务最多补拿几件红装 param.put("maxExtraWeaponsPerTask", 2); // maxSupplementRounds:补拿循环最大轮次(防死循环) param.put("maxSupplementRounds", 2); // extraPickMinScore:补拿时红装最低匹配分 param.put("extraPickMinScore", 1); // ===================== 阵位规则参数(写入 SubComponents.platform[].positions) ===================== // positionRuleEnabled:是否启用阵位规则。true=执行阵位生成;false=跳过,不改 platform.positions。 param.put("positionRuleEnabled", true); // positionAnchorMode:锚点模式。当前实现使用 hybrid(蓝方 taskWeapons.coordinate 的中心点作为主锚点)。 param.put("positionAnchorMode", "hybrid"); // trackPointDirectionMode:航向计算模式。 // - head2next:取 trackPoints[0] -> trackPoints[1] 作为方向(默认) // - tail2prev:取倒数第二个 -> 最后一个点作为方向 param.put("trackPointDirectionMode", "head2next"); // fallbackBearingDeg:当 trackPoints 缺失或无法计算方位时,使用该默认方位角(度,0-360)。 param.put("fallbackBearingDeg", 0); // deployDistanceKmMin:部署距离下限(km)。最终距离不会小于该值。 param.put("deployDistanceKmMin", 8); // deployDistanceKmMax:部署距离上限(km)。最终距离不会大于该值。 param.put("deployDistanceKmMax", 30); // deployDistanceKmDefault:默认部署距离(km)。 // 当 distanceByPlatformCsv 未命中任何关键词时,使用该值。 param.put("deployDistanceKmDefault", 15); // distanceByPlatformCsv:按“关键词”覆盖部署距离(km),不写死具体类型,完全由业务配置。 // 格式:关键词:距离,关键词:距离(示例:防空:18,反坦克:10,迫击炮:8) // 匹配范围:红方装备 Name / Platform_type 文本包含关键词即命中。 // 优先级:命中后覆盖 deployDistanceKmDefault;但最终仍受 deployDistanceKmMin 与 deployDistanceKmMax 约束。 param.put("distanceByPlatformCsv", ""); // formationType:编队样式,可选 line / wedge / circle。 param.put("formationType", "line"); // formationSpacingMeters:编队间距(米),影响同一红装下 platform[] 点位离散程度。 // 说明:Java 侧会与 minInterPlatformDistanceMeters 比较,取更大值,避免平台重叠过近。 param.put("formationSpacingMeters", 300); // formationHeadingOffsetDeg:编队相对主航向的偏转角(度),主要用于 wedge/circle 的分散方向。 param.put("formationHeadingOffsetDeg", 15); // defaultDeployHeight:默认部署高度(米),用于 positions 第3位高度值基线。 param.put("defaultDeployHeight", 30); // heightFollowBlueRatio:高度跟随蓝方比例(>=0)。 // 计算方式:高度 = defaultDeployHeight + 蓝方锚点平均高度 * heightFollowBlueRatio。 // 0 表示不跟随蓝方高度,仅使用默认高度。 param.put("heightFollowBlueRatio", 0.0); // enableWarZoneClamp:是否启用作战区约束。true=超出 warZoneLocation 时回拉到区内。 param.put("enableWarZoneClamp", true); // warZoneClampMode:作战区约束模式。当前实现使用 nearestInside(沿锚点到目标点方向二分回拉到区内)。 param.put("warZoneClampMode", "nearestInside"); // minInterPlatformDistanceMeters:平台最小间距(米)下限,用于抑制平台点位过度重叠。 param.put("minInterPlatformDistanceMeters", 80); // ===================== 航迹规则参数(写入 TrackParam 动态 key + execute.targetList.moveRouteId) ===================== // trackRuleEnabled:是否启用航迹生成与 moveRouteId 绑定。 param.put("trackRuleEnabled", true); // trackRouteAlgorithm:航迹变形算法。followBlue(默认) / shortestPath / flank / jam param.put("trackRouteAlgorithm", "followBlue"); // trackRouteNameSuffix:航迹名称 = 红方任务 drawName + 此后缀(默认「航迹」→ 如 xxx打击任务航迹) param.put("trackRouteNameSuffix", "航迹"); // trackAirDataTypeCsv:蓝方 dataType 命中任一子串(忽略大小写)则 TrackType=routeLineAir param.put("trackAirDataTypeCsv", "taskPlane,air,plane,flight"); // trackAirKeywordsCsv:蓝方 drawName 或红方 name/platformType 命中任一子串则视为飞行航迹 param.put("trackAirKeywordsCsv", "机,飞,空,J-,F-,无人机,直升机"); // trackGroundTrackType:非飞行类时 TrackType 取值(可先占位,后续再接地面路网) param.put("trackGroundTrackType", "routeLineGround"); // 航迹侧向算法复用上方「阵位规则」中的 trackPointDirectionMode(head2next / tail2prev);缺省回退见 trackFallbackBearingDeg param.put("trackFallbackBearingDeg", 0); // enableTrackWarZoneClamp:航迹点是否约束在 warZoneLocation 多边形内 param.put("enableTrackWarZoneClamp", true); // trackExtraNodesMax:在蓝方航迹点基础上最多额外插入的点数(0=与蓝方点数持平;>0 时在中段均匀插值) param.put("trackExtraNodesMax", 0); // shortestPath:相邻两点间直线插值分段数(>=1),越大折线越平滑(非真实路网最短路径) param.put("trackShortPathSegments", 3); // flank:侧向偏移距离(米);trackFlankSideMode:alternate / left / right param.put("trackFlankOffsetMeters", 800); param.put("trackFlankSideMode", "alternate"); // jam:正弦扰动振幅(米)、沿航迹起伏周期数(越大摆动越密) param.put("trackJamWobbleMeters", 400); param.put("trackJamSegments", 4); // ===================== 编组规则参数(写入 TrackParam.Groups + wingmanData) ===================== // groupRuleEnabled:是否生成编组节点。 param.put("groupRuleEnabled", true); // groupDrawNameSuffix:drawName = 领队装备 Name + 此后缀(默认「编组」) param.put("groupDrawNameSuffix", "编组"); // groupDrawNameWithIndex:是否在 drawName 末尾追加序号(避免多组同名),如 J15编组1 param.put("groupDrawNameWithIndex", false); // groupFormationMode:onePerRed(每件红装一组) / clusterByCount(按固定人数分组) / singleGroup(全部一组) param.put("groupFormationMode", "onePerRed"); // groupClusterSize:clusterByCount 模式下每组人数上限(>=1) param.put("groupClusterSize", 3); // groupLeaderPickMode:byHitRateThenId(命中率高优先,平手 equipmentId 小) / byId(equipmentId 字典序最小) param.put("groupLeaderPickMode", "byHitRateThenId"); // groupMinMembersForWingman:组内人数达到该值才生成 wingmanData(否则仅 leader 壳,僚机列表为空) param.put("groupMinMembersForWingman", 2); // wingman 几何参数(相对蓝方主航向 mainBearing 叠加) param.put("wingmanDistanceBaseMeters", 100); param.put("wingmanDistanceStepMeters", 50); param.put("wingmanAngleBaseDeg", 50); param.put("wingmanAngleStepDeg", 15); param.put("wingmanAltBaseMeters", 40); param.put("wingmanAltScale", 1.0); // ===== 以下由规则配置表自动同步生成(请勿手改 param.put 段) ===== param.put("groupRuleEnabled", true); param.put("groupDrawNameSuffix", "编组"); param.put("groupDrawNameWithIndex", false); param.put("groupFormationMode", "onePerRed"); param.put("groupClusterSize", 3); param.put("groupLeaderPickMode", "byHitRateThenId"); param.put("groupMinMembersForWingman", 2); param.put("wingmanDistanceBaseMeters", 100); param.put("wingmanDistanceStepMeters", 50); param.put("wingmanAngleBaseDeg", 50); param.put("wingmanAngleStepDeg", 15); param.put("wingmanAltBaseMeters", 40); param.put("wingmanAltScale", 1.0); param.put("enableTrackWarZoneClamp", true); param.put("trackRuleEnabled", true); param.put("trackRouteAlgorithm", "followBlue"); param.put("trackRouteNameSuffix", "航迹"); param.put("trackAirDataTypeCsv", "taskPlane,air,plane,flight"); param.put("trackAirKeywordsCsv", "机,飞,空,J-,F-,无人机,直升机"); param.put("trackGroundTrackType", "routeLineGround"); param.put("trackFallbackBearingDeg", 0); param.put("trackExtraNodesMax", 0); param.put("trackShortPathSegments", 3); param.put("trackFlankOffsetMeters", 800); param.put("trackFlankSideMode", "alternate"); param.put("trackJamWobbleMeters", 400); param.put("trackJamSegments", 4); param.put("enableWarZoneClamp", true); param.put("positionRuleEnabled", true); param.put("positionAnchorMode", "hybrid"); param.put("trackPointDirectionMode", "head2next"); param.put("fallbackBearingDeg", 0); param.put("deployDistanceKmMin", 8); param.put("deployDistanceKmMax", 30); param.put("deployDistanceKmDefault", 15); param.put("distanceByPlatformCsv", ""); param.put("formationType", "line"); param.put("formationSpacingMeters", 300); param.put("formationHeadingOffsetDeg", 15); param.put("defaultDeployHeight", 30); param.put("heightFollowBlueRatio", 0.0); param.put("warZoneClampMode", "nearestInside"); param.put("minInterPlatformDistanceMeters", 80); param.put("redHitRateThreshold", 0.6); param.put("maxExtraWeaponsPerTask", 2); param.put("maxSupplementRounds", 2); param.put("extraPickMinScore", 1); param.put("executeTypeDefault", "assault"); param.put("targetPickMode", "roundRobin"); param.put("minTargetsPerRed", 1); param.put("maxTargetsPerRedCap", 3); param.put("radToTargetsCsv", "0.8:1,0.5:2,0.2:3"); param.put("rangeParseRegex", "(\\\\d+(?:\\\\.\\\\d+)?)"); param.put("rangeUnit", "km"); param.put("minRangeToAllowAssignKm", 0); param.put("weight", 1); param.put("minSelectedScore", 1); param.put("tieBreak", "equipmentId"); param.put("outputDrawNameSuffix", "打击任务"); param.put("ruleSlotCount", 3); param.put("blueRuleKeywords_1", "F-16,F-35"); param.put("redRuleKeywords_1", "防空,导弹,无人机"); param.put("ruleScore_1", 5); param.put("blueRuleKeywords_2", "坦克,装甲"); param.put("redRuleKeywords_2", "反坦克"); param.put("ruleScore_2", 4); param.put("blueRuleKeywords_3", "地面,突击"); param.put("redRuleKeywords_3", "远火,榴弹,炮"); param.put("ruleScore_3", 2); param.put("bluePlatformKeywords_air", "F-16,J-10,F-35"); param.put("redPreferredWhenBlueAir", "防空,导弹,无人机,直升机,空空"); param.put("airScore", 2); param.put("airTaskKeywords", "空中,制空,拦截,空战"); param.put("airTaskScore", 10); param.put("groundTaskKeywords", "地面,突击,登陆"); param.put("redPreferredWhenGround", "远火,榴弹,炮,火箭"); param.put("groundScore", 1); param.put("tankKeywords", "坦克,装甲"); param.put("redMatchKeywords_tank", "反坦克"); param.put("tankScore", 1); param.put("missileKeywords", "导弹,火箭弹,巡航"); param.put("redMatchKeywords_missile", "防空,导弹,导弹发射"); param.put("missileScore", 1); return param; } function void mergeDefaultParams(Map current){ Map defaults = buildParam(); for (Object k : defaults.keySet()) { if (!current.containsKey(k)) { current.put(k, defaults.get(k)); } } } rule "装备匹配" salience 100 when $fact : DroolsFact(task != null) then // 以本文件 buildParam 为真源覆盖同名键,再执行 Java 侧匹配逻辑 // globalParams.putAll(buildParam()); mergeDefaultParams(globalParams); equipmentRule($fact, globalParams); end rule "目标匹配" salience 90 when $fact : DroolsFact(task != null) then // 显式目标分配规则:填充 Tasks.task.execute.targetList[*].targetId // globalParams.putAll(buildParam()); mergeDefaultParams(globalParams); target($fact, globalParams); end rule "阵位匹配" salience 80 when $fact : DroolsFact(task != null) then // 显式阵位规则:填充 redWeapons.SubComponents.platform[].positions // globalParams.putAll(buildParam()); mergeDefaultParams(globalParams); position($fact, globalParams); end rule "航迹匹配" salience 70 when $fact : DroolsFact(task != null) then // 显式航迹规则:填充 TrackParam 下各航迹 id,并绑定 execute[0].targetList[*].moveRouteId // globalParams.putAll(buildParam()); mergeDefaultParams(globalParams); trackRoute($fact, globalParams); end rule "编组匹配" salience 60 when $fact : DroolsFact(task != null) then // 显式编组规则:填充 TrackParam.Groups(groupType=addGroup)与 wingmanData // globalParams.putAll(buildParam()); mergeDefaultParams(globalParams); groupFormation($fact, globalParams); end