Files
auto-solution/auto-solution-rule/src/main/resources/rules/fire-rule.drl

1925 lines
70 KiB
Plaintext
Raw Normal View History

2026-03-27 10:27:04 +08:00
package rules;
2026-03-27 11:35:17 +08:00
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;
2026-03-27 10:27:04 +08:00
2026-03-27 11:35:17 +08:00
global java.util.Map globalParams;
//-------------------------------------------------------------------------------
2026-03-27 11:35:17 +08:00
rule "任务匹配1"
2026-03-27 10:27:04 +08:00
agenda-group "打击任务"
salience 100
2026-03-27 10:27:04 +08:00
when
2026-03-27 11:35:17 +08:00
then
// legacy 占位:旧的固定字符串匹配已停用,改由“任务自动匹配规则”统一处理。
2026-03-27 11:35:17 +08:00
end
//-------------------------------------------------------------------------------
rule "任务匹配2"
agenda-group "打击任务"
salience 100
2026-03-27 11:35:17 +08:00
when
then
// legacy 占位:旧的固定字符串匹配已停用,改由“任务自动匹配规则”统一处理。
2026-03-27 11:35:17 +08:00
end
//-------------------------------------------------------------------------------
rule "威胁等级规则"
agenda-group "打击任务"
salience 90
2026-03-27 11:35:17 +08:00
when
//如果蓝方威胁等级大于等于3则全局武器数量为3添加插入导弹发射车辆和防空导弹武器
$task : FactTask(blueTask.side == "蓝方",blueTask.threatLevel >= "3")
2026-03-27 11:35:17 +08:00
$redTask : FactTask(redTask.side == "红方")
then
//设置平台下组件的数量
globalParams.put("platNum",3);
//威胁等级大于等于3固定插入导弹发射车辆
// ========== 调用函数 ==========
threatLevels($redTask, globalParams);
2026-03-27 11:35:17 +08:00
end
//-------------------------------------------------------------------------------
rule "地面类型匹配规则"
agenda-group "打击任务"
salience 80
2026-03-27 11:35:17 +08:00
when
//如果蓝方武器为地面类型且高度不超过500米则使用插入空中力量打击
$task : FactTask(blueTask.side == "蓝方")
$weapons : List() from $task.blueTask.taskWeapons
$weapon : Weapon(supportType == "ground") from $weapons
2026-03-27 10:27:04 +08:00
then
2026-03-27 11:35:17 +08:00
Task redTask = $task.getRedTask();
List<Weapon> 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<Weapon> 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); // 联动触发门槛
2026-03-30 16:43:24 +08:00
// ---------- 全组件数量匹配(可改) ----------
// 逻辑:按红方武器 targetId 找到对应蓝方装备equipmentId然后覆盖所有“非导弹组件”的数量
cfg.put("enableComponentQuantityMatch", Boolean.TRUE);
// 格式示例:蓝穿甲弹->红穿甲弹,蓝火控雷达->红火控雷达;为空则默认 deviceName 一致匹配
cfg.put("componentDeviceNameMappingCsv", "");
// 跳过覆盖:蓝方组件 deviceName 包含该关键词则不覆盖(默认用来保留导弹联动偏移)
cfg.put("skipMissileComponentsByNameContains", "导弹");
// ---------- 任务自动命名模板(可改) ----------
// 任务分类优先级:导弹突击 > 防空压制 > 反装甲打击 > 炮火压制 > 通用打击
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 / defZoneLocation4点经纬度
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
2026-03-30 16:43:24 +08:00
//-------------------------------------------------------------------------------
// 全组件数量匹配:按 redWeapon.targetId 对应蓝方装备,覆盖“非导弹组件”的数量
rule "全组件数量匹配规则"
agenda-group "打击任务"
salience 53
when
$fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方")
then
Map cfg = buildBusinessConfig();
applyAllComponentQuantities($fact, cfg);
end
//-------------------------------------------------------------------------------
rule "任务自动匹配规则"
agenda-group "打击任务"
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<Weapon> blueWeapons = fact.getBlueTask().getTaskWeapons();
if (blueWeapons == null || blueWeapons.isEmpty()) {
return;
}
Task redTask = fact.getRedTask();
List<Weapon> 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"));
}
2026-03-27 11:35:17 +08:00
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<Weapon> blueWeapons = fact.getBlueTask().getTaskWeapons();
List<Weapon> 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);
}
}
}
}
2026-03-30 16:43:24 +08:00
//-------------------------------------------------------------------------------
// component 映射解析 + 全组件数量覆盖
function Map parseDeviceNameMapping(String csv) {
Map result = new java.util.HashMap();
if (csv == null) {
return result;
}
String text = csv.trim();
if (text.equals("")) {
return result;
}
String[] parts = text.split(",");
for (int i = 0; i < parts.length; i++) {
String one = parts[i];
if (one == null) {
continue;
}
String item = one.trim();
if (item.equals("")) {
continue;
}
int idx = item.indexOf("->");
if (idx <= 0 || idx >= item.length() - 2) {
continue;
}
String left = item.substring(0, idx).trim();
String right = item.substring(idx + 2).trim();
if (left.equals("") || right.equals("")) {
continue;
}
result.put(left, right);
}
return result;
}
function void applyAllComponentQuantities(FactTask fact, Map cfg) {
if (fact == null || fact.getBlueTask() == null || fact.getRedTask() == null) {
return;
}
if (!readBooleanCfg(cfg, "enableComponentQuantityMatch", true)) {
return;
}
Task blueTask = fact.getBlueTask();
Task redTask = fact.getRedTask();
List blueWeapons = blueTask.getTaskWeapons();
List redWeapons = redTask.getTaskWeapons();
if (blueWeapons == null || blueWeapons.isEmpty() || redWeapons == null || redWeapons.isEmpty()) {
return;
}
String mappingCsv = cfg == null ? null : (String) cfg.get("componentDeviceNameMappingCsv");
Map deviceNameMapping = parseDeviceNameMapping(mappingCsv);
String skipKeyword = cfg == null ? null : (String) cfg.get("skipMissileComponentsByNameContains");
if (skipKeyword != null) {
skipKeyword = skipKeyword.trim();
if (skipKeyword.equals("")) {
skipKeyword = null;
}
}
// 遍历红方每个武器:用 targetId 找蓝方装备equipmentId
for (Object objR : redWeapons) {
Weapon redWeapon = (Weapon) objR;
if (redWeapon == null) {
continue;
}
String targetId = redWeapon.getTargetId();
if (isBlank(targetId)) {
// 允许不匹配targetId 为空直接跳过
continue;
}
Weapon blueWeapon = null;
for (Object objB : blueWeapons) {
Weapon w = (Weapon) objB;
if (w == null) {
continue;
}
String eqId = w.getEquipmentId();
if (eqId != null && eqId.equals(targetId)) {
blueWeapon = w;
break;
}
}
if (blueWeapon == null) {
continue;
}
List blueComps = blueWeapon.getComponents();
List redComps = redWeapon.getComponents();
if (blueComps == null || blueComps.isEmpty() || redComps == null || redComps.isEmpty()) {
continue;
}
// 用蓝方组件驱动覆盖红方组件数量
for (Object objBC : blueComps) {
SubComponents blueComp = (SubComponents) objBC;
if (blueComp == null) {
continue;
}
String blueCompName = blueComp.getDeviceName();
if (isBlank(blueCompName)) {
continue;
}
// 跳过导弹组件覆盖,避免覆盖导弹联动偏移逻辑
if (skipKeyword != null && blueCompName.contains(skipKeyword)) {
continue;
}
String redCompName = (String) deviceNameMapping.get(blueCompName);
if (redCompName == null || redCompName.trim().equals("")) {
redCompName = blueCompName;
}
SubComponents redComp = null;
for (Object objRC : redComps) {
SubComponents rc = (SubComponents) objRC;
if (rc == null) {
continue;
}
if (redCompName.equals(rc.getDeviceName())) {
redComp = rc;
break;
}
}
if (redComp == null) {
// 允许不匹配:红方下没有该组件则跳过
continue;
}
List blueParams = blueComp.getComponentParams();
List redParams = redComp.getComponentParams();
if (blueParams == null || blueParams.isEmpty() || redParams == null || redParams.isEmpty()) {
continue;
}
ComponentParam blueFirst = (ComponentParam) blueParams.get(0);
ComponentParam redFirst = (ComponentParam) redParams.get(0);
if (blueFirst == null || redFirst == null) {
continue;
}
if (blueFirst.getNumber() == null) {
continue;
}
// 只覆盖数量componentParams[0].number
redFirst.setNumber(blueFirst.getNumber());
}
}
}
function void prepareDeploymentPools(FactTask fact, Map cfg, Map runtime) {
if (!readBooleanCfg(cfg, "enablePositionRules", true)) {
return;
}
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<Weapon> 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<SubComponents> 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<SubComponents> 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<SubComponents> 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<SubComponents> 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<ComponentParam> params = new ArrayList<>();
ComponentParam param = new ComponentParam();
param.setAttDefaultValue(value);
param.setAttExplain(unit);
param.setNumber(number);
params.add(param);
component.setComponentParams(params);
return component;
}
2026-03-27 11:35:17 +08:00
function void ensureOrUpdateParam(SubComponents component, String value, String unit, int number) {
List<ComponentParam> 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<Weapon> blueWeapons = blueTask.getTaskWeapons();
List<Weapon> redWeapons = redTask.getTaskWeapons();
if (blueWeapons == null || redWeapons == null || redWeapons.isEmpty()) {
return;
}
int blueLauncherCount = countLauncherComponents(blueWeapons, launcherName);
if (blueLauncherCount <= 0) {
return;
}
// 规则1红方若存在 plane 或导弹发射车,则这些武器都需要具备发射架
List<Weapon> 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<SubComponents> 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<SubComponents> 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;
}
2026-03-27 11:35:17 +08:00
//威胁等级添加武器函数
function void threatLevels(FactTask redFact, Map params) {
2026-03-27 11:35:17 +08:00
// 创建武器列表
List<Weapon> 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<>());
2026-03-27 11:35:17 +08:00
// 创建防空导弹武器
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<>());
2026-03-27 11:35:17 +08:00
// 添加到列表
weapons.add(weapon1);
weapons.add(weapon2);
// 设置到红方任务
redFact.getRedTask().setTaskWeapons(weapons);
}