From 2b642111738a6bb0e8ef522fc05bb23ce416d96b Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 15 Oct 2026 15:27:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(radar):=20=E4=BA=BA=E6=9C=BA=E8=AF=86?= =?UTF-8?q?=E5=88=AB=E6=A0=87=E8=AE=B0=EF=BC=88=E5=90=8C=E9=98=9F=E8=A3=85?= =?UTF-8?q?=E5=A4=87=E5=88=86=E6=95=B0=E7=9B=B8=E5=90=8C=E4=B8=94=20<=2080?= =?UTF-8?q?00=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hornet_lib:新增 GetGearScore(BattleGearScoreCC.score → 组件 cjf_1)、ScanBots(按队伍分组,同分且低于阈值判为人机)、GetBotSet/IsBot - hornet_radar:约每 0.3 秒扫一次;人机画「灰方块 + 黄框」,真人仍「实心圆 + 白边」;顶部统计加 Bot 计数 - 重新生成 hornet_bundle.js 并同步到 unpacked --- .../play/static/js/unpacked/hornet_bundle.js | 87 ++++++++++++++++--- 3dtank_bundle.js | 87 ++++++++++++++++--- tools/hornet_lib.js | 47 ++++++++++ tools/hornet_radar.js | 40 ++++++--- 4 files changed, 231 insertions(+), 30 deletions(-) diff --git a/3dtank.com/play/static/js/unpacked/hornet_bundle.js b/3dtank.com/play/static/js/unpacked/hornet_bundle.js index 178d898..38a2f4d 100644 --- a/3dtank.com/play/static/js/unpacked/hornet_bundle.js +++ b/3dtank.com/play/static/js/unpacked/hornet_bundle.js @@ -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 { diff --git a/3dtank_bundle.js b/3dtank_bundle.js index 178d898..38a2f4d 100644 --- a/3dtank_bundle.js +++ b/3dtank_bundle.js @@ -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 { diff --git a/tools/hornet_lib.js b/tools/hornet_lib.js index 43ee27f..b94196d 100644 --- a/tools/hornet_lib.js +++ b/tools/hornet_lib.js @@ -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, diff --git a/tools/hornet_radar.js b/tools/hornet_radar.js index 066aa55..3d3f800 100644 --- a/tools/hornet_radar.js +++ b/tools/hornet_radar.js @@ -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 {