1751 lines
64 KiB
Plaintext
1751 lines
64 KiB
Plaintext
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 / defZoneLocation(4点经纬度)
|
||
cfg.put("fireUnitSpacingMeters", 100); // 火力单元间距(米)
|
||
cfg.put("airDeployZonePreference", "combat"); // 飞机优先部署区:combat/defense
|
||
cfg.put("defensePriorityWeapons", "反坦克导弹系统,反坦克火箭,车载迫击炮,迫榴炮"); // 优先部署防区武器
|
||
cfg.put("groundDeployHeight", 20); // 地面武器部署高度
|
||
cfg.put("airDeployHeight", 300); // 空中武器部署高度
|
||
|
||
// ---------- 航迹规则参数(可改) ----------
|
||
cfg.put("enableTrajectoryRules", Boolean.TRUE); // 航迹规则总开关
|
||
cfg.put("strategyMode", "auto"); // auto/shortest/flank/interfere
|
||
cfg.put("enableShortest", Boolean.TRUE);
|
||
cfg.put("enableFlank", Boolean.TRUE);
|
||
cfg.put("enableInterfere", Boolean.TRUE);
|
||
cfg.put("nearDefDistanceMeters", 800); // 近防区阈值
|
||
cfg.put("farDefDistanceMeters", 2500); // 远防区阈值
|
||
cfg.put("fastSpeedThreshold", 180); // 快速阈值
|
||
cfg.put("flankOffsetMeters", 150); // 绕后偏移
|
||
cfg.put("interfereOffsetMeters", 120); // 干扰基础偏移
|
||
cfg.put("interfereZigzagAmplitude", 90); // 干扰锯齿幅度
|
||
cfg.put("keepBlueHeight", Boolean.TRUE); // true=沿用蓝方高度
|
||
cfg.put("redTrackHeightOverride", 200); // keepBlueHeight=false 时生效
|
||
|
||
return cfg;
|
||
}
|
||
|
||
//-------------------------------------------------------------------------------
|
||
rule "红方武器自适应装配规则"
|
||
agenda-group "打击任务"
|
||
salience 55
|
||
when
|
||
// 蓝方与红方任务都存在时触发,做“武器+组件”的基础装配
|
||
$fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方")
|
||
then
|
||
Map cfg = buildBusinessConfig();
|
||
configureRedWeaponsByBlue($fact, cfg);
|
||
end
|
||
|
||
//-------------------------------------------------------------------------------
|
||
rule "导弹联动增强规则"
|
||
agenda-group "打击任务"
|
||
salience 54
|
||
when
|
||
// 在基础装配后执行:若蓝方挂载导弹,红方空中武器自动增强导弹能力
|
||
$fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方")
|
||
then
|
||
Map cfg = buildBusinessConfig();
|
||
applyMissileLinkage($fact, cfg);
|
||
end
|
||
|
||
//-------------------------------------------------------------------------------
|
||
rule "任务自动匹配规则"
|
||
agenda-group "打击任务"
|
||
salience 50
|
||
when
|
||
// 以红方最终武器为主自动生成任务名,保证任务名与武器一致
|
||
$fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方")
|
||
then
|
||
Map cfg = buildBusinessConfig();
|
||
assignTaskNameByRedWeapons($fact, cfg);
|
||
end
|
||
|
||
//-------------------------------------------------------------------------------
|
||
rule "阵位规则-区域解析与点位生成"
|
||
agenda-group "打击任务"
|
||
salience 49
|
||
when
|
||
$fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方")
|
||
then
|
||
Map cfg = buildBusinessConfig();
|
||
prepareDeploymentPools($fact, cfg, globalParams);
|
||
end
|
||
|
||
//-------------------------------------------------------------------------------
|
||
rule "阵位规则-武器部署赋位"
|
||
agenda-group "打击任务"
|
||
salience 48
|
||
when
|
||
$fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方")
|
||
then
|
||
Map cfg = buildBusinessConfig();
|
||
applyWeaponDeployment($fact, cfg, globalParams);
|
||
end
|
||
|
||
//-------------------------------------------------------------------------------
|
||
rule "航迹规则-生成红方航迹"
|
||
agenda-group "打击任务"
|
||
salience 47
|
||
when
|
||
// 根据蓝方 trackPoints 生成红方 trackPoints,点数保持一致
|
||
$fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方")
|
||
then
|
||
Map cfg = buildBusinessConfig();
|
||
applyTrajectoryGeneration($fact, cfg);
|
||
end
|
||
|
||
//-------------------------------------------------------------------------------
|
||
// 说明:以下函数全部是 DRL function(不是 Java 类方法)
|
||
// 目标:让不懂代码的业务同事只改“可调整常量区”即可完成策略调整
|
||
|
||
// 根据蓝方武器结构,按业务映射装配红方武器并写入基础组件
|
||
function void configureRedWeaponsByBlue(
|
||
FactTask fact,
|
||
Map cfg
|
||
) {
|
||
if (fact == null || fact.getBlueTask() == null || fact.getRedTask() == null) {
|
||
return;
|
||
}
|
||
List<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);
|
||
}
|