// ===== 3DTank公共库 - 工具文件共享辅助函数 ===== // 在浏览器控制台运行(需先运行 hook_gamemode.js) // 功能:提供坦克查询、队伍判断、相机信息、3D投影等公共函数 // 用法:各工具文件通过 var lib = window.__hornetLib__ 访问 (function() { console.log('===== 3DTank公共库加载 ====='); if (!window.__gameMode__) { console.warn('[公共库] window.__gameMode__ 暂未捕获,工具启动时将失败。hook_gamemode.js 已安装 Hook,战斗中会自动捕获'); } // ===== 缓存队伍判断函数(ZCt/m2Fn) ===== var ZCt = null, m2Fn = null; // 加载队伍判断函数(从 module_87072 导出) // ZCt (mod.$_$.kd): 从坦克对象获取队伍ID // m2 (mod.$_$.rk): 判断两个队伍ID是否相同且非中立 = t.equals(n) && !t.equals(E$()) function LoadTeamFunctions() { if (ZCt && m2Fn) return { ZCt: ZCt, m2Fn: m2Fn }; try { var mod87072 = window.__wp_require__(87072); if (mod87072 && mod87072.$_$) { ZCt = mod87072.$_$.kd; m2Fn = mod87072.$_$.rk; console.log('[公共库] 队伍判断函数已加载: ZCt=' + (typeof ZCt) + ', m2=' + (typeof m2Fn)); } } catch(e) { console.warn('[公共库] 加载队伍判断函数失败:', e.message); } return { ZCt: ZCt, m2Fn: m2Fn }; } // ===== 从 entity 获取包含指定字段的组件 ===== // 遍历 entity.wyj_1.r2a_1(Map<组件标识, 组件列表>),value 是列表类(s_),g1(0) 获取实际组件 // 注意:用 !== undefined 检查字段存在性,避免 falsy 值(0/""/null/false)漏判 function FindComponentByField(entity, fieldName) { if (!entity || !entity.qz1_1 || !entity.qz1_1.s2a_1) return null; var result = null; entity.qz1_1.s2a_1.forEach(function(valueList, key) { if (result) return; try { var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList; if (comp && comp[fieldName] !== undefined) result = comp; } catch(e) {} }); return result; } // ===== 获取坦克位置(物理组件里的位置向量) ===== // 物理组件的字段名每个构建都可能平移,这里按"含 f20_1/g20_1/h20_1 的 O$ 向量"做多级探测: // ejt(tankPhysics)里可能的候选字段都试一遍,命中即用。 // 返回 {x, y, z},x/y 水平面,z 高度 var POS_FIELDS = ['xzq_1', 'uzq_1', 'azr_1', 'bzr_1', 'dzr_1', 'izr_1', 'jzr_1', 'rzf_1', 'gzh_1', 'pz7_1']; function GetTankPosition(tank) { for (var i = 0; i < POS_FIELDS.length; i++) { var Key = POS_FIELDS[i]; var Comp = null; try { Comp = FindComponentByField(tank, Key); } catch (e) { Comp = null; } if (Comp && Comp[Key] && typeof Comp[Key].f20_1 === 'number') { return { x: Comp[Key].f20_1, y: Comp[Key].g20_1, z: Comp[Key].h20_1 }; } } // 兜底 1:遍历坦克全部组件,找任意"带 O$ 向量"的组件 var Found = null; try { tank.qz1_1.s2a_1.forEach(function (valueList) { if (Found) return; var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList; if (!comp) return; for (var K in comp) { var V = comp[K]; if (V && typeof V === 'object' && typeof V.f20_1 === 'number' && typeof V.g20_1 === 'number' && typeof V.h20_1 === 'number') { if (Math.abs(V.f20_1) < 1e6 && Math.abs(V.g20_1) < 1e6 && Math.abs(V.h20_1) < 1e6) { Found = { x: V.f20_1, y: V.g20_1, z: V.h20_1 }; return; } } } }); } catch (e) {} if (Found) return Found; // 兜底 2:矩阵式位置 —— 引擎的世界矩阵通常是长度 ≥16 的数组,[12],[13],[14] 是平移分量 try { tank.qz1_1.s2a_1.forEach(function (valueList) { if (Found) return; var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList; if (!comp) return; for (var K in comp) { var M = comp[K]; if (M && typeof M === 'object' && typeof M.length === 'number' && M.length >= 15) { var X = M[12], Y = M[13], Z = M[14]; if (typeof X === 'number' && typeof Y === 'number' && typeof Z === 'number' && Math.abs(X) < 1e6 && Math.abs(Y) < 1e6 && Math.abs(Z) < 1e6 && (X !== 0 || Y !== 0 || Z !== 0)) { Found = { x: X, y: Y, z: Z }; return; } } } }); } catch (e) {} return Found; } // ===== 获取坦克状态值(Yjt 组件的 vzr_1.k3_1) ===== // 状态枚举:0=DEAD, 1=DEAD_PHANTOM, 2=SEMI_ACTIVE, 3=ACTIVE, 4=DISABLED // Yjt 特征:有 azs_1(状态机)和 vzr_1(状态枚举)字段 function GetTankState(tank) { if (!tank || !tank.qz1_1 || !tank.qz1_1.s2a_1) return -1; var tankComp = null; tank.qz1_1.s2a_1.forEach(function(valueList, key) { if (tankComp) return; try { var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList; if (comp && comp.p10c_1 && comp.k10c_1 !== undefined) tankComp = comp; } catch(e) {} }); if (tankComp && tankComp.k10c_1) return tankComp.k10c_1.k3_1; return -1; } // 可见/战斗中的坦克 = ACTIVE(3) 或 SEMI_ACTIVE(2) // 依据:引擎用 tankComponent.s14j()(= SEMI_ACTIVE || ACTIVE)决定该坦克节点是否渲染, // 只认 ACTIVE 会漏掉半活跃状态(出生/过渡)的坦克 function IsTankAlive(tank) { var s = GetTankState(tank); return s === 2 || s === 3; } // ===== 获取坦克名称(yot 用户信息组件) ===== // s120_1 = userName, u120_1 = clanTag(可为null), t120_1 = userRank function GetTankName(tank) { var userComp = FindComponentByField(tank, 'c12n_1'); if (!userComp) return null; var name = userComp.c12n_1 || ''; var clanTag = userComp.e12n_1 || ''; return clanTag ? (clanTag + ' ' + name) : name; } // ===== 获取坦克血量(Vkt 血量组件) ===== // f122_1 = 最大血量(max), g122_1 = 当前血量(current, 死亡时=0) function GetTankHealth(tank) { var healthComp = FindComponentByField(tank, 'q12o_1'); if (!healthComp) return null; var max = healthComp.p12o_1 || 0; var cur = healthComp.q12o_1 || 0; if (max <= 0) return null; 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.m10b_1) return BotSet; var playerTank = GetPlayerTank(gm); var list, n; try { list = gm.m10b_1.j11y(); 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以在死亡状态控制) function GetPlayerTank(gm, checkPosition) { if (checkPosition === undefined) checkPosition = true; var t = gm.t10b_1; if (t && (!checkPosition || GetTankPosition(t))) return t; t = gm.u10b_1; if (t && (!checkPosition || GetTankPosition(t))) return t; try { var tl = gm.m10b_1.j11y(); if (tl.e1() > 0) { t = tl.g1(0); if (t && (!checkPosition || GetTankPosition(t))) return t; } } catch(e) {} return null; } // ===== 判断是否友方 ===== // 策略1(优先):ZCt 获取双方队伍ID,m2 比较(同队且非中立) // 策略2(备用):比较 gm.gzr_1(本地队伍ID)与坦克队伍ID function IsFriendly(tank, playerTank, gm) { if (!tank || !playerTank) return false; if (ZCt && m2Fn) { try { var tankTeam = ZCt(tank); var playerTeam = ZCt(playerTank); if (tankTeam && playerTeam) return m2Fn(tankTeam, playerTeam); } catch(e) {} } try { if (gm && gm.v10b_1 && ZCt) { var team = ZCt(tank); if (team && gm.v10b_1) { return team.equals(gm.v10b_1) && m2Fn ? m2Fn(team, gm.v10b_1) : team.equals(gm.v10b_1); } } } catch(e) {} return false; } // ===== 获取相机信息($r/gq 相机组件) ===== // 遍历 GameMode entity 和所有坦克 entity 查找活跃相机组件 // 策略1:查找 $r/gq 相机组件(有 kym_1 位置 和 lym_1 方向) // 策略2:查找 k7 相机控制器(有 qz9_1 和 jzh_1),从 jzh_1 获取相机组件 // 返回 {yaw, pitch, fov, pos:{x,y,z}} // 注意:lym_1.h20_1 是 roll(绕Z),lym_1.f20_1 是 pitch(绕X) function GetCameraInfo(gm) { var entities = []; try { var gameScene = gm.lz4().jz1_1; var gmEntity = gm.lz4(); if (gmEntity) entities.push(gmEntity); } catch(e) {} try { var tankList = gm.m10b_1.j11y(); var tCount = tankList.e1(); for (var i = 0; i < tCount; i++) { var t = tankList.g1(i); if (t) entities.push(t); } } catch(e) {} for (var i = 0; i < entities.length; i++) { var entity = entities[i]; if (!entity || !entity.qz1_1 || !entity.qz1_1.s2a_1) continue; var found = null; entity.qz1_1.s2a_1.forEach(function(valueList, key) { if (found) return; try { var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList; if (!comp) return; // 策略1:$r/gq 相机组件 if (comp.cyw_1 && comp.dyw_1 && comp.cyw_1.f20_1 !== undefined && comp.dyw_1.f20_1 !== undefined) { if (Math.abs(comp.dyw_1.h20_1) > 0.001 || Math.abs(comp.dyw_1.f20_1) > 0.001) { found = comp; return; } } // 策略2:k7 相机控制器 if (comp.jzj_1 && comp.zzi_1) { var camComp = comp.zzi_1; if (camComp && camComp.cyw_1 && camComp.dyw_1 && (Math.abs(camComp.dyw_1.h20_1) > 0.001 || Math.abs(camComp.dyw_1.f20_1) > 0.001)) { found = camComp; return; } } } catch(e) {} }); if (found) { return { yaw: found.dyw_1.h20_1, pitch: found.dyw_1.f20_1, fov: found.byw_1 || 1.0471976, pos: { x: found.cyw_1.f20_1, y: found.cyw_1.g20_1, z: found.cyw_1.h20_1 } }; } } return null; } // ===== 获取全局碰撞检测器(uMt 组件,挂在 GameMode entity 上) ===== // g16x(startVec, dirVec, maxDist, resultObj, filterObj) = 全局射线检测 function GetUMtComponent(gm) { try { var gmEntity = gm.lz4(); if (!gmEntity || !gmEntity.qz1_1 || !gmEntity.qz1_1.s2a_1) return null; var uMt = null; gmEntity.qz1_1.s2a_1.forEach(function(valueList, key) { if (uMt) return; try { var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList; if (comp && typeof comp.a17p === 'function') uMt = comp; } catch(e) {} }); return uMt; } catch(e) { return null; } } // ===== 构造 mock O$ 向量(用于射线检测接口) ===== function MkVec(x, y, z) { return { f20_1: x || 0, g20_1: y || 0, h20_1: z || 0, k26: function(t) { this.f20_1 = t.f20_1; this.g20_1 = t.g20_1; this.h20_1 = t.h20_1; }, n23: function(x, y, z) { this.f20_1 = x; this.g20_1 = y; this.h20_1 = z; return this; } }; } // ===== 射线检测:返回命中距离(-1=未命中/异常) ===== // 从 fromPos 沿 (dirX,dirY,dirZ) 方向,最大距离 maxDist function RaycastDistance(gm, fromPos, dirX, dirY, dirZ, maxDist) { var uMt = GetUMtComponent(gm); if (!uMt) return -1; try { var startVec = MkVec(fromPos.x, fromPos.y, fromPos.z); var dirVec = MkVec(dirX, dirY, dirZ); var result = { y178_1: MkVec(0, 0, 0), z178_1: MkVec(0, 0, 0), a179_1: maxDist, q17o_1: null, b179_1: null, r17o: function(t, n, i, r, e) { this.y178_1.k26(t); this.z178_1.k26(n); this.a179_1 = i; this.q17o_1 = r; this.b179_1 = e; }, d3: function() { this.b179_1 = null; } }; var filter = { l1da_1: { h1: function() { return false; }, d3: function() {} }, w179: function() { return true; }, d3: function() { this.l1da_1.d3(); }, m1da: function() {} }; var hit = uMt.a17p(startVec, dirVec, maxDist, result, filter); return (hit && result.a179_1 < maxDist) ? result.a179_1 : -1; } catch(e) { return -1; } } // ===== 3D 到 2D 投影(用于 UI 标记) ===== // 标准透视投影:forward/right/up 三轴正交基 function Project3D(worldPos, camPos, yaw, pitch, fov, screenW, screenH) { var cp = Math.cos(pitch); var sp = Math.sin(pitch); var cy = Math.cos(yaw); var sy = Math.sin(yaw); var forward = { x: cp * (-sy), y: cp * cy, z: sp }; var right = { x: cy, y: sy, z: 0 }; var up = { x: right.y * forward.z - right.z * forward.y, y: right.z * forward.x - right.x * forward.z, z: right.x * forward.y - right.y * forward.x }; var relX = worldPos.x - camPos.x; var relY = worldPos.y - camPos.y; var relZ = worldPos.z - camPos.z; var camX = relX * right.x + relY * right.y + relZ * right.z; var camY = relX * up.x + relY * up.y + relZ * up.z; var camZ = relX * forward.x + relY * forward.y + relZ * forward.z; if (camZ <= 0.1) return null; var focalLength = screenH / (2 * Math.tan(fov / 2)); var screenX = screenW / 2 + (camX / camZ) * focalLength; var screenY = screenH / 2 - (camY / camZ) * focalLength; return { x: screenX, y: screenY, depth: camZ }; } // ===== 用世界变换矩阵变换顶点(列主序4x4矩阵) ===== function TransformVertex(matrix, x, y, z) { return { x: matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12], y: matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13], z: matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14] }; } // ===== 让元素可拖动(左键拖动,改变 left/top) ===== // 适用于固定定位的小窗口(雷达、后视镜等) // key: 可选,传入后会把窗口位置存到 localStorage(hornet_layout_)并在创建时还原 // 实现要点:Pointer Events + setPointerCapture,保证 pointerup/pointercancel 必定回到元素, // 并在 window 捕获阶段兜底,避免游戏画布抢事件后"松手仍粘着鼠标"。 function MakeDraggable(elem, key) { if (!elem) return null; elem.style.cursor = 'move'; var StorageKey = key ? ('hornet_layout_' + key) : null; function ClampX(v) { return Math.max(0, Math.min(window.innerWidth - 50, v)); } function ClampY(v) { return Math.max(0, Math.min(window.innerHeight - 30, v)); } // 还原上次保存的位置 if (StorageKey) { try { var __saved = localStorage.getItem(StorageKey); if (__saved) { var __p = JSON.parse(__saved); if (__p && typeof __p.left === 'number' && typeof __p.top === 'number') { elem.style.right = 'auto'; elem.style.transform = 'none'; elem.style.left = ClampX(__p.left) + 'px'; elem.style.top = ClampY(__p.top) + 'px'; } } } catch(e) {} } function SavePos() { if (!StorageKey) return; try { var r = elem.getBoundingClientRect(); localStorage.setItem(StorageKey, JSON.stringify({ left: Math.round(r.left), top: Math.round(r.top) })); } catch(e) {} } var Active = false; var PointerId = null; var GrabX = 0; var GrabY = 0; function MoveTo(ClientX, ClientY) { elem.style.left = ClampX(ClientX - GrabX) + 'px'; elem.style.top = ClampY(ClientY - GrabY) + 'px'; } function Begin(ClientX, ClientY) { var Rect = elem.getBoundingClientRect(); // 移除 right 定位,改用 left elem.style.right = 'auto'; elem.style.transform = 'none'; elem.style.left = Rect.left + 'px'; elem.style.top = Rect.top + 'px'; GrabX = ClientX - Rect.left; GrabY = ClientY - Rect.top; Active = true; elem.style.cursor = 'grabbing'; } function End() { if (!Active) return; Active = false; if (PointerId !== null && elem.releasePointerCapture) { try { elem.releasePointerCapture(PointerId); } catch (Err) {} } PointerId = null; elem.style.cursor = 'move'; SavePos(); } if (window.PointerEvent) { elem.addEventListener('pointerdown', function (e) { if (e.button !== 0) return; Begin(e.clientX, e.clientY); PointerId = e.pointerId; try { elem.setPointerCapture(e.pointerId); } catch (Err) {} e.preventDefault(); }); elem.addEventListener('pointermove', function (e) { if (!Active || (PointerId !== null && e.pointerId !== PointerId)) return; if (e.buttons === 0) { End(); return; } MoveTo(e.clientX, e.clientY); }); elem.addEventListener('pointerup', function () { End(); }); elem.addEventListener('pointercancel', function () { End(); }); elem.addEventListener('lostpointercapture', function () { End(); }); window.addEventListener('pointerup', function () { End(); }, true); } else { elem.addEventListener('mousedown', function (e) { if (e.button !== 0) return; Begin(e.clientX, e.clientY); e.preventDefault(); }); document.addEventListener('mousemove', function (e) { if (!Active) return; if (e.buttons === 0) { End(); return; } MoveTo(e.clientX, e.clientY); }, true); document.addEventListener('mouseup', function () { End(); }, true); } window.addEventListener('blur', End); window.addEventListener('mouseleave', End); return { End: End, IsActive: function () { return Active; } }; } // ===== 导出公共函数到 window.__hornetLib__ ===== window.__hornetLib__ = { LoadTeamFunctions: LoadTeamFunctions, FindComponentByField: FindComponentByField, GetTankPosition: GetTankPosition, GetTankState: GetTankState, IsTankAlive: IsTankAlive, GetTankName: GetTankName, GetTankHealth: GetTankHealth, GetGearScore: GetGearScore, ScanBots: ScanBots, GetBotSet: GetBotSet, IsBot: IsBot, GetPlayerTank: GetPlayerTank, IsFriendly: IsFriendly, GetCameraInfo: GetCameraInfo, GetUMtComponent: GetUMtComponent, MkVec: MkVec, RaycastDistance: RaycastDistance, Project3D: Project3D, TransformVertex: TransformVertex, MakeDraggable: MakeDraggable }; console.log('[公共库] 已加载,可用函数:', Object.keys(window.__hornetLib__).join(', ')); })();