From fecfb8d39c36502ca5c97421337d260a20d190da Mon Sep 17 00:00:00 2001 From: libertyspy Date: Tue, 31 Mar 2026 14:36:21 +0800 Subject: [PATCH 1/3] UPDATE: VERSION-20260331 --- modeler/src/style.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modeler/src/style.less b/modeler/src/style.less index c14bddb..254371c 100644 --- a/modeler/src/style.less +++ b/modeler/src/style.less @@ -1831,6 +1831,12 @@ } +.ant-select .ant-select-clear { + color: rgb(153 168 180); + background: #475f71; + border-radius: 50%; +} + .ks-add-parameter-action{ color: #eee; position: absolute; From e52aaa1680ff712cccd1911b2880ce45d5b12755 Mon Sep 17 00:00:00 2001 From: MHW Date: Tue, 31 Mar 2026 15:31:50 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E7=81=AB=E5=8A=9B=E8=A7=84=E5=88=99?= =?UTF-8?q?=EF=BC=9A=E6=B7=BB=E5=8A=A0=E6=AD=A6=E5=99=A8=E5=91=BD=E4=B8=AD?= =?UTF-8?q?=E7=8E=87=E5=9B=A0=E7=B4=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../rule/domain/simplerulepojo/Weapon.java | 3 + .../src/main/resources/rules/README.md | 21 +- .../src/main/resources/rules/fire-rule.drl | 503 +++++++++++++++++- 3 files changed, 515 insertions(+), 12 deletions(-) diff --git a/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/Weapon.java b/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/Weapon.java index 4d70f0e..1956766 100644 --- a/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/Weapon.java +++ b/auto-solution-rule/src/main/java/com/solution/rule/domain/simplerulepojo/Weapon.java @@ -25,6 +25,9 @@ public class Weapon { //武器数量 private Integer number; + //武器命中率 + private Double hitRate; + //目标id private String targetId; } diff --git a/auto-solution-rule/src/main/resources/rules/README.md b/auto-solution-rule/src/main/resources/rules/README.md index 6e715d0..5414aab 100644 --- a/auto-solution-rule/src/main/resources/rules/README.md +++ b/auto-solution-rule/src/main/resources/rules/README.md @@ -86,7 +86,20 @@ - 导弹发射车优先绑定蓝方导弹能力目标 - 当优先池不足时自动回退到地面池/全目标池,保证大部分武器有目标。 -### 2.7 阵位规则参数(新增) +### 2.7 命中率与动态火力参数(新增) +- `hitRateCsv`:业务可配置红方武器命中率,格式 `武器名=0.72,武器名2=0.55`。 +- `defaultHitRateFallback`:未命中 `hitRateCsv` 且武器未携带 `hitRate` 时的兜底命中率。 +- `desiredKillProbability`:目标毁伤置信度(例如 `0.9`)。 +- `offsetCsvByWeapon`:显式 offset(最高优先级),格式 `武器名=1,武器名2=2`。 +- `enableDynamicMultiRedPerBlue`:是否按命中率动态决定“一个蓝方目标需要几把红方武器”。 +- `minRedWeaponsPerBlueTarget` / `maxRedWeaponsPerBlueTargetCap`:每个蓝方目标的最小/最大红方分配数。 + +优先级说明(重要): +- 若 `offsetCsvByWeapon` 命中某武器,则直接使用显式 offset,不再使用命中率推导 offset。 +- 未配置显式 offset 时,规则按命中率与 `desiredKillProbability` 自动推导所需火力数量。 +- 同一蓝方 `equipmentId` 可能被多个红方武器绑定(不是固定 2 把),数量由命中率动态计算并受 cap 限制。 + +### 2.8 阵位规则参数(新增) - `enablePositionRules`:阵位规则总开关。 - 阵位输入来源:`blueTask.warZoneLocation` 与 `blueTask.defZoneLocation`(各 4 个经纬点)。 - `fireUnitSpacingMeters`:防区/作战区点位间距(米),例如 `100` 代表约每 100 米一个火力单元。 @@ -120,7 +133,7 @@ } ``` -### 2.8 航迹规则参数(新增) +### 2.9 航迹规则参数(新增) - `enableTrajectoryRules`:航迹规则总开关。 - `strategyMode`:`auto/shortest/flank/interfere`。 - `auto`:智能选择策略。 @@ -149,10 +162,12 @@ - 主决策在 `红方武器自适应装配规则`:调用 `configureRedWeaponsByBlue(...)`,按“映射配置”添加武器。 - 导弹增强在 `导弹联动增强规则`:调用 `applyMissileLinkage(...)`,受开关和阈值控制。 - 全组件数量匹配在 `全组件数量匹配规则`:按红方 `targetId` 绑定蓝方装备,覆盖非导弹组件 `componentParams[0].number`;找不到组件/targetId 允许跳过。 +- 命中率驱动数量在 `命中率规则-动态数量与offset`:按 `hitRate` 与目标毁伤概率推导火力数量;显式 offset 配置优先。 - 任务命名在 `任务自动匹配规则`:调用 `assignTaskNameByRedWeapons(...)`,按红方最终武器自动生成任务名和 `dataType`。 - 炮类约束:命中炮类条件时,炮类武器只保留 `炮弹` 组件,单位 `范围米`。 -- `targetId` 绑定:在装配后自动执行,尽量为红方武器绑定蓝方 `equipmentId`,允许少量空值冗余。 +- `targetId` 绑定:在装配后自动执行,按命中率动态给蓝目标分配多个红方武器(受上下限约束),允许少量空值冗余。 - 阵位部署:按多边形区域和武器类型自动赋位,保证防区火力覆盖。 +- 射程合理性在 `射程合理性校验规则`:基于蓝/红武器坐标计算距离,自动避免“射程不足却打击”的不合理情况(可自动调参)。 - 航迹生成:根据蓝方 `trackPoints` 生成红方 `trackPoints`,点数与蓝方一致,支持三套策略和智能选择。 ## 3.1 任务名称自动匹配(新增) diff --git a/auto-solution-rule/src/main/resources/rules/fire-rule.drl b/auto-solution-rule/src/main/resources/rules/fire-rule.drl index f462e22..97a0459 100644 --- a/auto-solution-rule/src/main/resources/rules/fire-rule.drl +++ b/auto-solution-rule/src/main/resources/rules/fire-rule.drl @@ -149,6 +149,18 @@ function Map buildBusinessConfig() { cfg.put("blueMissileRangeDefault", 220); // 蓝方导弹范围默认值 cfg.put("minBlueMissileCountForLinkage", 1); // 联动触发门槛 + // ---------- 命中率与动态火力(可改) ---------- + cfg.put("hitRateCsv", "防空导弹武器=0.72,火力打击无人机=0.62,武装直升机=0.68,反坦克火箭=0.55,反坦克导弹系统=0.78,迫榴炮=0.45,车载迫击炮=0.43,导弹发射车=0.82"); + cfg.put("defaultHitRateFallback", "0.6"); // 兜底命中率 + cfg.put("desiredKillProbability", "0.9"); // 期望毁伤置信度 + cfg.put("offsetCsvByWeapon", ""); // 显式offset(武器=offset)优先,例:反坦克火箭=1,武装直升机=2 + cfg.put("enableDynamicMultiRedPerBlue", Boolean.TRUE); // 按命中率动态决定每个蓝目标需要几个红武器 + cfg.put("minRedWeaponsPerBlueTarget", 1); // 每个蓝目标最少分配红武器数量 + cfg.put("maxRedWeaponsPerBlueTargetCap", 3); // 每个蓝目标最多分配红武器数量上限 + cfg.put("enableRangeSanityCheck", Boolean.TRUE); // 距离-射程合理性校验开关 + cfg.put("enableAutoRangeRecommend", Boolean.TRUE); // 射程不足时自动调参 + cfg.put("rangeSafetyMarginMeters", 50); // 射程安全裕量 + // ---------- 全组件数量匹配(可改) ---------- // 逻辑:按红方武器 targetId 找到对应蓝方装备(equipmentId),然后覆盖所有“非导弹组件”的数量 cfg.put("enableComponentQuantityMatch", Boolean.TRUE); @@ -238,6 +250,17 @@ then applyAllComponentQuantities($fact, cfg); end +//------------------------------------------------------------------------------- +rule "命中率规则-动态数量与offset" +agenda-group "打击任务" +salience 52 +when + $fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方") +then + Map cfg = buildBusinessConfig(); + applyHitRateDrivenOffsets($fact, cfg); +end + //------------------------------------------------------------------------------- rule "任务自动匹配规则" agenda-group "打击任务" @@ -273,9 +296,20 @@ then end //------------------------------------------------------------------------------- -rule "航迹规则-生成红方航迹" +rule "射程合理性校验规则" agenda-group "打击任务" salience 47 +when + $fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方") +then + Map cfg = buildBusinessConfig(); + applyRangeSanityAndRecommend($fact, cfg); +end + +//------------------------------------------------------------------------------- +rule "航迹规则-生成红方航迹" +agenda-group "打击任务" +salience 46 when // 根据蓝方 trackPoints 生成红方 trackPoints,点数保持一致 $fact : FactTask(blueTask.side == "蓝方", redTask.side == "红方") @@ -390,6 +424,9 @@ function void configureRedWeaponsByBlue( limitRedArtilleryToShellOnly(redWeapons, (String) cfg.get("shellRangeDefault")); } + // 写入红方武器默认命中率(若入参已给 hitRate,则不覆盖) + applyDefaultHitRateIfAbsent(redWeapons, cfg); + // 自动绑定红方武器 targetId(来源:蓝方 equipmentId) bindTargetIdsForRedWeapons(redWeapons, blueWeapons, cfg); } @@ -541,25 +578,72 @@ function void bindTargetIdsForRedWeapons(List redWeapons, List blueWeapons, Map } Map pools = extractBlueTargetPools(blueWeapons); - Map cursor = new java.util.HashMap(); + Map blueById = indexBlueWeaponsById(blueWeapons); + Map assignedCountByBlueId = new java.util.HashMap(); + Map survivalByBlueId = initBlueSurvivalMap(blueById); + double desiredKill = normalizeProbability(readDoubleCfg(cfg, "desiredKillProbability", 0.9d), 0.9d); + int cap = readIntCfg(cfg, "maxRedWeaponsPerBlueTargetCap", 3); + int minPerBlue = readIntCfg(cfg, "minRedWeaponsPerBlueTarget", 1); + if (cap <= 0) { + cap = 1; + } + if (minPerBlue < 0) { + minPerBlue = 0; + } + if (minPerBlue > cap) { + minPerBlue = cap; + } + int total = redWeapons.size(); int bound = 0; - // 第一轮:按武器类别优先匹配 + // 第一轮:先满足每个蓝目标的最小分配(在cap内) + if (minPerBlue > 0) { + List allBlueIds = (List) pools.get("all"); + if (allBlueIds != null && !allBlueIds.isEmpty()) { + for (Object idObj : allBlueIds) { + String blueId = String.valueOf(idObj); + int need = minPerBlue; + while (need > 0) { + Weapon candidate = pickUnboundRedWeaponForBlue(blueId, redWeapons); + if (candidate == null) { + break; + } + candidate.setTargetId(blueId); + bound++; + incrementAssignedCount(assignedCountByBlueId, blueId); + double p = resolveWeaponHitRate(candidate, cfg); + updateBlueSurvival(survivalByBlueId, blueId, p); + need--; + } + } + } + } + + // 第二轮:按命中率动态分配,优先填补“尚未达到期望毁伤概率”的蓝目标 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); + String targetId = pickBlueTargetByNeed( + pools, + poolKey, + assignedCountByBlueId, + survivalByBlueId, + desiredKill, + cap + ); if (!isBlank(targetId)) { redWeapon.setTargetId(targetId); bound++; + incrementAssignedCount(assignedCountByBlueId, targetId); + double p = resolveWeaponHitRate(redWeapon, cfg); + updateBlueSurvival(survivalByBlueId, targetId, p); } } @@ -570,7 +654,7 @@ function void bindTargetIdsForRedWeapons(List redWeapons, List blueWeapons, Map return; } - // 第二轮:若绑定率不足,回退到全目标池尽量补齐(仍允许复用目标) + // 第三轮:若绑定率不足,回退全目标池补齐(仍受cap约束) for (Object obj : redWeapons) { if (total > 0 && ((double) bound / (double) total) >= minRatio) { break; @@ -579,28 +663,418 @@ function void bindTargetIdsForRedWeapons(List redWeapons, List blueWeapons, Map if (redWeapon == null || !isBlank(redWeapon.getTargetId())) { continue; } - String targetId = pickTargetIdFromPools(pools, cursor, "all"); + String targetId = pickBlueTargetByNeed( + pools, + "all", + assignedCountByBlueId, + survivalByBlueId, + desiredKill, + cap + ); if (!isBlank(targetId)) { redWeapon.setTargetId(targetId); bound++; + incrementAssignedCount(assignedCountByBlueId, targetId); + double p = resolveWeaponHitRate(redWeapon, cfg); + updateBlueSurvival(survivalByBlueId, targetId, p); } } - // 第三轮:若不允许空 targetId,最后强制从 all 池补齐(尽力而为) + // 第四轮:若不允许空 targetId,最后尽力补齐 if (!allowReserveWithoutTarget) { for (Object obj : redWeapons) { Weapon redWeapon = (Weapon) obj; if (redWeapon == null || !isBlank(redWeapon.getTargetId())) { continue; } - String targetId = pickTargetIdFromPools(pools, cursor, "all"); + String targetId = pickBlueTargetByNeed( + pools, + "all", + assignedCountByBlueId, + survivalByBlueId, + desiredKill, + cap + ); if (!isBlank(targetId)) { redWeapon.setTargetId(targetId); + incrementAssignedCount(assignedCountByBlueId, targetId); + double p = resolveWeaponHitRate(redWeapon, cfg); + updateBlueSurvival(survivalByBlueId, targetId, p); } } } } +function void applyHitRateDrivenOffsets(FactTask fact, Map cfg) { + if (fact == null || fact.getRedTask() == null) { + return; + } + List redWeapons = fact.getRedTask().getTaskWeapons(); + if (redWeapons == null || redWeapons.isEmpty()) { + return; + } + applyDefaultHitRateIfAbsent(redWeapons, cfg); + Map explicitOffset = parseNameIntCsv((String) cfg.get("offsetCsvByWeapon")); + double desiredKill = normalizeProbability(readDoubleCfg(cfg, "desiredKillProbability", 0.9d), 0.9d); + for (Object obj : redWeapons) { + Weapon redWeapon = (Weapon) obj; + if (redWeapon == null) { + continue; + } + int base = redWeapon.getNumber() == null || redWeapon.getNumber() <= 0 ? 1 : redWeapon.getNumber().intValue(); + Integer explicit = (Integer) explicitOffset.get(redWeapon.getName()); + int offset = 0; + if (explicit != null) { + offset = explicit.intValue(); + } else { + double pHit = resolveWeaponHitRate(redWeapon, cfg); + int required = computeRequiredShots(pHit, desiredKill, base); + offset = required - base; + } + if (offset < 0) { + offset = 0; + } + redWeapon.setNumber(base + offset); + } +} + +function void applyRangeSanityAndRecommend(FactTask fact, Map cfg) { + if (!readBooleanCfg(cfg, "enableRangeSanityCheck", true)) { + return; + } + if (fact == null || fact.getBlueTask() == null || fact.getRedTask() == null) { + return; + } + List blueWeapons = fact.getBlueTask().getTaskWeapons(); + List redWeapons = fact.getRedTask().getTaskWeapons(); + if (blueWeapons == null || blueWeapons.isEmpty() || redWeapons == null || redWeapons.isEmpty()) { + return; + } + Map blueById = indexBlueWeaponsById(blueWeapons); + int margin = readIntCfg(cfg, "rangeSafetyMarginMeters", 50); + boolean autoAdjust = readBooleanCfg(cfg, "enableAutoRangeRecommend", true); + for (Object obj : redWeapons) { + Weapon redWeapon = (Weapon) obj; + if (redWeapon == null || isBlank(redWeapon.getTargetId())) { + continue; + } + Weapon blueWeapon = (Weapon) blueById.get(redWeapon.getTargetId()); + if (blueWeapon == null) { + continue; + } + Coordinate rc = redWeapon.getCoordinate(); + Coordinate bc = blueWeapon.getCoordinate(); + if (!isCoordinateUsable(rc) || !isCoordinateUsable(bc)) { + continue; + } + int currentRange = readRangeMetersFromWeapon(redWeapon); + if (currentRange <= 0) { + continue; + } + double d = approxDistanceMeters( + rc.getLongitude().doubleValue(), + rc.getLatitude().doubleValue(), + bc.getLongitude().doubleValue(), + bc.getLatitude().doubleValue() + ); + int minRequired = (int) Math.ceil(d) + margin; + if (currentRange >= minRequired) { + continue; + } + if (autoAdjust) { + setWeaponFirstRangeParamAtLeast(redWeapon, minRequired); + } + } +} + +function void applyDefaultHitRateIfAbsent(List redWeapons, Map cfg) { + if (redWeapons == null || redWeapons.isEmpty()) { + return; + } + Map hitRateMap = parseNameDoubleCsv((String) cfg.get("hitRateCsv")); + double fallback = normalizeProbability(readDoubleCfg(cfg, "defaultHitRateFallback", 0.6d), 0.6d); + for (Object obj : redWeapons) { + Weapon redWeapon = (Weapon) obj; + if (redWeapon == null) { + continue; + } + if (redWeapon.getHitRate() != null && redWeapon.getHitRate().doubleValue() > 0.0d) { + continue; + } + Double p = (Double) hitRateMap.get(redWeapon.getName()); + if (p == null) { + p = fallback; + } + redWeapon.setHitRate(normalizeProbability(p.doubleValue(), fallback)); + } +} + +function double resolveWeaponHitRate(Weapon redWeapon, Map cfg) { + if (redWeapon != null && redWeapon.getHitRate() != null) { + double p = redWeapon.getHitRate().doubleValue(); + return normalizeProbability(p, 0.6d); + } + Map hitRateMap = parseNameDoubleCsv((String) cfg.get("hitRateCsv")); + Double fromCfg = redWeapon == null ? null : (Double) hitRateMap.get(redWeapon.getName()); + if (fromCfg != null) { + return normalizeProbability(fromCfg.doubleValue(), 0.6d); + } + return normalizeProbability(readDoubleCfg(cfg, "defaultHitRateFallback", 0.6d), 0.6d); +} + +function int computeRequiredShots(double pHit, double pKill, int fallback) { + double p = normalizeProbability(pHit, 0.6d); + double pk = normalizeProbability(pKill, 0.9d); + if (p >= 0.999d) { + return 1; + } + if (p <= 0.0001d) { + return fallback <= 0 ? 1 : fallback; + } + double up = Math.log(1.0d - pk); + double down = Math.log(1.0d - p); + if (down == 0.0d) { + return fallback <= 0 ? 1 : fallback; + } + int n = (int) Math.ceil(up / down); + if (n <= 0) { + n = 1; + } + return n; +} + +function Map parseNameDoubleCsv(String csv) { + Map result = new java.util.HashMap(); + if (csv == null || csv.trim().equals("")) { + return result; + } + String[] items = csv.split(","); + for (int i = 0; i < items.length; i++) { + String one = items[i]; + if (one == null) { + continue; + } + String text = one.trim(); + if (text.equals("")) { + continue; + } + int idx = text.indexOf("="); + if (idx <= 0 || idx >= text.length() - 1) { + continue; + } + String key = text.substring(0, idx).trim(); + String val = text.substring(idx + 1).trim(); + if (key.equals("") || val.equals("")) { + continue; + } + result.put(key, Double.valueOf(parseDoubleSafe(val, 0.0d))); + } + return result; +} + +function Map parseNameIntCsv(String csv) { + Map result = new java.util.HashMap(); + if (csv == null || csv.trim().equals("")) { + return result; + } + String[] items = csv.split(","); + for (int i = 0; i < items.length; i++) { + String one = items[i]; + if (one == null) { + continue; + } + String text = one.trim(); + if (text.equals("")) { + continue; + } + int idx = text.indexOf("="); + if (idx <= 0 || idx >= text.length() - 1) { + continue; + } + String key = text.substring(0, idx).trim(); + String val = text.substring(idx + 1).trim(); + if (key.equals("") || val.equals("")) { + continue; + } + result.put(key, Integer.valueOf(parseIntSafe(val, 0))); + } + return result; +} + +function double normalizeProbability(double p, double fallback) { + if (Double.isNaN(p) || Double.isInfinite(p)) { + p = fallback; + } + if (p <= 0.0d) { + p = fallback; + } + if (p <= 0.0d) { + p = 0.0001d; + } + if (p >= 1.0d) { + p = 0.999d; + } + return p; +} + +function Map indexBlueWeaponsById(List blueWeapons) { + Map result = new java.util.HashMap(); + if (blueWeapons == null) { + return result; + } + for (Object obj : blueWeapons) { + Weapon w = (Weapon) obj; + if (w == null || isBlank(w.getEquipmentId())) { + continue; + } + result.put(w.getEquipmentId(), w); + } + return result; +} + +function Map initBlueSurvivalMap(Map blueById) { + Map result = new java.util.HashMap(); + if (blueById == null) { + return result; + } + for (Object key : blueById.keySet()) { + result.put(key, Double.valueOf(1.0d)); + } + return result; +} + +function void updateBlueSurvival(Map survivalByBlueId, String blueId, double pHit) { + if (survivalByBlueId == null || isBlank(blueId)) { + return; + } + Double survival = (Double) survivalByBlueId.get(blueId); + if (survival == null) { + survival = Double.valueOf(1.0d); + } + double s = survival.doubleValue(); + double p = normalizeProbability(pHit, 0.6d); + s = s * (1.0d - p); + if (s < 0.0d) { + s = 0.0d; + } + survivalByBlueId.put(blueId, Double.valueOf(s)); +} + +function void incrementAssignedCount(Map assignedCountByBlueId, String blueId) { + Integer old = (Integer) assignedCountByBlueId.get(blueId); + int now = old == null ? 1 : old.intValue() + 1; + assignedCountByBlueId.put(blueId, Integer.valueOf(now)); +} + +function Weapon pickUnboundRedWeaponForBlue(String blueId, List redWeapons) { + if (redWeapons == null || redWeapons.isEmpty()) { + return null; + } + for (Object obj : redWeapons) { + Weapon w = (Weapon) obj; + if (w == null || !isBlank(w.getTargetId())) { + continue; + } + return w; + } + return null; +} + +function String pickBlueTargetByNeed(Map pools, String preferredKey, Map assignedCountByBlueId, Map survivalByBlueId, double desiredKill, int cap) { + String best = pickBestBlueIdFromPool((List) pools.get(preferredKey), assignedCountByBlueId, survivalByBlueId, desiredKill, cap); + if (!isBlank(best)) { + return best; + } + if (!"ground".equals(preferredKey)) { + String fromGround = pickBestBlueIdFromPool((List) pools.get("ground"), assignedCountByBlueId, survivalByBlueId, desiredKill, cap); + if (!isBlank(fromGround)) { + return fromGround; + } + } + return pickBestBlueIdFromPool((List) pools.get("all"), assignedCountByBlueId, survivalByBlueId, desiredKill, cap); +} + +function String pickBestBlueIdFromPool(List ids, Map assignedCountByBlueId, Map survivalByBlueId, double desiredKill, int cap) { + if (ids == null || ids.isEmpty()) { + return null; + } + String bestId = null; + double bestNeed = -9999.0d; + int bestAssigned = Integer.MAX_VALUE; + for (Object obj : ids) { + String id = String.valueOf(obj); + if (isBlank(id)) { + continue; + } + Integer assignedObj = (Integer) assignedCountByBlueId.get(id); + int assigned = assignedObj == null ? 0 : assignedObj.intValue(); + if (assigned >= cap) { + continue; + } + Double survivalObj = (Double) survivalByBlueId.get(id); + double survival = survivalObj == null ? 1.0d : survivalObj.doubleValue(); + double achieved = 1.0d - survival; + double need = desiredKill - achieved; + if (need > bestNeed || (Math.abs(need - bestNeed) < 1e-9 && assigned < bestAssigned)) { + bestNeed = need; + bestAssigned = assigned; + bestId = id; + } + } + return bestId; +} + +function boolean isCoordinateUsable(Coordinate c) { + return c != null && c.getLongitude() != null && c.getLatitude() != null; +} + +function int readRangeMetersFromWeapon(Weapon weapon) { + if (weapon == null || weapon.getComponents() == null) { + return -1; + } + for (Object obj : weapon.getComponents()) { + SubComponents comp = (SubComponents) obj; + if (comp == null || comp.getComponentParams() == null || comp.getComponentParams().isEmpty()) { + continue; + } + ComponentParam first = (ComponentParam) comp.getComponentParams().get(0); + if (first == null || first.getAttExplain() == null) { + continue; + } + String explain = first.getAttExplain(); + if (!(explain.contains("范围") || explain.contains("射程"))) { + continue; + } + return parseIntSafe(first.getAttDefaultValue(), -1); + } + return -1; +} + +function void setWeaponFirstRangeParamAtLeast(Weapon weapon, int minRange) { + if (weapon == null || weapon.getComponents() == null || minRange <= 0) { + return; + } + for (Object obj : weapon.getComponents()) { + SubComponents comp = (SubComponents) obj; + if (comp == null || comp.getComponentParams() == null || comp.getComponentParams().isEmpty()) { + continue; + } + ComponentParam first = (ComponentParam) comp.getComponentParams().get(0); + if (first == null || first.getAttExplain() == null) { + continue; + } + String explain = first.getAttExplain(); + if (!(explain.contains("范围") || explain.contains("射程"))) { + continue; + } + int current = parseIntSafe(first.getAttDefaultValue(), 0); + if (current < minRange) { + first.setAttDefaultValue(String.valueOf(minRange)); + } + return; + } +} + //------------------------------------------------------------------------------- // component 映射解析 + 全组件数量覆盖 function Map parseDeviceNameMapping(String csv) { @@ -1653,6 +2127,17 @@ function int parseIntSafe(String text, int fallback) { } } +function double parseDoubleSafe(String text, double fallback) { + if (text == null || text.equals("")) { + return fallback; + } + try { + return Double.parseDouble(text.trim()); + } catch (Exception ex) { + return fallback; + } +} + function int readIntCfg(Map cfg, String key, int fallback) { if (cfg == null || key == null) { return fallback; From eb57d2d2287665077a6eb8f82fcc394600ea4171 Mon Sep 17 00:00:00 2001 From: MHW Date: Tue, 31 Mar 2026 15:39:58 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E8=A1=8C=E4=B8=BA=E6=A0=91=EF=BC=9A?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=A1=8C=E4=B8=BA=E6=A0=91=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0id?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solution/system/domain/Behaviortree.java | 32 +++++++++++++------ .../mapper/system/BehaviortreeMapper.xml | 2 ++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/auto-solution-behaviour/src/main/java/com/solution/system/domain/Behaviortree.java b/auto-solution-behaviour/src/main/java/com/solution/system/domain/Behaviortree.java index 3bbfe74..857d083 100644 --- a/auto-solution-behaviour/src/main/java/com/solution/system/domain/Behaviortree.java +++ b/auto-solution-behaviour/src/main/java/com/solution/system/domain/Behaviortree.java @@ -46,7 +46,18 @@ public class Behaviortree extends BaseEntity @Excel(name = "储存行为树的节点关系图") private String xmlContent; - public void setId(Long id) + @Excel(name = "平台ID") + private Integer platformId; + + public Integer getPlatformId() { + return platformId; + } + + public void setPlatformId(Integer platformId) { + this.platformId = platformId; + } + + public void setId(Long id) { this.id = id; } @@ -118,14 +129,15 @@ public class Behaviortree extends BaseEntity @Override public String toString() { - return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) - .append("id", getId()) - .append("name", getName()) - .append("description", getDescription()) - .append("createdAt", getCreatedAt()) - .append("updatedAt", getUpdatedAt()) - .append("englishName", getEnglishName()) - .append("xmlContent", getXmlContent()) - .toString(); + return "Behaviortree{" + + "id=" + id + + ", name='" + name + '\'' + + ", description='" + description + '\'' + + ", createdAt=" + createdAt + + ", updatedAt=" + updatedAt + + ", englishName='" + englishName + '\'' + + ", xmlContent='" + xmlContent + '\'' + + ", platformId=" + platformId + + '}'; } } diff --git a/auto-solution-behaviour/src/main/resources/mapper/system/BehaviortreeMapper.xml b/auto-solution-behaviour/src/main/resources/mapper/system/BehaviortreeMapper.xml index 0507194..302f88e 100644 --- a/auto-solution-behaviour/src/main/resources/mapper/system/BehaviortreeMapper.xml +++ b/auto-solution-behaviour/src/main/resources/mapper/system/BehaviortreeMapper.xml @@ -44,6 +44,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" updated_at, english_name, xml_content, + platform_id, #{name}, @@ -52,6 +53,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{updatedAt}, #{englishName}, #{xmlContent}, + #{platformId},