feat(radar): 人机识别标记(同队装备分数相同且 < 8000)

- hornet_lib:新增 GetGearScore(BattleGearScoreCC.score → 组件 cjf_1)、ScanBots(按队伍分组,同分且低于阈值判为人机)、GetBotSet/IsBot

- hornet_radar:约每 0.3 秒扫一次;人机画「灰方块 + 黄框」,真人仍「实心圆 + 白边」;顶部统计加 Bot 计数

- 重新生成 hornet_bundle.js 并同步到 unpacked
This commit is contained in:
TermiNexus committed 2026-10-15 15:27:34 +08:00
1 parent 92b5ca6e9b
commit 2b64211173
4 files changed
+231 -30

No files matched your search

@@ -322,6 +322,49 @@
return { current: cur, max: max, percent: cur / max };
}
// ===== 装备分数(战斗 GS:BattleGearScoreCC.score 落在组件的 cjf_1 上) =====
// 来源:module_85183 的 BattleGearScoreCC(score = cjf_1);战斗内每辆坦克各有一份
function GetGearScore(tank) {
var c = FindComponentByField(tank, 'cjf_1');
if (!c || typeof c.cjf_1 !== 'number') return null;
return c.cjf_1;
}
// ===== 人机识别 =====
// 规则:**同一队伍**内,**装备分数相同**的坦克(且分数 < maxScore,默认 8000)判为人机。
// 组内唯一的分数不算(真人装备各不相同,机器人往往整队同款)。
// 用法:工具定期调用 ScanBots()(约每 0.3 秒一次即可),再用 IsBot(tank) 查询。
var BotSet = new Set();
function ScanBots(opt) {
opt = opt || {};
var MaxScore = typeof opt.maxScore === 'number' ? opt.maxScore : 8000;
BotSet = new Set();
var gm = window.__gameMode__;
if (!gm || !gm.q100_1) return BotSet;
var playerTank = GetPlayerTank(gm);
var list, n;
try { list = gm.q100_1.r11l(); n = list.e1(); } catch (e) { return BotSet; }
var Groups = { ally: {}, enemy: {} }; // 组 → { 分数 → [坦克...] }
for (var i = 0; i < n; i++) {
var tank = list.g1(i);
if (!tank) continue;
var gs = GetGearScore(tank);
if (gs === null || gs >= MaxScore) continue; // 只统计低于阈值的
var g = IsFriendly(tank, playerTank, gm) ? 'ally' : 'enemy';
(Groups[g][gs] = Groups[g][gs] || []).push(tank);
}
['ally', 'enemy'].forEach(function (g) {
var Bag = Groups[g];
Object.keys(Bag).forEach(function (gs) {
var Arr = Bag[gs];
if (Arr.length >= 2) Arr.forEach(function (t) { BotSet.add(t); }); // 同队同分 → 人机
});
});
return BotSet;
}
function GetBotSet() { return BotSet; }
function IsBot(tank) { return BotSet.has(tank); }
// ===== 获取玩家坦克 =====
// 优先 ezr_1(当前观察坦克),备用 fzr_1(本地控制坦克),再备用坦克列表第一个
// checkPosition: 是否检查位置有效性(默认true;测试脚本传false以在死亡状态控制)
@@ -649,6 +692,10 @@
IsTankAlive: IsTankAlive,
GetTankName: GetTankName,
GetTankHealth: GetTankHealth,
GetGearScore: GetGearScore,
ScanBots: ScanBots,
GetBotSet: GetBotSet,
IsBot: IsBot,
GetPlayerTank: GetPlayerTank,
IsFriendly: IsFriendly,
GetCameraInfo: GetCameraInfo,
@@ -689,6 +736,8 @@
}
var teamFns = lib.LoadTeamFunctions();
var ZCt = teamFns.ZCt, m2Fn = teamFns.m2Fn;
// 人机标记:ScanBots 定期扫描(同队内装备分数相同且 < 8000)→ IsBot 查询
var ScanBots = lib.ScanBots, IsBot = lib.IsBot;
// 组件查找:使用公共库(已修复 falsy 值漏判 bug)
var FindComponentByField = lib.FindComponentByField;
@@ -1228,6 +1277,12 @@
}
radar.tankCount = count;
// 人机扫描:装备分数不会每帧变,节流到约每 0.3 秒一次
if (Ready && ScanBots && (Date.now() - (radar._lastBotScan || 0) > 300)) {
radar._lastBotScan = Date.now();
ScanBots();
}
// 获取玩家坦克(带备用方案)
var playerTank = Ready ? GetPlayerTank() : null;
radar.playerFound = (playerTank !== null && playerTank !== undefined);
@@ -1307,7 +1362,7 @@
if (playerPos) drawTerrain(ctx, playerPos, playerAngle);
// 绘制所有坦克
var friendlyCount = 0, enemyCount = 0, deadCount = 0;
var friendlyCount = 0, enemyCount = 0, deadCount = 0, botCount = 0;
for (var i = 0; i < count; i++) {
var tank = tankList.g1(i);
if (!tank) continue;
@@ -1351,14 +1406,26 @@
var color = friendly ? '#4488ff' : '#ff4444';
if (friendly) friendlyCount++; else enemyCount++;
// 画坦克点
ctx.beginPath();
ctx.arc(radarX, radarY, 5, 0, 2 * Math.PI);
ctx.fillStyle = color;
ctx.fill();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
ctx.stroke();
// 画坦克点:人机用「灰方块 + 黄框」,真人用「实心圆 + 白边」
var bot = (typeof IsBot === 'function') && IsBot(tank);
if (bot) botCount++;
if (bot) {
ctx.beginPath();
ctx.rect(radarX - 5, radarY - 5, 10, 10);
ctx.fillStyle = 'rgba(170,170,170,0.55)';
ctx.fill();
ctx.strokeStyle = '#ffd24a';
ctx.lineWidth = 1.5;
ctx.stroke();
} else {
ctx.beginPath();
ctx.arc(radarX, radarY, 5, 0, 2 * Math.PI);
ctx.fillStyle = color;
ctx.fill();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
ctx.stroke();
}
}
// 画玩家(中心,三角形朝上)
@@ -1391,7 +1458,7 @@
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
ctx.font = '9px Arial';
ctx.textAlign = 'left';
ctx.fillText('Tanks: ' + count + ' (B:' + friendlyCount + ' R:' + enemyCount + ' X:' + deadCount + ')', 5, 12);
ctx.fillText('Tanks: ' + count + ' (B:' + friendlyCount + ' R:' + enemyCount + ' X:' + deadCount + ' Bot:' + botCount + ')', 5, 12);
if (radar.playerFound) {
ctx.fillText('Player: ' + (playerTank ? playerTank.hyt_1 : '?'), 5, 24);
} else {
+77 -10
View File
@@ -322,6 +322,49 @@
return { current: cur, max: max, percent: cur / max };
}
// ===== 装备分数(战斗 GS:BattleGearScoreCC.score 落在组件的 cjf_1 上) =====
// 来源:module_85183 的 BattleGearScoreCC(score = cjf_1);战斗内每辆坦克各有一份
function GetGearScore(tank) {
var c = FindComponentByField(tank, 'cjf_1');
if (!c || typeof c.cjf_1 !== 'number') return null;
return c.cjf_1;
}
// ===== 人机识别 =====
// 规则:**同一队伍**内,**装备分数相同**的坦克(且分数 < maxScore,默认 8000)判为人机。
// 组内唯一的分数不算(真人装备各不相同,机器人往往整队同款)。
// 用法:工具定期调用 ScanBots()(约每 0.3 秒一次即可),再用 IsBot(tank) 查询。
var BotSet = new Set();
function ScanBots(opt) {
opt = opt || {};
var MaxScore = typeof opt.maxScore === 'number' ? opt.maxScore : 8000;
BotSet = new Set();
var gm = window.__gameMode__;
if (!gm || !gm.q100_1) return BotSet;
var playerTank = GetPlayerTank(gm);
var list, n;
try { list = gm.q100_1.r11l(); n = list.e1(); } catch (e) { return BotSet; }
var Groups = { ally: {}, enemy: {} }; // 组 → { 分数 → [坦克...] }
for (var i = 0; i < n; i++) {
var tank = list.g1(i);
if (!tank) continue;
var gs = GetGearScore(tank);
if (gs === null || gs >= MaxScore) continue; // 只统计低于阈值的
var g = IsFriendly(tank, playerTank, gm) ? 'ally' : 'enemy';
(Groups[g][gs] = Groups[g][gs] || []).push(tank);
}
['ally', 'enemy'].forEach(function (g) {
var Bag = Groups[g];
Object.keys(Bag).forEach(function (gs) {
var Arr = Bag[gs];
if (Arr.length >= 2) Arr.forEach(function (t) { BotSet.add(t); }); // 同队同分 → 人机
});
});
return BotSet;
}
function GetBotSet() { return BotSet; }
function IsBot(tank) { return BotSet.has(tank); }
// ===== 获取玩家坦克 =====
// 优先 ezr_1(当前观察坦克),备用 fzr_1(本地控制坦克),再备用坦克列表第一个
// checkPosition: 是否检查位置有效性(默认true;测试脚本传false以在死亡状态控制)
@@ -649,6 +692,10 @@
IsTankAlive: IsTankAlive,
GetTankName: GetTankName,
GetTankHealth: GetTankHealth,
GetGearScore: GetGearScore,
ScanBots: ScanBots,
GetBotSet: GetBotSet,
IsBot: IsBot,
GetPlayerTank: GetPlayerTank,
IsFriendly: IsFriendly,
GetCameraInfo: GetCameraInfo,
@@ -689,6 +736,8 @@
}
var teamFns = lib.LoadTeamFunctions();
var ZCt = teamFns.ZCt, m2Fn = teamFns.m2Fn;
// 人机标记:ScanBots 定期扫描(同队内装备分数相同且 < 8000)→ IsBot 查询
var ScanBots = lib.ScanBots, IsBot = lib.IsBot;
// 组件查找:使用公共库(已修复 falsy 值漏判 bug)
var FindComponentByField = lib.FindComponentByField;
@@ -1228,6 +1277,12 @@
}
radar.tankCount = count;
// 人机扫描:装备分数不会每帧变,节流到约每 0.3 秒一次
if (Ready && ScanBots && (Date.now() - (radar._lastBotScan || 0) > 300)) {
radar._lastBotScan = Date.now();
ScanBots();
}
// 获取玩家坦克(带备用方案)
var playerTank = Ready ? GetPlayerTank() : null;
radar.playerFound = (playerTank !== null && playerTank !== undefined);
@@ -1307,7 +1362,7 @@
if (playerPos) drawTerrain(ctx, playerPos, playerAngle);
// 绘制所有坦克
var friendlyCount = 0, enemyCount = 0, deadCount = 0;
var friendlyCount = 0, enemyCount = 0, deadCount = 0, botCount = 0;
for (var i = 0; i < count; i++) {
var tank = tankList.g1(i);
if (!tank) continue;
@@ -1351,14 +1406,26 @@
var color = friendly ? '#4488ff' : '#ff4444';
if (friendly) friendlyCount++; else enemyCount++;
// 画坦克点
ctx.beginPath();
ctx.arc(radarX, radarY, 5, 0, 2 * Math.PI);
ctx.fillStyle = color;
ctx.fill();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
ctx.stroke();
// 画坦克点:人机用「灰方块 + 黄框」,真人用「实心圆 + 白边」
var bot = (typeof IsBot === 'function') && IsBot(tank);
if (bot) botCount++;
if (bot) {
ctx.beginPath();
ctx.rect(radarX - 5, radarY - 5, 10, 10);
ctx.fillStyle = 'rgba(170,170,170,0.55)';
ctx.fill();
ctx.strokeStyle = '#ffd24a';
ctx.lineWidth = 1.5;
ctx.stroke();
} else {
ctx.beginPath();
ctx.arc(radarX, radarY, 5, 0, 2 * Math.PI);
ctx.fillStyle = color;
ctx.fill();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
ctx.stroke();
}
}
// 画玩家(中心,三角形朝上)
@@ -1391,7 +1458,7 @@
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
ctx.font = '9px Arial';
ctx.textAlign = 'left';
ctx.fillText('Tanks: ' + count + ' (B:' + friendlyCount + ' R:' + enemyCount + ' X:' + deadCount + ')', 5, 12);
ctx.fillText('Tanks: ' + count + ' (B:' + friendlyCount + ' R:' + enemyCount + ' X:' + deadCount + ' Bot:' + botCount + ')', 5, 12);
if (radar.playerFound) {
ctx.fillText('Player: ' + (playerTank ? playerTank.hyt_1 : '?'), 5, 24);
} else {
+47
View File
@@ -107,6 +107,49 @@
return { current: cur, max: max, percent: cur / max };
}
// ===== 装备分数(战斗 GS:BattleGearScoreCC.score 落在组件的 cjf_1 上) =====
// 来源:module_85183 的 BattleGearScoreCC(score = cjf_1);战斗内每辆坦克各有一份
function GetGearScore(tank) {
var c = FindComponentByField(tank, 'cjf_1');
if (!c || typeof c.cjf_1 !== 'number') return null;
return c.cjf_1;
}
// ===== 人机识别 =====
// 规则:**同一队伍**内,**装备分数相同**的坦克(且分数 < maxScore,默认 8000)判为人机。
// 组内唯一的分数不算(真人装备各不相同,机器人往往整队同款)。
// 用法:工具定期调用 ScanBots()(约每 0.3 秒一次即可),再用 IsBot(tank) 查询。
var BotSet = new Set();
function ScanBots(opt) {
opt = opt || {};
var MaxScore = typeof opt.maxScore === 'number' ? opt.maxScore : 8000;
BotSet = new Set();
var gm = window.__gameMode__;
if (!gm || !gm.q100_1) return BotSet;
var playerTank = GetPlayerTank(gm);
var list, n;
try { list = gm.q100_1.r11l(); n = list.e1(); } catch (e) { return BotSet; }
var Groups = { ally: {}, enemy: {} }; // 组 → { 分数 → [坦克...] }
for (var i = 0; i < n; i++) {
var tank = list.g1(i);
if (!tank) continue;
var gs = GetGearScore(tank);
if (gs === null || gs >= MaxScore) continue; // 只统计低于阈值的
var g = IsFriendly(tank, playerTank, gm) ? 'ally' : 'enemy';
(Groups[g][gs] = Groups[g][gs] || []).push(tank);
}
['ally', 'enemy'].forEach(function (g) {
var Bag = Groups[g];
Object.keys(Bag).forEach(function (gs) {
var Arr = Bag[gs];
if (Arr.length >= 2) Arr.forEach(function (t) { BotSet.add(t); }); // 同队同分 → 人机
});
});
return BotSet;
}
function GetBotSet() { return BotSet; }
function IsBot(tank) { return BotSet.has(tank); }
// ===== 获取玩家坦克 =====
// 优先 ezr_1(当前观察坦克),备用 fzr_1(本地控制坦克),再备用坦克列表第一个
// checkPosition: 是否检查位置有效性(默认true;测试脚本传false以在死亡状态控制)
@@ -434,6 +477,10 @@
IsTankAlive: IsTankAlive,
GetTankName: GetTankName,
GetTankHealth: GetTankHealth,
GetGearScore: GetGearScore,
ScanBots: ScanBots,
GetBotSet: GetBotSet,
IsBot: IsBot,
GetPlayerTank: GetPlayerTank,
IsFriendly: IsFriendly,
GetCameraInfo: GetCameraInfo,
+30 -10
View File
@@ -22,6 +22,8 @@
}
var teamFns = lib.LoadTeamFunctions();
var ZCt = teamFns.ZCt, m2Fn = teamFns.m2Fn;
// 人机标记:ScanBots 定期扫描(同队内装备分数相同且 < 8000)→ IsBot 查询
var ScanBots = lib.ScanBots, IsBot = lib.IsBot;
// 组件查找:使用公共库(已修复 falsy 值漏判 bug)
var FindComponentByField = lib.FindComponentByField;
@@ -561,6 +563,12 @@
}
radar.tankCount = count;
// 人机扫描:装备分数不会每帧变,节流到约每 0.3 秒一次
if (Ready && ScanBots && (Date.now() - (radar._lastBotScan || 0) > 300)) {
radar._lastBotScan = Date.now();
ScanBots();
}
// 获取玩家坦克(带备用方案)
var playerTank = Ready ? GetPlayerTank() : null;
radar.playerFound = (playerTank !== null && playerTank !== undefined);
@@ -640,7 +648,7 @@
if (playerPos) drawTerrain(ctx, playerPos, playerAngle);
// 绘制所有坦克
var friendlyCount = 0, enemyCount = 0, deadCount = 0;
var friendlyCount = 0, enemyCount = 0, deadCount = 0, botCount = 0;
for (var i = 0; i < count; i++) {
var tank = tankList.g1(i);
if (!tank) continue;
@@ -684,14 +692,26 @@
var color = friendly ? '#4488ff' : '#ff4444';
if (friendly) friendlyCount++; else enemyCount++;
// 画坦克点
ctx.beginPath();
ctx.arc(radarX, radarY, 5, 0, 2 * Math.PI);
ctx.fillStyle = color;
ctx.fill();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
ctx.stroke();
// 画坦克点:人机用「灰方块 + 黄框」,真人用「实心圆 + 白边」
var bot = (typeof IsBot === 'function') && IsBot(tank);
if (bot) botCount++;
if (bot) {
ctx.beginPath();
ctx.rect(radarX - 5, radarY - 5, 10, 10);
ctx.fillStyle = 'rgba(170,170,170,0.55)';
ctx.fill();
ctx.strokeStyle = '#ffd24a';
ctx.lineWidth = 1.5;
ctx.stroke();
} else {
ctx.beginPath();
ctx.arc(radarX, radarY, 5, 0, 2 * Math.PI);
ctx.fillStyle = color;
ctx.fill();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 1;
ctx.stroke();
}
}
// 画玩家(中心,三角形朝上)
@@ -724,7 +744,7 @@
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
ctx.font = '9px Arial';
ctx.textAlign = 'left';
ctx.fillText('Tanks: ' + count + ' (B:' + friendlyCount + ' R:' + enemyCount + ' X:' + deadCount + ')', 5, 12);
ctx.fillText('Tanks: ' + count + ' (B:' + friendlyCount + ' R:' + enemyCount + ' X:' + deadCount + ' Bot:' + botCount + ')', 5, 12);
if (radar.playerFound) {
ctx.fillText('Player: ' + (playerTank ? playerTank.hyt_1 : '?'), 5, 24);
} else {