- 根因:物理组件的位置向量字段每构建都平移,本次 pz7_1→gzh_1→rzf_1 的映射全都指错(gzh_1 是 TerminatorCC、rzf_1 是 RicochetSfxData) - GetTankPosition 改为多级探测:先试一组成员字段(xzq_1/uzq_1/azr_1/bzr_1/dzr_1/izr_1/jzr_1…),再兜底遍历组件找带 f20_1/g20_1/h20_1 的 O\$ 向量 - 透视的坦克绘制与金箱位置都改用 GetTankPosition
518 lines
20 KiB
JavaScript
518 lines
20 KiB
JavaScript
// ===== 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 };
|
||
}
|
||
}
|
||
// 兜底:遍历坦克全部组件,找任意"带 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) {}
|
||
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_<key>)并在创建时还原
|
||
// 实现要点: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(', '));
|
||
})();
|