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

1751 lines
64 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<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); // 联动触发门槛
// ---------- 任务自动命名模板(可改) ----------
// 任务分类优先级:导弹突击 > 防空压制 > 反装甲打击 > 炮火压制 > 通用打击
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
//-------------------------------------------------------------------------------
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"));
}
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);
}
}
}
}
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;
}
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;
}
//威胁等级添加武器函数
function void threatLevels(FactTask redFact, Map params) {
// 创建武器列表
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<>());
// 创建防空导弹武器
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);
}