feat(3dtank): 本地镜像与 hornet 工具链迁到新版 bundle main.6469b966.js

旧 main.b7114b43.js 已被新构建替换(10,011,039 字节 / 1335 模块),本地镜像整体重切到新版并清掉旧产物;hornet 工具链按 tools/map_symbols.js 生成的新旧符号映射改名后重新打包(3dtank_bundle.js 与 unpacked/hornet_bundle.js 同步)。tools/symbol_map.json(8.9MB 映射产物)与切割解包目录不进库,可用 map_symbols.js / split.js 重新生成。
This commit is contained in:
TermiNexus committed 2026-09-17 19:59:40 +08:00
1 parent fd32881c3d
commit 90c377ad79
103 files changed
+400753 -399528

No files matched your search

+5 -5
View File
@@ -47,7 +47,7 @@
// 尝试获取坦克列表(失败也不影响 gameMode 设置)
var tankCount = -1;
try {
var list = gm.xzq_1.z11b();
var list = gm.q100_1.r11l();
tankCount = list.e1();
window.__tankList__ = list;
} catch(e) {
@@ -103,7 +103,7 @@
if (window.__gameMode__) {
// 验证当前引用是否有效
try {
var list = window.__gameMode__.xzq_1.z11b();
var list = window.__gameMode__.q100_1.r11l();
window.__tankList__ = list;
} catch(e) {
// 引用失效(新战斗/退出战斗),重置等待重新捕获
@@ -142,7 +142,7 @@
console.log('ght 实例未捕获,请等待');
return;
}
var list = window.__gameMode__.xzq_1.z11b();
var list = window.__gameMode__.q100_1.r11l();
var count = list.e1();
console.log('=== 坦克列表 (' + count + " 个) ===");
for (var i = 0; i < count; i++) {
@@ -157,7 +157,7 @@
console.log('ght 实例未捕获');
return;
}
var list = window.__gameMode__.xzq_1.z11b();
var list = window.__gameMode__.q100_1.r11l();
var tank = list.g1(idx || 0);
console.log('坦克[' + (idx || 0) + ']:', tank);
console.log('字段:', Object.keys(tank));
@@ -181,7 +181,7 @@
window.__refreshGameMode__ = function() {
if (window.__gameMode__) {
try {
var list = window.__gameMode__.xzq_1.z11b();
var list = window.__gameMode__.q100_1.r11l();
console.log('%c[刷新] ght 引用有效,坦克数:' + list.e1(), 'color:#0f0;font-weight:bold');
return window.__gameMode__;
} catch(e) {
+50 -50
View File
@@ -126,7 +126,7 @@
}
function GetTDtComponent(entity) {
var comp = FindComponentByField(entity, 'u112_1');
var comp = FindComponentByField(entity, 'n11c_1');
if (comp) return comp;
comp = FindComponentByField(entity, 'q112_1');
return comp;
@@ -134,14 +134,14 @@
// ===== 从GPU读取顶点位置数据(缓冲区[0]) =====
function ReadVertexPositionsFromTp(tp) {
if (!tp || !tp.h4w_1) return null;
var tl = tp.h4w_1.g1(0);
if (!tl || !tl.w3w_1) return null;
var vq = tl.w3w_1;
var bs = vq.l3h_1;
if (!bs || !bs.p73_1) return null;
var gl = bs.p73_1;
var webglBuf = vq.k3x_1 && vq.k3x_1.n73_1;
if (!tp || !tp.t4w_1) return null;
var tl = tp.t4w_1.g1(0);
if (!tl || !tl.d3x_1) return null;
var vq = tl.d3x_1;
var bs = vq.s3h_1;
if (!bs || !bs.b74_1) return null;
var gl = bs.b74_1;
var webglBuf = vq.r3x_1 && vq.r3x_1.z73_1;
if (!webglBuf) return null;
gl.bindBuffer(gl.ARRAY_BUFFER, webglBuf);
var bufSize = gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE);
@@ -153,18 +153,18 @@
// ===== 从GPU读取索引数据 =====
function ReadIndicesFromTp(tp) {
if (!tp.m4w_1 || typeof tp.m4w_1.e1 !== 'function') return null;
var meshCount = tp.m4w_1.e1();
if (!tp.y4w_1 || typeof tp.y4w_1.e1 !== 'function') return null;
var meshCount = tp.y4w_1.e1();
if (meshCount === 0) return null;
var allIndices = [];
for (var m = 0; m < meshCount; m++) {
var il = tp.m4w_1.g1(m);
if (!il || !il.j3p_1) continue;
var fq = il.j3p_1;
var bs = fq.l3h_1;
if (!bs || !bs.p73_1) continue;
var gl = bs.p73_1;
var webglBuf = fq.c3x_1 && fq.c3x_1.n73_1;
var il = tp.y4w_1.g1(m);
if (!il || !il.q3p_1) continue;
var fq = il.q3p_1;
var bs = fq.s3h_1;
if (!bs || !bs.b74_1) continue;
var gl = bs.b74_1;
var webglBuf = fq.j3x_1 && fq.j3x_1.z73_1;
if (!webglBuf) continue;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webglBuf);
var bufSize = gl.getBufferParameter(gl.ELEMENT_ARRAY_BUFFER, gl.BUFFER_SIZE);
@@ -224,12 +224,12 @@
var GhostCooldown = 120;
function IsNodeGhost(bpNode, tankIsMoving) {
if (!bpNode || !bpNode.g35_1) return false;
if (!bpNode || !bpNode.n35_1) return false;
// 方案1:z34_1 字段检测(主方案)
var z34Val = bpNode.z34_1;
var z34Val = bpNode.g35_1;
if (z34Val !== undefined && z34Val < 0.9) return true;
// 方案2:世界矩阵更新检测(后备方案)
var curMatrix = bpNode.g35_1;
var curMatrix = bpNode.n35_1;
var hist = NodeMatrixHistory.get(bpNode);
if (!hist) {
hist = { m: new Array(16), unchanged: 0, cooldown: 0 };
@@ -257,23 +257,23 @@
// ===== 递归收集mesh节点(每帧重新获取 Bp 节点引用) =====
function CollectMeshNodes(bpNode, nodes, depth, group) {
if (!bpNode || depth > 5) return;
var name = bpNode.x34_1 || '';
if (bpNode.m3m_1) {
var gpuData = GetGpuData(bpNode.m3m_1);
var name = bpNode.e35_1 || '';
if (bpNode.t3m_1) {
var gpuData = GetGpuData(bpNode.t3m_1);
if (gpuData) {
nodes.push({
name: name, node: bpNode, tp: bpNode.m3m_1,
name: name, node: bpNode, tp: bpNode.t3m_1,
positions: gpuData.positions, indices: gpuData.indices, stride: gpuData.stride, group: group
});
}
}
if (bpNode.e4x_1 && typeof bpNode.e4x_1.e1 === 'function') {
if (bpNode.q4x_1 && typeof bpNode.q4x_1.e1 === 'function') {
try {
var lodCount = bpNode.e4x_1.e1();
var lodCount = bpNode.q4x_1.e1();
for (var lod = 0; lod < lodCount && lod < 1; lod++) {
var lodLevel = bpNode.e4x_1.g1(lod);
var lodLevel = bpNode.q4x_1.g1(lod);
if (!lodLevel) continue;
var lodTp = lodLevel.m3m_1 || lodLevel.o3m_1;
var lodTp = lodLevel.t3m_1 || lodLevel.v3m_1;
if (!lodTp) continue;
var lodGpuData = GetGpuData(lodTp);
if (lodGpuData) {
@@ -285,11 +285,11 @@
}
} catch(e) {}
}
if (bpNode.i35_1 && typeof bpNode.i35_1.e1 === 'function') {
var childCount = bpNode.i35_1.e1();
if (bpNode.p35_1 && typeof bpNode.p35_1.e1 === 'function') {
var childCount = bpNode.p35_1.e1();
for (var c = 0; c < childCount; c++) {
try {
var child = bpNode.i35_1.g1(c);
var child = bpNode.p35_1.g1(c);
if (child) CollectMeshNodes(child, nodes, depth + 1, group);
} catch(e) {}
}
@@ -300,16 +300,16 @@
function GetTankMeshData(tank) {
var nodes = [];
var kmt = GetKMtComponent(tank);
if (kmt && kmt.t14l_1 && kmt.t14l_1.p16z_1) {
if (kmt && kmt.t14l_1 && kmt.t14l_1.s17b_1) {
var skin = kmt.t14l_1;
try {
var partCount = skin.p16z_1.e1();
var partCount = skin.s17b_1.e1();
for (var p = 0; p < partCount; p++) {
var part = skin.p16z_1.g1(p);
if (!part || !part.s16z_1) continue;
var nodeCount = part.s16z_1.e1();
var part = skin.s17b_1.g1(p);
if (!part || !part.v17b_1) continue;
var nodeCount = part.v17b_1.e1();
for (var n = 0; n < nodeCount; n++) {
var bpNode = part.s16z_1.g1(n);
var bpNode = part.v17b_1.g1(n);
if (!bpNode) continue;
CollectMeshNodes(bpNode, nodes, 0, 'hull');
}
@@ -317,21 +317,21 @@
} catch(e) {}
}
var tdt = GetTDtComponent(tank);
if (tdt && tdt.u112_1 && tdt.u112_1.f16y_1) {
try { CollectMeshNodes(tdt.u112_1.f16y_1, nodes, 0, 'turret'); } catch(e) {}
if (tdt && tdt.n11c_1 && tdt.n11c_1.m17a_1) {
try { CollectMeshNodes(tdt.n11c_1.m17a_1, nodes, 0, 'turret'); } catch(e) {}
}
return nodes;
}
// ===== 检查节点是否有效(幽灵检测 + 位置检查) =====
function IsNodeValid(bpNode, pos, tankIsMoving, diagStats) {
var worldMatrix = bpNode.g35_1;
var worldMatrix = bpNode.n35_1;
if (!worldMatrix) return null;
if (diagStats) diagStats.totalNodes++;
// 幽灵检测1:z34_1 字段
var z34Val = bpNode.z34_1;
var z34Val = bpNode.g35_1;
if (z34Val !== undefined && z34Val < 0.9) {
if (diagStats) diagStats.ghostNodes++;
return null;
@@ -681,7 +681,7 @@
if (pPos) camInfo.pos = { x: pPos.x, y: pPos.y, z: pPos.z + 100 };
}
var tankList = gm.xzq_1.z11b();
var tankList = gm.q100_1.r11l();
var count = tankList.e1();
var drawnCount = 0;
@@ -689,19 +689,19 @@
var tank = tankList.g1(i);
if (!tank || tank === playerTank) continue;
var physicsComp = FindComponentByField(tank, 'pz7_1');
if (!physicsComp || !physicsComp.pz7_1) continue;
var physicsComp = FindComponentByField(tank, 'gzh_1');
if (!physicsComp || !physicsComp.gzh_1) continue;
var tankState = GetTankState(tank);
if (tankState !== 3) continue;
var pos = {
x: physicsComp.pz7_1.f20_1,
y: physicsComp.pz7_1.g20_1,
z: physicsComp.pz7_1.h20_1
x: physicsComp.gzh_1.f20_1,
y: physicsComp.gzh_1.g20_1,
z: physicsComp.gzh_1.h20_1
};
// 坦克移动检测
var tankId = tank.oyj_1;
var tankId = tank.hyt_1;
var prevPos = TankPrevPos.get(tankId);
var tankIsMoving = false;
if (prevPos) {
@@ -718,7 +718,7 @@
if (distance > 50000) continue;
var friendly = IsFriendly(tank, playerTank);
var tankName = GetTankName(tank) || ('ID:' + tank.oyj_1);
var tankName = GetTankName(tank) || ('ID:' + tank.hyt_1);
// 根据 HighlightMode 判断是否渲染该坦克
if (!ShouldRender(tankName)) continue;
// 颜色按队伍:友方=蓝,敌方=红
+30 -30
View File
@@ -75,7 +75,7 @@
// ===== 获取 TDt 武器皮肤组件(炮塔) =====
function GetTDtComponent(entity) {
var comp = FindComponentByField(entity, 'u112_1');
var comp = FindComponentByField(entity, 'n11c_1');
if (comp) return comp;
comp = FindComponentByField(entity, 'q112_1');
return comp;
@@ -90,9 +90,9 @@
// 返回 { angle: 炮塔朝向与"敌方→玩家"方向的夹角, distance: 距离, turretAngle: 炮塔朝向角度, turretDir: {x,y} }
function GetTurretAimAngle(tank, enemyPos, playerPos) {
var tdt = GetTDtComponent(tank);
if (!tdt || !tdt.u112_1 || !tdt.u112_1.f16y_1) return null;
var bpNode = tdt.u112_1.f16y_1;
var m = bpNode.g35_1;
if (!tdt || !tdt.n11c_1 || !tdt.n11c_1.m17a_1) return null;
var bpNode = tdt.n11c_1.m17a_1;
var m = bpNode.n35_1;
if (!m) return null;
// 敌方→玩家方向(x,y 水平面)
@@ -132,13 +132,13 @@
function GetUMtComponent() {
if (UMtComponent) return UMtComponent;
try {
var gmEntity = gm.rym();
if (!gmEntity || !gmEntity.wyj_1 || !gmEntity.wyj_1.r2a_1) return null;
gmEntity.wyj_1.r2a_1.forEach(function(valueList, key) {
var gmEntity = gm.jyw();
if (!gmEntity || !gmEntity.pyt_1 || !gmEntity.pyt_1.s2a_1) return null;
gmEntity.pyt_1.s2a_1.forEach(function(valueList, key) {
if (UMtComponent) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (comp && typeof comp.g16x === 'function') UMtComponent = comp;
if (comp && typeof comp.m179 === 'function') UMtComponent = comp;
} catch(e) {}
});
if (UMtComponent) console.log('[瞄准] uMt 碰撞检测器已获取');
@@ -180,30 +180,30 @@
var maxDist = dist - 300;
var result = {
s16w_1: MkVec(0,0,0),
t16w_1: MkVec(0,0,0),
u16w_1: maxDist,
w16w_1: null,
v16w_1: null,
x16w: function(t, n, i, r, e) {
this.s16w_1.j26(t);
this.t16w_1.j26(n);
this.u16w_1 = i;
this.w16w_1 = r;
this.v16w_1 = e;
y178_1: MkVec(0,0,0),
z178_1: MkVec(0,0,0),
a179_1: maxDist,
c179_1: null,
b179_1: null,
d179: function(t, n, i, r, e) {
this.y178_1.k26(t);
this.z178_1.k26(n);
this.a179_1 = i;
this.c179_1 = r;
this.b179_1 = e;
},
d3: function() { this.v16w_1 = null; }
d3: function() { this.b179_1 = null; }
};
var filter = {
w1cg_1: { h1: function() { return false; }, d3: function() {} },
q16x: function() { return true; },
d3: function() { this.w1cg_1.d3(); },
x1cg: function() {}
o1co_1: { h1: function() { return false; }, d3: function() {} },
w179: function() { return true; },
d3: function() { this.o1co_1.d3(); },
p1co: function() {}
};
var hit = uMt.g16x(startVec, dirVec, maxDist, result, filter);
blocked = !!hit && result.u16w_1 < (maxDist - 50);
var hit = uMt.m179(startVec, dirVec, maxDist, result, filter);
blocked = !!hit && result.a179_1 < (maxDist - 50);
RaycastFailCount = 0;
} catch(e) {
RaycastFailCount++;
@@ -271,7 +271,7 @@
camInfo.pos = { x: playerPos.x, y: playerPos.y, z: playerPos.z + 100 };
}
var tankList = gm.xzq_1.z11b();
var tankList = gm.q100_1.r11l();
var count = tankList.e1();
var threats = [];
@@ -296,19 +296,19 @@
if (aimInfo.angle >= aimThreshold) continue;
// 墙体阻隔判断
var blocked = IsLineOfSightBlocked(pos, playerPos, tank.oyj_1, frameCount);
var blocked = IsLineOfSightBlocked(pos, playerPos, tank.hyt_1, frameCount);
if (blocked) continue;
// 记录威胁
var proj = Project3D(pos, camInfo.pos, camInfo.yaw, camInfo.pitch, camInfo.fov, screenW, screenH);
var tankName = GetTankName(tank) || ('ID:' + tank.oyj_1);
var tankName = GetTankName(tank) || ('ID:' + tank.hyt_1);
threats.push({
pos: pos,
distance: aimInfo.distance,
angle: aimInfo.angle,
turretDir: aimInfo.turretDir,
screenProj: proj,
tankId: tank.oyj_1,
tankId: tank.hyt_1,
tankName: tankName
});
}
+29 -29
View File
@@ -33,15 +33,15 @@
var initError = null;
try {
scene = gm.rym().pyj_1;
mainCamera = scene.nyf_1;
ff = scene.lye_1.r74_1;
if (!mainCamera || !mainCamera.l35_1 || !mainCamera.k35_1 || !mainCamera.h37) {
scene = gm.jyw().iyt_1;
mainCamera = scene.hyp_1;
ff = scene.fyo_1.d75_1;
if (!mainCamera || !mainCamera.s35_1 || !mainCamera.r35_1 || !mainCamera.o37) {
throw new Error('相机字段缺失');
}
console.log('[自由视角] 主相机:', mainCamera.constructor.name,
'l35=(' + mainCamera.l35_1.f20_1.toFixed(2) + ',' + mainCamera.l35_1.g20_1.toFixed(2) + ',' + mainCamera.l35_1.h20_1.toFixed(2) + ')',
'k35=(' + mainCamera.k35_1.f20_1.toFixed(0) + ',' + mainCamera.k35_1.g20_1.toFixed(0) + ',' + mainCamera.k35_1.h20_1.toFixed(0) + ')');
'l35=(' + mainCamera.s35_1.f20_1.toFixed(2) + ',' + mainCamera.s35_1.g20_1.toFixed(2) + ',' + mainCamera.s35_1.h20_1.toFixed(2) + ')',
'k35=(' + mainCamera.r35_1.f20_1.toFixed(0) + ',' + mainCamera.r35_1.g20_1.toFixed(0) + ',' + mainCamera.r35_1.h20_1.toFixed(0) + ')');
} catch(e) {
initError = e;
console.error('[自由视角] 初始化失败:', e.message);
@@ -63,16 +63,16 @@
var mouseSensitivity = 0.005; // 鼠标灵敏度
// ===== Hook mainCamera.h37 =====
var hadOwnH37 = mainCamera.hasOwnProperty('h37');
var origH37 = mainCamera.h37;
var hadOwnH37 = mainCamera.hasOwnProperty('o37');
var origH37 = mainCamera.o37;
function applyFreeLookParams() {
mainCamera.l35_1.f20_1 = freePitch; // pitch (f)
mainCamera.l35_1.g20_1 = freeRoll; // roll (g) 保持初始值
mainCamera.l35_1.h20_1 = freeYaw; // yaw (h) 鼠标左右控制
mainCamera.k35_1.f20_1 = freePosX;
mainCamera.k35_1.g20_1 = freePosY;
mainCamera.k35_1.h20_1 = freePosZ;
mainCamera.s35_1.f20_1 = freePitch; // pitch (f)
mainCamera.s35_1.g20_1 = freeRoll; // roll (g) 保持初始值
mainCamera.s35_1.h20_1 = freeYaw; // yaw (h) 鼠标左右控制
mainCamera.r35_1.f20_1 = freePosX;
mainCamera.r35_1.g20_1 = freePosY;
mainCamera.r35_1.h20_1 = freePosZ;
}
// 缓存从 viewMatrix 提取的前方向/右方向(h37 更新后设置)
@@ -84,34 +84,34 @@
var cachedInitialized = false;
var suspended = false; // 后视镜等模块渲染时临时暂停 hook
mainCamera.h37 = function() {
mainCamera.o37 = function() {
if (freeLookActive && !suspended) {
applyFreeLookParams();
origH37.call(this);
// 从更新后的 viewMatrix (u36_1) 提取前方向和右方向(水平分量 x,y)
if (this.u36_1) {
cachedFwdX = -this.u36_1[2]; // forward.x
cachedFwdY = -this.u36_1[6]; // forward.y
cachedRightX = this.u36_1[0]; // right.x
cachedRightY = this.u36_1[4]; // right.y
if (this.b37_1) {
cachedFwdX = -this.b37_1[2]; // forward.x
cachedFwdY = -this.b37_1[6]; // forward.y
cachedRightX = this.b37_1[0]; // right.x
cachedRightY = this.b37_1[4]; // right.y
cachedInitialized = true;
}
} else {
return origH37.call(this);
}
};
console.log('[自由视角] mainCamera.h37 已 Hook');
console.log('[自由视角] mainCamera.o37 已 Hook');
// ===== 开启/关闭自由视角 =====
function enableFreeLook() {
// 从当前相机参数初始化(平滑过渡)
// l35_1: f=pitch, g=roll, h=yaw
freePitch = mainCamera.l35_1.f20_1;
freeRoll = mainCamera.l35_1.g20_1; // roll 保持初始值
freeYaw = mainCamera.l35_1.h20_1; // yaw 鼠标控制
freePosX = mainCamera.k35_1.f20_1;
freePosY = mainCamera.k35_1.g20_1;
freePosZ = mainCamera.k35_1.h20_1;
freePitch = mainCamera.s35_1.f20_1;
freeRoll = mainCamera.s35_1.g20_1; // roll 保持初始值
freeYaw = mainCamera.s35_1.h20_1; // yaw 鼠标控制
freePosX = mainCamera.r35_1.f20_1;
freePosY = mainCamera.r35_1.g20_1;
freePosZ = mainCamera.r35_1.h20_1;
freeLookActive = true;
console.log('[自由视角] 已开启 — WASD移动 | 鼠标旋转 | 空格上升/Shift下降 | 滚轮调速 | Alt+F关闭');
updateHud();
@@ -282,8 +282,8 @@
if (rafId) cancelAnimationFrame(rafId);
disableFreeLook();
// 恢复 h37
if (hadOwnH37) mainCamera.h37 = origH37;
else delete mainCamera.h37;
if (hadOwnH37) mainCamera.o37 = origH37;
else delete mainCamera.o37;
// 移除监听
window.removeEventListener('keydown', onKeyDown, true);
window.removeEventListener('keyup', onKeyUp, true);
+139 -92
View File
@@ -15,14 +15,14 @@
// 加载队伍判断函数(从 module_87072 导出)
// ZCt (mod.$_$.kd): 从坦克对象获取队伍ID
// m2 (mod.$_$.jk): 判断两个队伍ID是否相同且非中立 = t.equals(n) && !t.equals(E$())
// 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.$_$.jk;
m2Fn = mod87072.$_$.rk;
console.log('[公共库] 队伍判断函数已加载: ZCt=' + (typeof ZCt) + ', m2=' + (typeof m2Fn));
}
} catch(e) {
@@ -35,9 +35,9 @@
// 遍历 entity.wyj_1.r2a_1(Map<组件标识, 组件列表>),value 是列表类(s_),g1(0) 获取实际组件
// 注意:用 !== undefined 检查字段存在性,避免 falsy 值(0/""/null/false)漏判
function FindComponentByField(entity, fieldName) {
if (!entity || !entity.wyj_1 || !entity.wyj_1.r2a_1) return null;
if (!entity || !entity.pyt_1 || !entity.pyt_1.s2a_1) return null;
var result = null;
entity.wyj_1.r2a_1.forEach(function(valueList, key) {
entity.pyt_1.s2a_1.forEach(function(valueList, key) {
if (result) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
@@ -50,12 +50,12 @@
// ===== 获取坦克位置(G$t 物理组件的 pz7_1 = O$向量) =====
// 返回 {x, y, z},x/y 水平面,z 高度
function GetTankPosition(tank) {
var physicsComp = FindComponentByField(tank, 'pz7_1');
if (physicsComp && physicsComp.pz7_1) {
var physicsComp = FindComponentByField(tank, 'gzh_1');
if (physicsComp && physicsComp.gzh_1) {
return {
x: physicsComp.pz7_1.f20_1,
y: physicsComp.pz7_1.g20_1,
z: physicsComp.pz7_1.h20_1
x: physicsComp.gzh_1.f20_1,
y: physicsComp.gzh_1.g20_1,
z: physicsComp.gzh_1.h20_1
};
}
return null;
@@ -65,16 +65,16 @@
// 状态枚举:0=DEAD, 1=DEAD_PHANTOM, 2=SEMI_ACTIVE, 3=ACTIVE, 4=DISABLED
// Yjt 特征:有 azs_1(状态机)和 vzr_1(状态枚举)字段
function GetTankState(tank) {
if (!tank || !tank.wyj_1 || !tank.wyj_1.r2a_1) return -1;
if (!tank || !tank.pyt_1 || !tank.pyt_1.s2a_1) return -1;
var tankComp = null;
tank.wyj_1.r2a_1.forEach(function(valueList, key) {
tank.pyt_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.azs_1 && comp.vzr_1 !== undefined) tankComp = comp;
if (comp && comp.t101_1 && comp.o101_1 !== undefined) tankComp = comp;
} catch(e) {}
});
if (tankComp && tankComp.vzr_1) return tankComp.vzr_1.k3_1;
if (tankComp && tankComp.o101_1) return tankComp.o101_1.k3_1;
return -1;
}
@@ -84,20 +84,20 @@
// ===== 获取坦克名称(yot 用户信息组件) =====
// s120_1 = userName, u120_1 = clanTag(可为null), t120_1 = userRank
function GetTankName(tank) {
var userComp = FindComponentByField(tank, 's120_1');
var userComp = FindComponentByField(tank, 'k12a_1');
if (!userComp) return null;
var name = userComp.s120_1 || '';
var clanTag = userComp.u120_1 || '';
var name = userComp.k12a_1 || '';
var clanTag = userComp.m12a_1 || '';
return clanTag ? (clanTag + ' ' + name) : name;
}
// ===== 获取坦克血量(Vkt 血量组件) =====
// f122_1 = 最大血量(max), g122_1 = 当前血量(current, 死亡时=0)
function GetTankHealth(tank) {
var healthComp = FindComponentByField(tank, 'g122_1');
var healthComp = FindComponentByField(tank, 'y12b_1');
if (!healthComp) return null;
var max = healthComp.f122_1 || 0;
var cur = healthComp.g122_1 || 0;
var max = healthComp.x12b_1 || 0;
var cur = healthComp.y12b_1 || 0;
if (max <= 0) return null;
return { current: cur, max: max, percent: cur / max };
}
@@ -107,12 +107,12 @@
// checkPosition: 是否检查位置有效性(默认true;测试脚本传false以在死亡状态控制)
function GetPlayerTank(gm, checkPosition) {
if (checkPosition === undefined) checkPosition = true;
var t = gm.ezr_1;
var t = gm.x100_1;
if (t && (!checkPosition || GetTankPosition(t))) return t;
t = gm.fzr_1;
t = gm.y100_1;
if (t && (!checkPosition || GetTankPosition(t))) return t;
try {
var tl = gm.xzq_1.z11b();
var tl = gm.q100_1.r11l();
if (tl.e1() > 0) {
t = tl.g1(0);
if (t && (!checkPosition || GetTankPosition(t))) return t;
@@ -134,10 +134,10 @@
} catch(e) {}
}
try {
if (gm && gm.gzr_1 && ZCt) {
if (gm && gm.z100_1 && ZCt) {
var team = ZCt(tank);
if (team && gm.gzr_1) {
return team.equals(gm.gzr_1) && m2Fn ? m2Fn(team, gm.gzr_1) : team.equals(gm.gzr_1);
if (team && gm.z100_1) {
return team.equals(gm.z100_1) && m2Fn ? m2Fn(team, gm.z100_1) : team.equals(gm.z100_1);
}
}
} catch(e) {}
@@ -153,12 +153,12 @@
function GetCameraInfo(gm) {
var entities = [];
try {
var gameScene = gm.rym().pyj_1;
var gmEntity = gameScene.eyl("GameMode");
var gameScene = gm.jyw().iyt_1;
var gmEntity = gameScene.xyu("GameMode");
if (gmEntity) entities.push(gmEntity);
} catch(e) {}
try {
var tankList = gm.xzq_1.z11b();
var tankList = gm.q100_1.r11l();
var tCount = tankList.e1();
for (var i = 0; i < tCount; i++) {
var t = tankList.g1(i);
@@ -168,26 +168,26 @@
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
if (!entity || !entity.wyj_1 || !entity.wyj_1.r2a_1) continue;
if (!entity || !entity.pyt_1 || !entity.pyt_1.s2a_1) continue;
var found = null;
entity.wyj_1.r2a_1.forEach(function(valueList, key) {
entity.pyt_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.kym_1 && comp.lym_1 &&
comp.kym_1.f20_1 !== undefined && comp.lym_1.f20_1 !== undefined) {
if (Math.abs(comp.lym_1.h20_1) > 0.001 || Math.abs(comp.lym_1.f20_1) > 0.001) {
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.qz9_1 && comp.gz9_1) {
var camComp = comp.gz9_1;
if (camComp && camComp.kym_1 && camComp.lym_1 &&
(Math.abs(camComp.lym_1.h20_1) > 0.001 || Math.abs(camComp.lym_1.f20_1) > 0.001)) {
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;
}
@@ -196,13 +196,13 @@
});
if (found) {
return {
yaw: found.lym_1.h20_1,
pitch: found.lym_1.f20_1,
fov: found.jym_1 || 1.0471976,
yaw: found.dyw_1.h20_1,
pitch: found.dyw_1.f20_1,
fov: found.byw_1 || 1.0471976,
pos: {
x: found.kym_1.f20_1,
y: found.kym_1.g20_1,
z: found.kym_1.h20_1
x: found.cyw_1.f20_1,
y: found.cyw_1.g20_1,
z: found.cyw_1.h20_1
}
};
}
@@ -214,14 +214,14 @@
// g16x(startVec, dirVec, maxDist, resultObj, filterObj) = 全局射线检测
function GetUMtComponent(gm) {
try {
var gmEntity = gm.rym();
if (!gmEntity || !gmEntity.wyj_1 || !gmEntity.wyj_1.r2a_1) return null;
var gmEntity = gm.jyw();
if (!gmEntity || !gmEntity.pyt_1 || !gmEntity.pyt_1.s2a_1) return null;
var uMt = null;
gmEntity.wyj_1.r2a_1.forEach(function(valueList, key) {
gmEntity.pyt_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.g16x === 'function') uMt = comp;
if (comp && typeof comp.m179 === 'function') uMt = comp;
} catch(e) {}
});
return uMt;
@@ -234,8 +234,8 @@
function MkVec(x, y, z) {
return {
f20_1: x || 0, g20_1: y || 0, h20_1: z || 0,
j26: function(t) { this.f20_1 = t.f20_1; this.g20_1 = t.g20_1; this.h20_1 = t.h20_1; },
o23: function(x, y, z) { this.f20_1 = x; this.g20_1 = y; this.h20_1 = z; return this; }
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; }
};
}
@@ -248,28 +248,28 @@
var startVec = MkVec(fromPos.x, fromPos.y, fromPos.z);
var dirVec = MkVec(dirX, dirY, dirZ);
var result = {
s16w_1: MkVec(0, 0, 0),
t16w_1: MkVec(0, 0, 0),
u16w_1: maxDist,
w16w_1: null,
v16w_1: null,
x16w: function(t, n, i, r, e) {
this.s16w_1.j26(t);
this.t16w_1.j26(n);
this.u16w_1 = i;
this.w16w_1 = r;
this.v16w_1 = e;
y178_1: MkVec(0, 0, 0),
z178_1: MkVec(0, 0, 0),
a179_1: maxDist,
c179_1: null,
b179_1: null,
d179: function(t, n, i, r, e) {
this.y178_1.k26(t);
this.z178_1.k26(n);
this.a179_1 = i;
this.c179_1 = r;
this.b179_1 = e;
},
d3: function() { this.v16w_1 = null; }
d3: function() { this.b179_1 = null; }
};
var filter = {
w1cg_1: { h1: function() { return false; }, d3: function() {} },
q16x: function() { return true; },
d3: function() { this.w1cg_1.d3(); },
x1cg: function() {}
o1co_1: { h1: function() { return false; }, d3: function() {} },
w179: function() { return true; },
d3: function() { this.o1co_1.d3(); },
p1co: function() {}
};
var hit = uMt.g16x(startVec, dirVec, maxDist, result, filter);
return (hit && result.u16w_1 < maxDist) ? result.u16w_1 : -1;
var hit = uMt.m179(startVec, dirVec, maxDist, result, filter);
return (hit && result.a179_1 < maxDist) ? result.a179_1 : -1;
} catch(e) {
return -1;
}
@@ -313,35 +313,82 @@
// ===== 让元素可拖动(左键拖动,改变 left/top) =====
// 适用于固定定位的小窗口(雷达、后视镜等)
// 实现要点:Pointer Events + setPointerCapture,保证 pointerup/pointercancel 必定回到元素,
// 并在 window 捕获阶段兜底,避免游戏画布抢事件后"松手仍粘着鼠标"。
function MakeDraggable(elem) {
if (!elem) return;
if (!elem) return null;
elem.style.cursor = 'move';
var dragging = false;
var startX = 0, startY = 0, elemX = 0, elemY = 0;
elem.addEventListener('mousedown', function(e) {
if (e.button !== 0) return;
dragging = true;
startX = e.clientX;
startY = e.clientY;
var rect = elem.getBoundingClientRect();
elemX = rect.left;
elemY = rect.top;
var Active = false;
var PointerId = null;
var GrabX = 0;
var GrabY = 0;
function MoveTo(ClientX, ClientY) {
var NextX = Math.max(0, Math.min(window.innerWidth - 50, ClientX - GrabX));
var NextY = Math.max(0, Math.min(window.innerHeight - 30, ClientY - GrabY));
elem.style.left = NextX + 'px';
elem.style.top = NextY + 'px';
}
function Begin(ClientX, ClientY) {
var Rect = elem.getBoundingClientRect();
// 移除 right 定位,改用 left
elem.style.right = 'auto';
elem.style.left = elemX + 'px';
elem.style.top = elemY + 'px';
e.preventDefault();
});
document.addEventListener('mousemove', function(e) {
if (!dragging) return;
var nx = elemX + (e.clientX - startX);
var ny = elemY + (e.clientY - startY);
nx = Math.max(0, Math.min(window.innerWidth - 50, nx));
ny = Math.max(0, Math.min(window.innerHeight - 30, ny));
elem.style.left = nx + 'px';
elem.style.top = ny + 'px';
});
document.addEventListener('mouseup', function() { dragging = false; });
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';
}
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__ =====
+99 -33
View File
@@ -16,42 +16,101 @@
// ===== 通用拖动功能 =====
// elem: 要拖动的元素,handle: 触发拖动的元素(默认=elem)
// ignoreTags: 点击这些标签时不触发拖动(如 BUTTON)
// 实现要点:Pointer Events + setPointerCapture 把指针"锁"在 handle 上,
// pointerup/pointercancel 必定回到 handle;并在 window 捕获阶段加兜底,
// 避免面板内 stopPropagation 或游戏画布抢事件导致"松手后仍粘着鼠标"。
// 返回:{ End, IsActive },供隐藏面板时强制结束拖动
function MakeDraggable(elem, handle, ignoreTags) {
handle = handle || elem;
ignoreTags = ignoreTags || ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'];
handle.style.cursor = 'move';
var dragging = false;
var startX = 0, startY = 0, elemX = 0, elemY = 0;
handle.addEventListener('mousedown', function(e) {
if (e.button !== 0) return; // 只响应左键
var tag = e.target.tagName;
if (ignoreTags.indexOf(tag) >= 0) return; // 按钮等不触发拖动
dragging = true;
startX = e.clientX;
startY = e.clientY;
var rect = elem.getBoundingClientRect();
elemX = rect.left;
elemY = rect.top;
// 移除 transform 居中,改用绝对位置
var Active = false;
var PointerId = null;
var GrabX = 0;
var GrabY = 0;
function MoveTo(ClientX, ClientY) {
var MaxX = window.innerWidth - 40;
var MaxY = window.innerHeight - 24;
var NextX = Math.max(0, Math.min(MaxX, ClientX - GrabX));
var NextY = Math.max(0, Math.min(MaxY, ClientY - GrabY));
elem.style.left = NextX + 'px';
elem.style.top = NextY + 'px';
}
function Begin(ClientX, ClientY) {
var Rect = elem.getBoundingClientRect();
// 移除 transform 居中,改用绝对定位,避免拖动起点跳变
elem.style.transform = 'none';
elem.style.left = elemX + 'px';
elem.style.top = elemY + 'px';
e.preventDefault();
});
elem.style.left = Rect.left + 'px';
elem.style.top = Rect.top + 'px';
GrabX = ClientX - Rect.left;
GrabY = ClientY - Rect.top;
Active = true;
handle.style.cursor = 'grabbing';
}
document.addEventListener('mousemove', function(e) {
if (!dragging) return;
var nx = elemX + (e.clientX - startX);
var ny = elemY + (e.clientY - startY);
// 限制在屏幕内
nx = Math.max(0, Math.min(window.innerWidth - 50, nx));
ny = Math.max(0, Math.min(window.innerHeight - 30, ny));
elem.style.left = nx + 'px';
elem.style.top = ny + 'px';
});
function End() {
if (!Active) return;
Active = false;
if (PointerId !== null && handle.releasePointerCapture) {
try { handle.releasePointerCapture(PointerId); } catch (Err) {}
}
PointerId = null;
handle.style.cursor = 'move';
}
document.addEventListener('mouseup', function() { dragging = false; });
function CanStart(Target) {
return !Target || ignoreTags.indexOf(Target.tagName) < 0;
}
if (window.PointerEvent) {
// 捕获阶段监听:先于元素自身的 stopPropagation 执行
handle.addEventListener('pointerdown', function (e) {
if (e.button !== 0) return;
if (!CanStart(e.target)) return;
Begin(e.clientX, e.clientY);
PointerId = e.pointerId;
try { handle.setPointerCapture(e.pointerId); } catch (Err) {}
e.preventDefault();
e.stopPropagation();
}, true);
handle.addEventListener('pointermove', function (e) {
if (!Active || (PointerId !== null && e.pointerId !== PointerId)) return;
// 兜底:按键已经松开(例如在窗口外松手)→ 结束拖动
if (e.buttons === 0) { End(); return; }
MoveTo(e.clientX, e.clientY);
e.preventDefault();
}, true);
handle.addEventListener('pointerup', function () { End(); }, true);
handle.addEventListener('pointercancel', function () { End(); }, true);
handle.addEventListener('lostpointercapture', function () { End(); }, true);
window.addEventListener('pointerup', function () { End(); }, true);
} else {
// 旧浏览器回退:mouse 事件 + document 捕获阶段监听
handle.addEventListener('mousedown', function (e) {
if (e.button !== 0) return;
if (!CanStart(e.target)) return;
Begin(e.clientX, e.clientY);
e.preventDefault();
}, true);
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);
document.addEventListener('visibilitychange', function () { if (document.hidden) End(); });
return { End: End, IsActive: function () { return Active; } };
}
// ===== 工具列表(动态从 __hornetTools__ 获取)=====
@@ -78,7 +137,7 @@
'white-space:pre;line-height:1.6;text-shadow:0 0 3px rgba(125,249,255,0.4);' +
'user-select:none;-webkit-user-select:none;box-shadow:0 2px 12px rgba(0,0,0,0.4);';
document.body.appendChild(hud);
MakeDraggable(hud);
var HudDrag = MakeDraggable(hud);
// ===== 面板(可拖动,Alt+T 切换)=====
var panel = document.createElement('div');
@@ -428,7 +487,7 @@
document.body.appendChild(panel);
// 面板通过标题栏拖动
MakeDraggable(panel, title);
var PanelDrag = MakeDraggable(panel, title);
// ===== 阻止面板内事件冒泡到 window(防止游戏/工具拦截键盘鼠标)=====
// 鼠标事件:面板上所有鼠标事件都阻止冒泡(防止游戏转视角/开炮等)
@@ -624,9 +683,14 @@
return parts.join('+');
}
function HidePanel() {
panel.style.display = 'none';
PanelDrag.End(); // 防止隐藏时拖动状态残留(粘手)
}
function TogglePanel() {
if (panel.style.display === 'block') {
panel.style.display = 'none';
HidePanel();
} else {
panel.style.display = 'block';
UpdateButtons();
@@ -636,7 +700,7 @@
function OnHotkey(e) {
if (e.key === 'Escape' && panel.style.display === 'block') {
panel.style.display = 'none';
HidePanel();
return;
}
var hk = EventToHotkey(e);
@@ -667,7 +731,7 @@
window.__hornetPanel__ = {
Toggle: TogglePanel,
Show: function() { panel.style.display = 'block'; UpdateButtons(); SyncWfSettings(); },
Hide: function() { panel.style.display = 'none'; },
Hide: HidePanel,
UpdateButtons: UpdateButtons,
RebuildButtons: BuildButtons,
GetHotkeys: function() { var r = {}; Object.keys(Hotkeys).forEach(function(k) { r[k] = Hotkeys[k]; }); return r; },
@@ -688,6 +752,8 @@
console.log('[3DTank] 热键已重置为默认值');
},
Destroy: function() {
HudDrag.End();
PanelDrag.End();
if (hud.parentNode) hud.parentNode.removeChild(hud);
if (panel.parentNode) panel.parentNode.removeChild(panel);
window.removeEventListener('keydown', OnHotkey);
+2 -2
View File
@@ -50,8 +50,8 @@
console.log('[Hornet Prehook] __webpack_require__ 已捕获');
console.log('[Hornet Prehook] window.__wp_require__ 就绪');
// 加载 hornet_bundle.js(如果尚未加载)
if (!window.__hornetTools__) {
// 加载 hornet_bundle.js(默认关闭:URL 加 ?hornet=1 才加载,避免日志遮挡)
if (!window.__hornetTools__ && /[?&]hornet=1(&|$)/.test(location.search)) {
var s = document.createElement('script');
s.src = '/play/static/js/unpacked/hornet_bundle.js';
s.onload = function() {
+39 -39
View File
@@ -36,12 +36,12 @@
// ===== 获取坦克方向(Kkt 组件的 dzc_1) =====
function getTankDirection(tank) {
var hullComp = FindComponentByField(tank, 'czc_1');
if (hullComp && hullComp.dzc_1) {
var hullComp = FindComponentByField(tank, 'vzl_1');
if (hullComp && hullComp.wzl_1) {
return {
x: hullComp.dzc_1.f20_1,
y: hullComp.dzc_1.g20_1,
z: hullComp.dzc_1.h20_1
x: hullComp.wzl_1.f20_1,
y: hullComp.wzl_1.g20_1,
z: hullComp.wzl_1.h20_1
};
}
return null;
@@ -56,14 +56,14 @@
// 1. GameMode entity
try {
var gameScene = gm.rym().pyj_1;
var gmEntity = gameScene.eyl("GameMode");
var gameScene = gm.jyw().iyt_1;
var gmEntity = gameScene.xyu("GameMode");
if (gmEntity) entities.push(gmEntity);
} catch(e) {}
// 2. 所有坦克列表中的 entity
try {
var tankList = gm.xzq_1.z11b();
var tankList = gm.q100_1.r11l();
var tCount = tankList.e1();
for (var i = 0; i < tCount; i++) {
var t = tankList.g1(i);
@@ -74,30 +74,30 @@
// 在每个 entity 上查找相机组件
for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
if (!entity || !entity.wyj_1 || !entity.wyj_1.r2a_1) continue;
if (!entity || !entity.pyt_1 || !entity.pyt_1.s2a_1) continue;
var found = null;
entity.wyj_1.r2a_1.forEach(function(valueList, key) {
entity.pyt_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 相机组件(有 kym_1 和 lym_1 字段)
if (comp.kym_1 && comp.lym_1 &&
comp.kym_1.f20_1 !== undefined && comp.lym_1.f20_1 !== undefined) {
if (comp.cyw_1 && comp.dyw_1 &&
comp.cyw_1.f20_1 !== undefined && comp.dyw_1.f20_1 !== undefined) {
// 检查是否是活跃的相机(lym_1 不全为0)
if (Math.abs(comp.lym_1.h20_1) > 0.001 || Math.abs(comp.lym_1.f20_1) > 0.001) {
if (Math.abs(comp.dyw_1.h20_1) > 0.001 || Math.abs(comp.dyw_1.f20_1) > 0.001) {
found = comp;
return;
}
}
// 策略2:查找 k7 相机控制器(有 qz9_1 和 gz9_1 字段)
if (comp.qz9_1 && comp.gz9_1) {
var camComp = comp.gz9_1;
if (camComp && camComp.kym_1 && camComp.lym_1 &&
(Math.abs(camComp.lym_1.h20_1) > 0.001 || Math.abs(camComp.lym_1.f20_1) > 0.001)) {
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;
}
@@ -109,12 +109,12 @@
// lym_1.h20_1 = -atan2(t, n) = 偏航角
// lym_1.f20_1 = atan2(i, e) = 俯仰角
return {
yaw: -found.lym_1.h20_1, // 偏航角(转为正向)
pitch: found.lym_1.f20_1, // 俯仰角
yaw: -found.dyw_1.h20_1, // 偏航角(转为正向)
pitch: found.dyw_1.f20_1, // 俯仰角
pos: {
x: found.kym_1.f20_1,
y: found.kym_1.g20_1,
z: found.kym_1.h20_1
x: found.cyw_1.f20_1,
y: found.cyw_1.g20_1,
z: found.cyw_1.h20_1
}
};
}
@@ -133,13 +133,13 @@
function getUMtComponent() {
if (UMtComponent) return UMtComponent;
try {
var gmEntity = gm.rym();
if (!gmEntity || !gmEntity.wyj_1 || !gmEntity.wyj_1.r2a_1) return null;
gmEntity.wyj_1.r2a_1.forEach(function(valueList, key) {
var gmEntity = gm.jyw();
if (!gmEntity || !gmEntity.pyt_1 || !gmEntity.pyt_1.s2a_1) return null;
gmEntity.pyt_1.s2a_1.forEach(function(valueList, key) {
if (UMtComponent) return;
try {
var comp = (valueList && typeof valueList.g1 === 'function') ? valueList.g1(0) : valueList;
if (comp && typeof comp.g16x === 'function') UMtComponent = comp;
if (comp && typeof comp.m179 === 'function') UMtComponent = comp;
} catch(e) {}
});
if (UMtComponent) console.log('[雷达] uMt 碰撞检测器已获取');
@@ -161,21 +161,21 @@
var startVec = MkVec(fromPos.x, fromPos.y, fromPos.z);
var dirVec = MkVec(dirX, dirY, dirZ);
var result = {
s16w_1: MkVec(0,0,0), t16w_1: MkVec(0,0,0),
u16w_1: maxDist, w16w_1: null, v16w_1: null,
x16w: function(t, n, i, r, e) {
this.s16w_1.j26(t); this.t16w_1.j26(n);
this.u16w_1 = i; this.w16w_1 = r; this.v16w_1 = e;
y178_1: MkVec(0,0,0), z178_1: MkVec(0,0,0),
a179_1: maxDist, c179_1: null, b179_1: null,
d179: function(t, n, i, r, e) {
this.y178_1.k26(t); this.z178_1.k26(n);
this.a179_1 = i; this.c179_1 = r; this.b179_1 = e;
},
d3: function() { this.v16w_1 = null; }
d3: function() { this.b179_1 = null; }
};
var filter = {
w1cg_1: { h1: function() { return false; }, d3: function() {} },
q16x: function() { return true; },
d3: function() {}, x1cg: function() {}
o1co_1: { h1: function() { return false; }, d3: function() {} },
w179: function() { return true; },
d3: function() {}, p1co: function() {}
};
var hit = uMt.g16x(startVec, dirVec, maxDist, result, filter);
return (hit && result.u16w_1 < maxDist) ? result.u16w_1 : -1;
var hit = uMt.m179(startVec, dirVec, maxDist, result, filter);
return (hit && result.a179_1 < maxDist) ? result.a179_1 : -1;
} catch(e) {
return -1;
}
@@ -524,7 +524,7 @@
radar.frameCount++;
try {
var tankList = gm.xzq_1.z11b();
var tankList = gm.q100_1.r11l();
var count = tankList.e1();
radar.tankCount = count;
@@ -677,7 +677,7 @@
ctx.textAlign = 'left';
ctx.fillText('Tanks: ' + count + ' (B:' + friendlyCount + ' R:' + enemyCount + ')', 5, 12);
if (radar.playerFound) {
ctx.fillText('Player: ' + (playerTank ? playerTank.oyj_1 : '?'), 5, 24);
ctx.fillText('Player: ' + (playerTank ? playerTank.hyt_1 : '?'), 5, 24);
} else {
ctx.fillText('Player: NOT FOUND', 5, 24);
}
+70 -70
View File
@@ -71,7 +71,7 @@
var fakeQueue = {
e1: function() { return 0; },
g1: function(i) { return null; },
v2a: function(x) {},
w2a: function(x) {},
d3: function() {},
ii: function(x) {}
};
@@ -81,27 +81,27 @@
var initError = null;
try {
scene = gm.rym().pyj_1;
scene = gm.jyw().iyt_1;
console.log('[后视] 场景对象:', scene);
// hC 渲染器实例(jr.lye_1)
var renderer = scene.lye_1;
var renderer = scene.fyo_1;
console.log('[后视] 渲染器:', renderer);
// Ff 绘制器实例(hC.r74_1)
ff = renderer.r74_1;
ff = renderer.d75_1;
console.log('[后视] Ff绘制器:', ff);
// 场景根节点(jr.kyf_1)
rootNode = scene.kyf_1;
rootNode = scene.eyp_1;
console.log('[后视] 根节点:', rootNode);
// 主相机 Ha 实例(jr.nyf_1)
mainCamera = scene.nyf_1;
mainCamera = scene.hyp_1;
console.log('[后视] 主相机:', mainCamera);
// 游戏 gl context(Ff.p39_1.p73_1 = BS.p73_1)
gl = ff.p39_1.p73_1;
gl = ff.w39_1.b74_1;
console.log('[后视] GL上下文:', gl);
if (!ff || !rootNode || !mainCamera || !gl) {
@@ -109,11 +109,11 @@
}
// 探测相机字段
console.log('[后视] 相机l35_1:', mainCamera.l35_1);
console.log('[后视] 相机k35_1:', mainCamera.k35_1);
console.log('[后视] 相机u36_1:', mainCamera.u36_1);
console.log('[后视] 相机s35_1:', mainCamera.s35_1);
console.log('[后视] 相机r35_1:', mainCamera.r35_1);
console.log('[后视] 相机b37_1:', mainCamera.b37_1);
if (!mainCamera.l35_1 || !mainCamera.k35_1) {
if (!mainCamera.s35_1 || !mainCamera.r35_1) {
throw new Error('相机字段缺失');
}
@@ -245,11 +245,11 @@
// Hook BS.h3c/g3c:后视渲染时返回 FBO 尺寸(修复 screenSize/aspect ratio)
// y3s 会调用 BS.h3c()/g3c() 获取 drawingBufferWidth/Height 设置相机 l36_1/m36_1
// 不 Hook 的话 aspect ratio 会用游戏 canvas 尺寸(如1920x1080),导致画面比例错误
origH3c = ff.p39_1.h3c.bind(ff.p39_1);
origG3c = ff.p39_1.g3c.bind(ff.p39_1);
ff.p39_1.h3c = function() { return rearviewRendering ? rw : origH3c(); };
ff.p39_1.g3c = function() { return rearviewRendering ? rh : origG3c(); };
console.log('[后视] BS.h3c/g3c 已 Hook');
origH3c = ff.w39_1.o3c.bind(ff.w39_1);
origG3c = ff.w39_1.n3c.bind(ff.w39_1);
ff.w39_1.o3c = function() { return rearviewRendering ? rw : origH3c(); };
ff.w39_1.n3c = function() { return rearviewRendering ? rh : origG3c(); };
console.log('[后视] BS.o3c/n3c 已 Hook');
}
// ===== 渲染后视镜 =====
@@ -309,17 +309,17 @@
var camPosZ = playerPos.z + Config.cameraHeight;
// ===== 保存主相机参数 =====
var savedPitch = mainCamera.l35_1.f20_1;
var savedYaw = mainCamera.l35_1.g20_1;
var savedRoll = mainCamera.l35_1.h20_1;
var savedX = mainCamera.k35_1.f20_1;
var savedY = mainCamera.k35_1.g20_1;
var savedZ = mainCamera.k35_1.h20_1;
var savedL36 = mainCamera.l36_1;
var savedM36 = mainCamera.m36_1;
var savedS36 = mainCamera.s36_1;
var savedQ36 = mainCamera.q36_1;
var savedR36 = mainCamera.r36_1;
var savedPitch = mainCamera.s35_1.f20_1;
var savedYaw = mainCamera.s35_1.g20_1;
var savedRoll = mainCamera.s35_1.h20_1;
var savedX = mainCamera.r35_1.f20_1;
var savedY = mainCamera.r35_1.g20_1;
var savedZ = mainCamera.r35_1.h20_1;
var savedL36 = mainCamera.s36_1;
var savedM36 = mainCamera.t36_1;
var savedS36 = mainCamera.z36_1;
var savedQ36 = mainCamera.x36_1;
var savedR36 = mainCamera.y36_1;
// 临时暂停自由视角的 h37 hook,避免覆盖后视镜相机参数
var fl = window.__hornetFreeLook__;
@@ -330,41 +330,41 @@
// g=π 使水平方向反向(朝后看),h=yaw+π 保持水平视角正确
// f=-pitch 使垂直方向与主视一致(低头看后方地形)
// up 朝下 → FBO 画面颠倒 → GL readback Y 翻转修正
mainCamera.l35_1.f20_1 = rearPitch;
mainCamera.l35_1.g20_1 = Math.PI;
mainCamera.l35_1.h20_1 = camInfo.yaw + Math.PI;
mainCamera.k35_1.f20_1 = camPosX;
mainCamera.k35_1.g20_1 = camPosY;
mainCamera.k35_1.h20_1 = camPosZ;
mainCamera.l36_1 = rw;
mainCamera.m36_1 = rh;
mainCamera.s36_1 = Config.fov;
mainCamera.q36_1 = Config.near;
mainCamera.r36_1 = Config.far;
mainCamera.s35_1.f20_1 = rearPitch;
mainCamera.s35_1.g20_1 = Math.PI;
mainCamera.s35_1.h20_1 = camInfo.yaw + Math.PI;
mainCamera.r35_1.f20_1 = camPosX;
mainCamera.r35_1.g20_1 = camPosY;
mainCamera.r35_1.h20_1 = camPosZ;
mainCamera.s36_1 = rw;
mainCamera.t36_1 = rh;
mainCamera.z36_1 = Config.fov;
mainCamera.x36_1 = Config.near;
mainCamera.y36_1 = Config.far;
// 更新相机矩阵(viewMatrix, projectionMatrix, viewProjectionMatrix)
mainCamera.h37();
mainCamera.o37();
// 保存后视视图矩阵副本(用于 UI 标记投影,finally 会恢复相机所以需提前保存)
var rearViewMatrix = new Float32Array(mainCamera.u36_1);
var rearViewMatrix = new Float32Array(mainCamera.b37_1);
// ===== 保存并禁用阴影/后处理(避免它们切换 FBO 覆盖 rearFbo)=====
// p38_1.x3n_1 控制 z39_1(阴影),y3n_1 控制 c3a_1/d3a_1(后处理),z3n_1 控制 f3a_1
// 禁用后 y3s 只执行主场景渲染 z3k,跳过所有 FBO 切换的 pass
var savedX3n = ff.p38_1.x3n_1;
var savedY3n = ff.p38_1.y3n_1;
var savedZ3n = ff.p38_1.z3n_1;
ff.p38_1.x3n_1 = false;
ff.p38_1.y3n_1 = false;
ff.p38_1.z3n_1 = false;
var savedX3n = ff.w38_1.e3o_1;
var savedY3n = ff.w38_1.f3o_1;
var savedZ3n = ff.w38_1.g3o_1;
ff.w38_1.e3o_1 = false;
ff.w38_1.f3o_1 = false;
ff.w38_1.g3o_1 = false;
// ===== 禁用 UI 渲染层(防止技能条等 UI 混入后视 FBO)=====
// 保存 UI 层队列,替换为空假对象,y3s 后恢复
var savedUiQueues = {};
for (var ui = 0; ui < uiLayerIndices.length; ui++) {
var li = uiLayerIndices[ui];
savedUiQueues[li] = ff.x38_1[li];
ff.x38_1[li] = fakeQueue;
savedUiQueues[li] = ff.e39_1[li];
ff.e39_1[li] = fakeQueue;
}
// ===== 渲染到 FBO =====
@@ -377,7 +377,7 @@
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// 调用游戏原生渲染器(已禁用阴影/后处理/UI层,只渲染 3D 场景)
ff.y3s(rootNode, mainCamera);
ff.f3t(rootNode, mainCamera);
rearviewRendering = false;
@@ -430,9 +430,9 @@
// ===== 恢复阴影/后处理(异常时也要恢复)=====
try {
if (typeof savedX3n !== 'undefined') {
ff.p38_1.x3n_1 = savedX3n;
ff.p38_1.y3n_1 = savedY3n;
ff.p38_1.z3n_1 = savedZ3n;
ff.w38_1.e3o_1 = savedX3n;
ff.w38_1.f3o_1 = savedY3n;
ff.w38_1.g3o_1 = savedZ3n;
}
} catch(e) {
console.error('[后视] 恢复阴影/后处理失败:', e.message);
@@ -443,7 +443,7 @@
if (savedUiQueues) {
for (var ui2 = 0; ui2 < uiLayerIndices.length; ui2++) {
var li2 = uiLayerIndices[ui2];
ff.x38_1[li2] = savedUiQueues[li2];
ff.e39_1[li2] = savedUiQueues[li2];
}
}
} catch(e) {
@@ -452,18 +452,18 @@
// ===== 恢复主相机参数 =====
try {
mainCamera.l35_1.f20_1 = savedPitch;
mainCamera.l35_1.g20_1 = savedYaw;
mainCamera.l35_1.h20_1 = savedRoll;
mainCamera.k35_1.f20_1 = savedX;
mainCamera.k35_1.g20_1 = savedY;
mainCamera.k35_1.h20_1 = savedZ;
mainCamera.l36_1 = savedL36;
mainCamera.m36_1 = savedM36;
mainCamera.s36_1 = savedS36;
mainCamera.q36_1 = savedQ36;
mainCamera.r36_1 = savedR36;
mainCamera.h37();
mainCamera.s35_1.f20_1 = savedPitch;
mainCamera.s35_1.g20_1 = savedYaw;
mainCamera.s35_1.h20_1 = savedRoll;
mainCamera.r35_1.f20_1 = savedX;
mainCamera.r35_1.g20_1 = savedY;
mainCamera.r35_1.h20_1 = savedZ;
mainCamera.s36_1 = savedL36;
mainCamera.t36_1 = savedM36;
mainCamera.z36_1 = savedS36;
mainCamera.x36_1 = savedQ36;
mainCamera.y36_1 = savedR36;
mainCamera.o37();
} catch(e) {
console.error('[后视] 恢复相机失败:', e.message);
}
@@ -479,7 +479,7 @@
// ===== UI 标记绘制 =====
function DrawUIMarkers(playerTank, playerPos, camInfo, viewMatrix) {
var tankList = gm.xzq_1.z11b();
var tankList = gm.q100_1.r11l();
var count = tankList.e1();
debugInfo.tankCount = count;
var visibleTanks = 0;
@@ -579,10 +579,10 @@
}
// 恢复 BS.h3c/g3c
if (origH3c && ff && ff.p39_1) {
ff.p39_1.h3c = origH3c;
ff.p39_1.g3c = origG3c;
console.log('[后视] BS.h3c/g3c 已恢复');
if (origH3c && ff && ff.w39_1) {
ff.w39_1.o3c = origH3c;
ff.w39_1.n3c = origG3c;
console.log('[后视] BS.o3c/n3c 已恢复');
}
// 清理 FBO
+50 -50
View File
@@ -26,7 +26,7 @@
var kcVerified = false;
function _start() {
console.log('===== 坦克移动控制启动(j14b_1 控制值修改版) =====');
console.log('===== 坦克移动控制启动(v14m_1 控制值修改版) =====');
if (!window.__gameMode__) {
console.error('请先运行 hook_gamemode.js');
@@ -47,12 +47,12 @@
// ===== 获取玩家坦克 =====
function GetPlayerTank() {
var t = gm.fzr_1;
var t = gm.y100_1;
if (t) return t;
t = gm.ezr_1;
t = gm.x100_1;
if (t) return t;
try {
var tl = gm.xzq_1.z11b();
var tl = gm.q100_1.r11l();
if (tl.e1() > 0) return tl.g1(0);
} catch(e) {}
return null;
@@ -65,47 +65,47 @@
}
// ===== 获取 Dyt 组件(履带底盘,有 j14b_1 字段) =====
dyt = FindComponentByField(playerTank, 'j14b_1');
dyt = FindComponentByField(playerTank, 'v14m_1');
if (!dyt) {
console.error('[测试] 未找到 Dyt 组件(j14b_1 字段)');
console.error('[测试] 未找到 Dyt 组件(v14m_1 字段)');
console.error('[测试] 玩家坦克可能不是履带坦克,或移动组件未初始化');
return;
}
// ===== 诊断信息 =====
console.log('[诊断] Dyt 组件状态:');
console.log(' b14b_1 (在战斗中):', dyt.b14b_1);
console.log(' n14m_1 (在战斗中):', dyt.n14m_1);
try {
console.log(' j14b_1:', dyt.j14b_1.toString());
console.log(' v14m_1:', dyt.v14m_1.toString());
console.log(' i14b_1:', dyt.i14b_1.toString());
} catch(e) {
console.log(' j14b_1/i14b_1 toString 失败:', e.message);
console.log(' v14m_1/i14b_1 toString 失败:', e.message);
}
// 检查 KC(z14a_1)的方法
if (dyt.z14a_1) {
console.log(' z14a_1.aye:', typeof dyt.z14a_1.aye);
console.log(' z14a_1.kak:', typeof dyt.z14a_1.kak);
console.log(' z14a_1 constructor:', dyt.z14a_1.constructor && dyt.z14a_1.constructor.name);
if (dyt.l14m_1) {
console.log(' l14m_1.aye:', typeof dyt.l14m_1.aye);
console.log(' l14m_1.kak:', typeof dyt.l14m_1.kak);
console.log(' l14m_1 constructor:', dyt.l14m_1.constructor && dyt.l14m_1.constructor.name);
} else {
console.warn(' z14a_1 为 null,KC 回调可能未注册');
console.warn(' l14m_1 为 null,KC 回调可能未注册');
}
// ===== 获取 maxSpeed 和 maxTurn =====
// 尝试从 f14b_1(speedCharacteristics/dgt 组件)获取
try {
var sc = dyt.f14b_1;
var sc = dyt.r14m_1;
if (sc) {
if (typeof sc.na9 === 'function') {
maxSpeed = sc.na9();
console.log(' 从 f14b_1.na9() 获取 maxSpeed:', maxSpeed);
} else if (sc.i141_1 !== undefined) {
maxSpeed = sc.i141_1;
console.log(' 从 f14b_1.i141_1 获取 maxSpeed:', maxSpeed);
console.log(' 从 r14m_1.na9() 获取 maxSpeed:', maxSpeed);
} else if (sc.f14c_1 !== undefined) {
maxSpeed = sc.f14c_1;
console.log(' 从 r14m_1.f14c_1 获取 maxSpeed:', maxSpeed);
}
if (sc.m141_1 !== undefined) {
maxTurn = sc.m141_1;
console.log(' 从 f14b_1.m141_1 获取 maxTurn:', maxTurn);
if (sc.j14c_1 !== undefined) {
maxTurn = sc.j14c_1;
console.log(' 从 r14m_1.j14c_1 获取 maxTurn:', maxTurn);
}
}
} catch(e) {
@@ -115,13 +115,13 @@
// 尝试从其他组件获取(a149_1 字段存储 na9() 的值)
if (maxSpeed === 100) {
try {
playerTank.wyj_1.r2a_1.forEach(function(valueList) {
playerTank.pyt_1.s2a_1.forEach(function(valueList) {
if (maxSpeed !== 100) return;
try {
var comp = valueList && typeof valueList.g1 === 'function' ? valueList.g1(0) : valueList;
if (comp && comp.a149_1 !== undefined && typeof comp.a149_1 === 'number' && comp.a149_1 > 0) {
maxSpeed = comp.a149_1;
console.log(' 从组件 a149_1 获取 maxSpeed:', maxSpeed);
if (comp && comp.m14k_1 !== undefined && typeof comp.m14k_1 === 'number' && comp.m14k_1 > 0) {
maxSpeed = comp.m14k_1;
console.log(' 从组件 m14k_1 获取 maxSpeed:', maxSpeed);
}
} catch(e) {}
});
@@ -131,8 +131,8 @@
console.log(' 最终 maxSpeed:', maxSpeed);
console.log(' 最终 maxTurn:', maxTurn);
if (!dyt.b14b_1) {
console.warn('[测试] 玩家不在战斗中(b14b_1=false),KC 回调会直接返回不触发 ryp(Ds)');
if (!dyt.n14m_1) {
console.warn('[测试] 玩家不在战斗中(n14m_1=false),KC 回调会直接返回不触发 iyz(Ds)');
console.warn('[测试] 请确保在战斗中再启动移动控制');
}
@@ -159,8 +159,8 @@
// ===== 主循环:设置 j14b_1 控制值 =====
running = true;
lastS140 = dyt.j14b_1.s140_1;
lastT140 = dyt.j14b_1.t140_1;
lastS140 = dyt.v14m_1.m14b_1;
lastT140 = dyt.v14m_1.n14b_1;
updateInterval = setInterval(function() {
if (!running || !dyt) return;
@@ -174,15 +174,15 @@
// 只在值变化时设置(KC 回调检测 j14b_1 ≠ i14b_1 才触发 ryp(Ds))
if (newS140 !== lastS140 || newT140 !== lastT140) {
try {
dyt.j14b_1.s140_1 = newS140;
dyt.j14b_1.t140_1 = newT140;
dyt.v14m_1.m14b_1 = newS140;
dyt.v14m_1.n14b_1 = newT140;
// u140_1 (reverseBackTurn) 不修改
lastS140 = newS140;
lastT140 = newT140;
console.log('[移动] s140_1=' + newS140 + ' t140_1=' + newT140 +
' (j14b_1 ≠ i14b_1 → KC 回调将触发 ryp(Ds) → kyt 发送网络包)');
console.log('[移动] m14b_1=' + newS140 + ' n14b_1=' + newT140 +
' (v14m_1 ≠ i14b_1 → KC 回调将触发 iyz(Ds) → kyt 发送网络包)');
} catch(e) {
console.error('[测试] 设置 j14b_1 失败:', e.message);
console.error('[测试] 设置 v14m_1 失败:', e.message);
}
}
}, 50);
@@ -199,8 +199,8 @@
clearInterval(verifyInterval);
verifyInterval = null;
if (!kcVerified) {
console.warn('[验证] 3秒内未检测到 KC 回调工作(i14b_1 未跟随 j14b_1 更新)');
console.warn('[验证] 可能原因:1) KC 回调不每帧调用 2) b14b_1=false 3) z14a_1 未注册');
console.warn('[验证] 3秒内未检测到 KC 回调工作(i14b_1 未跟随 v14m_1 更新)');
console.warn('[验证] 可能原因:1) KC 回调不每帧调用 2) n14m_1=false 3) l14m_1 未注册');
console.warn('[验证] 请尝试在游戏中移动(点击地面寻路),观察 i14b_1 是否变化');
}
return;
@@ -208,15 +208,15 @@
// 如果 j14b_1 ≠ i14b_1,说明 KC 回调还没复制(可能 KC 不每帧调用)
// 如果 j14b_1 == i14b_1,说明 KC 回调已复制(KC 工作正常)
try {
if (dyt.j14b_1.s140_1 === dyt.i14b_1.s140_1 &&
dyt.j14b_1.t140_1 === dyt.i14b_1.t140_1) {
if (dyt.v14m_1.m14b_1 === dyt.i14b_1.m14b_1 &&
dyt.v14m_1.n14b_1 === dyt.i14b_1.n14b_1) {
if (lastS140 !== 0 || lastT140 !== 0) {
// j14b_1 有非零值且 i14b_1 已同步
kcVerified = true;
clearInterval(verifyInterval);
verifyInterval = null;
console.log('[验证] KC 回调工作正常!i14b_1 已跟随 j14b_1 更新');
console.log('[验证] ryp(Ds) 应已触发,kyt 应已发送网络包');
console.log('[验证] KC 回调工作正常!i14b_1 已跟随 v14m_1 更新');
console.log('[验证] iyz(Ds) 应已触发,kyt 应已发送网络包');
}
}
} catch(e) {}
@@ -271,13 +271,13 @@
_stopCallbacks = null;
}
// 停止移动:设置 j14b_1 为 0
if (dyt && dyt.j14b_1) {
if (dyt && dyt.v14m_1) {
try {
dyt.j14b_1.s140_1 = 0;
dyt.j14b_1.t140_1 = 0;
console.log('[测试] j14b_1 已归零,坦克停止移动');
dyt.v14m_1.m14b_1 = 0;
dyt.v14m_1.n14b_1 = 0;
console.log('[测试] v14m_1 已归零,坦克停止移动');
} catch(e) {
console.error('[测试] 归零 j14b_1 失败:', e.message);
console.error('[测试] 归零 v14m_1 失败:', e.message);
}
}
keys = {};
@@ -307,18 +307,18 @@
// ===== 手动设置移动值(不通过键盘) =====
function SetMove(forward, turn) {
if (!dyt || !dyt.j14b_1) {
if (!dyt || !dyt.v14m_1) {
console.error('[测试] Dyt 组件未初始化,请先 Start');
return;
}
var newS140 = Math.round(forward * maxSpeed);
var newT140 = Math.round(turn * maxTurn);
try {
dyt.j14b_1.s140_1 = newS140;
dyt.j14b_1.t140_1 = newT140;
dyt.v14m_1.m14b_1 = newS140;
dyt.v14m_1.n14b_1 = newT140;
lastS140 = newS140;
lastT140 = newT140;
console.log('[手动移动] s140_1=' + newS140 + ' t140_1=' + newT140);
console.log('[手动移动] m14b_1=' + newS140 + ' n14b_1=' + newT140);
} catch(e) {
console.error('[测试] 手动设置失败:', e.message);
}
+132 -85
View File
@@ -41,19 +41,19 @@
FindComponentByField(tank, 'p14l_1');
if (!hullComp || !hullComp.t14l_1) return null;
var skin = hullComp.t14l_1;
if (!skin || !skin.p16z_1) return null;
if (!skin || !skin.s17b_1) return null;
try {
var partCount = skin.p16z_1.e1();
var partCount = skin.s17b_1.e1();
for (var p = 0; p < partCount; p++) {
var part = skin.p16z_1.g1(p);
if (!part || !part.s16z_1) continue;
var nodeCount = part.s16z_1.e1();
var part = skin.s17b_1.g1(p);
if (!part || !part.v17b_1) continue;
var nodeCount = part.v17b_1.e1();
for (var n = 0; n < nodeCount; n++) {
var bpNode = part.s16z_1.g1(n);
if (!bpNode || !bpNode.g35_1) continue;
var bpNode = part.v17b_1.g1(n);
if (!bpNode || !bpNode.n35_1) continue;
// 跳过幽灵节点(z34_1 < 0.9)
if (bpNode.z34_1 !== undefined && bpNode.z34_1 < 0.9) continue;
var m = bpNode.g35_1;
if (bpNode.g35_1 !== undefined && bpNode.g35_1 < 0.9) continue;
var m = bpNode.n35_1;
// 列主序世界矩阵:Col0=(m[0],m[1],m[2]) Col1=(m[4],m[5],m[6]) Col2=(m[8],m[9],m[10])
// 游戏坐标:x,y 水平面; z(h20_1)=高度。找"上"列(|z|最大),其余为水平方向
var az0 = Math.abs(m[2]), az1 = Math.abs(m[6]), az2 = Math.abs(m[10]);
@@ -83,11 +83,11 @@
var scene = null, ff = null, rootNode = null, mainCamera = null, gl = null;
var initError = null;
try {
scene = gm.rym().pyj_1;
ff = scene.lye_1.r74_1;
rootNode = scene.kyf_1;
mainCamera = scene.nyf_1;
gl = ff.p39_1.p73_1;
scene = gm.jyw().iyt_1;
ff = scene.fyo_1.d75_1;
rootNode = scene.eyp_1;
mainCamera = scene.hyp_1;
gl = ff.w39_1.b74_1;
if (!ff || !rootNode || !mainCamera || !gl) throw new Error('关键字段缺失');
} catch(e) {
initError = e;
@@ -227,16 +227,16 @@
}
return origBindFramebuffer(target, fbo);
};
var origH3c = ff.p39_1.h3c.bind(ff.p39_1);
var origG3c = ff.p39_1.g3c.bind(ff.p39_1);
ff.p39_1.h3c = function() { return sandboxRendering ? FBO_W : origH3c(); };
ff.p39_1.g3c = function() { return sandboxRendering ? FBO_H : origG3c(); };
var origH3c = ff.w39_1.o3c.bind(ff.w39_1);
var origG3c = ff.w39_1.n3c.bind(ff.w39_1);
ff.w39_1.o3c = function() { return sandboxRendering ? FBO_W : origH3c(); };
ff.w39_1.n3c = function() { return sandboxRendering ? FBO_H : origG3c(); };
// UI 层禁用
var uiLayerIndices = [20, 21, 22, 23, 24];
var fakeQueue = {
e1: function() { return 0; }, g1: function(i) { return null; },
v2a: function(x) {}, d3: function() {}, ii: function(x) {}
w2a: function(x) {}, d3: function() {}, ii: function(x) {}
};
// ===== 视图状态 =====
@@ -245,45 +245,92 @@
var viewYaw = 0; // 地图旋转(0=北朝上)
var initialized = false;
// ===== 指针拖动绑定(Pointer Events + setPointerCapture,避免松手后粘手)=====
// HandleEl: 触发拖动的元素;OnStart/OnMove/OnEnd 分别处理开始/移动/结束
function BindPointerDrag(HandleEl, OnStart, OnMove, OnEnd) {
var Active = false;
var PointerId = null;
function Finish() {
if (!Active) return;
Active = false;
if (PointerId !== null && HandleEl.releasePointerCapture) {
try { HandleEl.releasePointerCapture(PointerId); } catch (Err) {}
}
PointerId = null;
OnEnd();
}
if (window.PointerEvent) {
HandleEl.addEventListener('pointerdown', function(e) {
if (e.button !== 0) return;
if (OnStart(e) === false) return;
Active = true;
PointerId = e.pointerId;
try { HandleEl.setPointerCapture(e.pointerId); } catch (Err) {}
e.preventDefault();
e.stopPropagation();
}, true);
HandleEl.addEventListener('pointermove', function(e) {
if (!Active || (PointerId !== null && e.pointerId !== PointerId)) return;
if (e.buttons === 0) { Finish(); return; } // 兜底:按键已松开
OnMove(e);
e.preventDefault();
}, true);
HandleEl.addEventListener('pointerup', Finish, true);
HandleEl.addEventListener('pointercancel', Finish, true);
HandleEl.addEventListener('lostpointercapture', Finish, true);
window.addEventListener('pointerup', Finish, true);
} else {
HandleEl.addEventListener('mousedown', function(e) {
if (e.button !== 0) return;
if (OnStart(e) === false) return;
Active = true;
e.preventDefault();
}, true);
document.addEventListener('mousemove', function(e) {
if (!Active) return;
if (e.buttons === 0) { Finish(); return; }
OnMove(e);
}, true);
document.addEventListener('mouseup', Finish, true);
}
window.addEventListener('blur', Finish);
window.addEventListener('mouseleave', Finish);
return { End: Finish, IsActive: function() { return Active; } };
}
// ===== 窗口拖动 =====
var dragging = false, dsx = 0, dsy = 0, dwx = 0, dwy = 0;
titleBar.addEventListener('mousedown', function(e) {
if (e.button !== 0 || e.target.tagName === 'BUTTON') return;
dragging = true; dsx = e.clientX; dsy = e.clientY;
var dsx = 0, dsy = 0, dwx = 0, dwy = 0;
BindPointerDrag(titleBar, function(e) {
if (e.target.tagName === 'BUTTON') return false;
dsx = e.clientX; dsy = e.clientY;
var rect = container.getBoundingClientRect();
dwx = rect.left; dwy = rect.top;
container.style.transform = 'none';
container.style.left = dwx + 'px'; container.style.top = dwy + 'px';
e.preventDefault();
});
document.addEventListener('mousemove', function(e) {
if (!dragging) return;
}, function(e) {
var nx = Math.max(0, Math.min(window.innerWidth - 100, dwx + e.clientX - dsx));
var ny = Math.max(0, Math.min(window.innerHeight - 40, dwy + e.clientY - dsy));
container.style.left = nx + 'px'; container.style.top = ny + 'px';
});
document.addEventListener('mouseup', function() { dragging = false; });
}, function() {});
// ===== 地图平移 =====
var panning = false, psx = 0, psy = 0, pcx = 0, pcy = 0;
canvas.addEventListener('mousedown', function(e) {
if (e.button !== 0) return;
panning = true; psx = e.clientX; psy = e.clientY;
var psx = 0, psy = 0, pcx = 0, pcy = 0;
BindPointerDrag(canvas, function(e) {
psx = e.clientX; psy = e.clientY;
pcx = viewCenterX; pcy = viewCenterY;
canvas.style.cursor = 'grabbing';
e.preventDefault();
});
document.addEventListener('mousemove', function(e) {
if (!panning) return;
}, function(e) {
// 俯瞰视图:屏幕像素→世界单位,X/Y 方向缩放不同(FBO 拉伸到 canvas)
var ext = getViewExtents();
var dx = (e.clientX - psx) * ext.wppX;
var dy = (e.clientY - psy) * ext.wppY;
viewCenterX = pcx - dx;
viewCenterY = pcy + dy; // y 反转
});
document.addEventListener('mouseup', function() {
if (panning) { panning = false; canvas.style.cursor = 'grab'; }
}, function() {
canvas.style.cursor = 'grab';
});
// 滚轮缩放(改变相机高度)
@@ -383,7 +430,7 @@
// 坦克标记
var tanks = [];
try {
var list = gm.xzq_1.z11b();
var list = gm.q100_1.r11l();
var n = list.e1();
for (var i = 0; i < n; i++) { var t = list.g1(i); if (t) tanks.push(t); }
} catch(e) {}
@@ -551,44 +598,44 @@
try {
// 保存主相机参数
var sPitch = mainCamera.l35_1.f20_1;
var sRoll = mainCamera.l35_1.g20_1;
var sYaw = mainCamera.l35_1.h20_1;
var sX = mainCamera.k35_1.f20_1;
var sY = mainCamera.k35_1.g20_1;
var sZ = mainCamera.k35_1.h20_1;
var sL36 = mainCamera.l36_1, sM36 = mainCamera.m36_1;
var sS36 = mainCamera.s36_1, sQ36 = mainCamera.q36_1, sR36 = mainCamera.r36_1;
var sPitch = mainCamera.s35_1.f20_1;
var sRoll = mainCamera.s35_1.g20_1;
var sYaw = mainCamera.s35_1.h20_1;
var sX = mainCamera.r35_1.f20_1;
var sY = mainCamera.r35_1.g20_1;
var sZ = mainCamera.r35_1.h20_1;
var sL36 = mainCamera.s36_1, sM36 = mainCamera.t36_1;
var sS36 = mainCamera.z36_1, sQ36 = mainCamera.x36_1, sR36 = mainCamera.y36_1;
// 设置俯瞰相机参数
// pitch=-π/2 朝下看,roll=0 不翻转,yaw=0 北朝上
mainCamera.l35_1.f20_1 = -Math.PI / 2;
mainCamera.l35_1.g20_1 = 0;
mainCamera.l35_1.h20_1 = 0;
mainCamera.k35_1.f20_1 = viewCenterX;
mainCamera.k35_1.g20_1 = viewCenterY;
mainCamera.k35_1.h20_1 = camHeight;
mainCamera.l36_1 = FBO_W;
mainCamera.m36_1 = FBO_H;
mainCamera.s36_1 = Config.fov;
mainCamera.q36_1 = Config.near;
mainCamera.r36_1 = Config.far;
mainCamera.h37();
mainCamera.s35_1.f20_1 = -Math.PI / 2;
mainCamera.s35_1.g20_1 = 0;
mainCamera.s35_1.h20_1 = 0;
mainCamera.r35_1.f20_1 = viewCenterX;
mainCamera.r35_1.g20_1 = viewCenterY;
mainCamera.r35_1.h20_1 = camHeight;
mainCamera.s36_1 = FBO_W;
mainCamera.t36_1 = FBO_H;
mainCamera.z36_1 = Config.fov;
mainCamera.x36_1 = Config.near;
mainCamera.y36_1 = Config.far;
mainCamera.o37();
// 禁用阴影/后处理
var savedX3n = ff.p38_1.x3n_1;
var savedY3n = ff.p38_1.y3n_1;
var savedZ3n = ff.p38_1.z3n_1;
ff.p38_1.x3n_1 = false;
ff.p38_1.y3n_1 = false;
ff.p38_1.z3n_1 = false;
var savedX3n = ff.w38_1.e3o_1;
var savedY3n = ff.w38_1.f3o_1;
var savedZ3n = ff.w38_1.g3o_1;
ff.w38_1.e3o_1 = false;
ff.w38_1.f3o_1 = false;
ff.w38_1.g3o_1 = false;
// 禁用 UI 层
var savedUiQueues = {};
for (var ui = 0; ui < uiLayerIndices.length; ui++) {
var li = uiLayerIndices[ui];
savedUiQueues[li] = ff.x38_1[li];
ff.x38_1[li] = fakeQueue;
savedUiQueues[li] = ff.e39_1[li];
ff.e39_1[li] = fakeQueue;
}
// 渲染到 FBO
@@ -597,7 +644,7 @@
gl.viewport(0, 0, FBO_W, FBO_H);
gl.clearColor(0.05, 0.08, 0.12, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
ff.y3s(rootNode, mainCamera);
ff.f3t(rootNode, mainCamera);
sandboxRendering = false;
// Readback
@@ -634,23 +681,23 @@
origBindFramebuffer(gl.FRAMEBUFFER, mainFbo);
// 恢复渲染状态
ff.p38_1.x3n_1 = savedX3n;
ff.p38_1.y3n_1 = savedY3n;
ff.p38_1.z3n_1 = savedZ3n;
ff.w38_1.e3o_1 = savedX3n;
ff.w38_1.f3o_1 = savedY3n;
ff.w38_1.g3o_1 = savedZ3n;
for (var ui2 = 0; ui2 < uiLayerIndices.length; ui2++) {
ff.x38_1[uiLayerIndices[ui2]] = savedUiQueues[uiLayerIndices[ui2]];
ff.e39_1[uiLayerIndices[ui2]] = savedUiQueues[uiLayerIndices[ui2]];
}
// 恢复主相机
mainCamera.l35_1.f20_1 = sPitch;
mainCamera.l35_1.g20_1 = sRoll;
mainCamera.l35_1.h20_1 = sYaw;
mainCamera.k35_1.f20_1 = sX;
mainCamera.k35_1.g20_1 = sY;
mainCamera.k35_1.h20_1 = sZ;
mainCamera.l36_1 = sL36; mainCamera.m36_1 = sM36;
mainCamera.s36_1 = sS36; mainCamera.q36_1 = sQ36; mainCamera.r36_1 = sR36;
mainCamera.h37();
mainCamera.s35_1.f20_1 = sPitch;
mainCamera.s35_1.g20_1 = sRoll;
mainCamera.s35_1.h20_1 = sYaw;
mainCamera.r35_1.f20_1 = sX;
mainCamera.r35_1.g20_1 = sY;
mainCamera.r35_1.h20_1 = sZ;
mainCamera.s36_1 = sL36; mainCamera.t36_1 = sM36;
mainCamera.z36_1 = sS36; mainCamera.x36_1 = sQ36; mainCamera.y36_1 = sR36;
mainCamera.o37();
// 绘制 2D 叠加层(坦克标记/网格/罗盘)
drawOverlays();
@@ -685,8 +732,8 @@
if (rafId) cancelAnimationFrame(rafId);
// 恢复 hook
gl.bindFramebuffer = origBindFramebuffer;
ff.p39_1.h3c = origH3c;
ff.p39_1.g3c = origG3c;
ff.w39_1.o3c = origH3c;
ff.w39_1.n3c = origG3c;
window.removeEventListener('resize', resizeCanvas);
if (container.parentNode) container.parentNode.removeChild(container);
window.__hornetWarSandbox__ = null;
+430
View File
@@ -0,0 +1,430 @@
// map_symbols.js - 生成新旧客户端 bundle 之间的符号映射表
// 原理:两版源码除"混淆重命名"外高度一致。用 K 元 token 块做锚点对齐,
// 对齐后区域若 token 数相同则按位置一一对应,据此对标识符改名投票。
// 用法: node map_symbols.js <旧unpacked目录> <新bundle路径> <模块id逗号分隔> [输出json]
// 输出: JSON,结构 { 模块id: { 旧名: 新名 }, "_merged": { 旧名: 新名 } }
const fs = require('fs');
const path = require('path');
const K = 10; // 锚点块长度(token 数)
const MIN_ID_LEN = 3; // 参与投票的标识符最小长度(过滤局部单字符名)
// ===== 词法切分:标识符 / 数字 / 字符串 / 正则 / 标点 =====
function Tokenize(src) {
const out = [];
let i = 0;
const n = src.length;
const IdStart = /[A-Za-z_$]/;
const IdPart = /[A-Za-z0-9_$]/;
while (i < n) {
const c = src[i];
// 空白(换行/空格/制表)不参与比对
if (c === ' ' || c === '\n' || c === '\r' || c === '\t' || c === '\f' || c === '\v') { i++; continue; }
// 注释
if (c === '/' && src[i + 1] === '/') {
while (i < n && src[i] !== '\n') i++;
continue;
}
if (c === '/' && src[i + 1] === '*') {
i += 2;
while (i < n && !(src[i] === '*' && src[i + 1] === '/')) i++;
i += 2;
continue;
}
// 字符串
if (c === '"' || c === "'" || c === '`') {
const quote = c;
let j = i + 1;
while (j < n) {
if (src[j] === '\\') { j += 2; continue; }
if (src[j] === quote) { j++; break; }
j++;
}
out.push({ k: 'str', s: src.slice(i, j) });
i = j;
continue;
}
// 正则字面量(启发式:前一个有效字符不是标识符/数字/右括号)
if (c === '/') {
let p = out.length - 1;
const prev = p >= 0 ? out[p] : null;
const okPrev = !prev || (prev.k === 'punct' && !')]'.includes(prev.s));
if (okPrev) {
let j = i + 1;
let closed = false;
while (j < n) {
if (src[j] === '\\') { j += 2; continue; }
if (src[j] === '\n') break;
if (src[j] === '[') { while (j < n && src[j] !== ']') { if (src[j] === '\\') j++; j++; } }
if (src[j] === '/') { j++; closed = true; break; }
j++;
}
if (closed) {
while (j < n && /[a-z]/.test(src[j])) j++;
out.push({ k: 're', s: src.slice(i, j) });
i = j;
continue;
}
}
}
if (IdStart.test(c)) {
let j = i + 1;
while (j < n && IdPart.test(src[j])) j++;
out.push({ k: 'id', s: src.slice(i, j) });
i = j;
continue;
}
if (/[0-9]/.test(c)) {
let j = i + 1;
while (j < n && /[0-9a-fA-FxX._]/.test(src[j])) j++;
out.push({ k: 'num', s: src.slice(i, j) });
i = j;
continue;
}
out.push({ k: 'punct', s: c });
i++;
}
return out;
}
// ===== 从新 bundle 提取模块源码 =====
function ExtractNewModule(bundle, id) {
const re = /(?<=[{,])(\d{3,6}):(?:\(|function)/g;
let m;
const marks = [];
while ((m = re.exec(bundle)) !== null) {
marks.push({ id: m[1], start: m.index, keyEnd: m.index + m[1].length });
}
for (let i = 0; i < marks.length; i++) {
if (marks[i].id !== String(Number(id))) continue;
const end = i + 1 < marks.length ? marks[i + 1].start : bundle.length;
// 从 ':' 之后开始(跳过 "87072:")
return bundle.slice(marks[i].keyEnd + 1, end);
}
return null;
}
// ===== 读旧模块源码(去掉文件头注释)=====
function ReadOldModule(dir, id) {
const f = path.join(dir, 'module_' + id + '.js');
if (!fs.existsSync(f)) return null;
let src = fs.readFileSync(f, 'utf8');
src = src.replace(/^(?:\s*\/\/[^\n]*\n)+/, '');
return src;
}
// ===== 过滤"无信息"标点 =====
// 两版由不同代 minifier 产出:括号/逗号/分号的增删不影响语义,但会打断对齐,故丢弃。
// 保留:标识符/数字/字符串/正则,以及 {} . [] = + - * / % < > ! & | ? : ~ ^
const SIG_PUNCT = '{}.[]=+-*/%<>!&|?:~^';
function FilterSignificant(tokens) {
const out = [];
for (const t of tokens) {
if (t.k === 'punct') {
if (SIG_PUNCT.indexOf(t.s) < 0) continue;
out.push(t);
} else {
out.push(t);
}
}
return out;
}
// ===== 流式对齐 =====
// 两个 token 流同源,差异只有两类:①标识符改名 ②minifier 风格差异/少量源码改动。
// 策略:同步推进;标识符对标识符即记录一条改名投票;否则在有限窗口内找一个"4 token 完全一致块"重同步。
const LOOK = 40; // 重同步搜索窗口(token 数)
const SYNC_LEN = 4; // 重同步锚块长度
function SameTok(a, b) {
return a.k === b.k && a.s === b.s;
}
function SameBlock(oldT, i, newT, j, len) {
if (i + len > oldT.length || j + len > newT.length) return false;
for (let x = 0; x < len; x++) if (!SameTok(oldT[i + x], newT[j + x])) return false;
return true;
}
function WalkAlign(oldT, newT, onIdPair, counter, onUnsync, look) {
const LOOK2 = look || LOOK;
let i = 0, j = 0;
const n = oldT.length, m = newT.length;
while (i < n && j < m) {
const a = oldT[i], b = newT[j];
if (SameTok(a, b)) { i++; j++; counter.same++; continue; }
if (a.k === 'id' && b.k === 'id') { onIdPair(a.s, b.s); i++; j++; counter.rename++; continue; }
// 重同步:优先以字符串/数字常量定位,其次 4-token 块
let bestK = -1, bestL = -1;
for (let k = 0; k <= LOOK2 && i + k < n; k++) {
const ta = oldT[i + k];
if (ta.k !== 'str' && ta.k !== 'num') continue;
for (let l = 0; l <= LOOK2 && j + l < m; l++) {
const tb = newT[j + l];
if (SameTok(ta, tb)) { bestK = k; bestL = l; break; }
}
if (bestK >= 0) break;
}
if (bestK < 0) {
for (let k = 0; k <= LOOK2 && i + k < n; k++) {
for (let l = 0; l <= LOOK2 && j + l < m; l++) {
if (SameBlock(oldT, i + k, newT, j + l, SYNC_LEN)) { bestK = k; bestL = l; break; }
}
if (bestK >= 0) break;
}
}
if (bestK < 0) {
if (onUnsync) onUnsync(i, j, oldT, newT);
i++; j++; counter.unsync++; continue;
}
counter.sync += (bestK + bestL);
i += bestK;
j += bestL;
}
}
// ===== 递归唯一 token 切分 =====
// 思路(patience diff):在两侧都"只出现一次"的 token 天然对应,可作锚点切分;
// 切出的段内再递归找唯一 token,直到段足够小,再做局部对齐。
const REGION_MAX = 4000; // 单段最大 token 数,超过则继续切分
const REGION_BAD = 0.25; // 段内未同步比例上限,超过则丢弃该段的投票
const MAX_DEPTH = 12;
function LowerBound(arr, v) {
let lo = 0, hi = arr.length;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (arr[mid] < v) lo = mid + 1; else hi = mid;
}
return lo;
}
function UniqueAnchors(oldT, newT) {
const cntO = new Map(), posO = new Map();
for (let i = 0; i < oldT.length; i++) {
const k = oldT[i].k + ':' + oldT[i].s;
cntO.set(k, (cntO.get(k) || 0) + 1);
posO.set(k, i);
}
const cntN = new Map(), posN = new Map();
for (let j = 0; j < newT.length; j++) {
const k = newT[j].k + ':' + newT[j].s;
cntN.set(k, (cntN.get(k) || 0) + 1);
posN.set(k, j);
}
const pairs = [];
for (const [k, c] of cntO) {
if (c !== 1 || cntN.get(k) !== 1) continue;
pairs.push([posO.get(k), posN.get(k)]);
}
pairs.sort((a, b) => a[0] - b[0]);
return LisLongest(pairs);
}
// 取按第二坐标严格递增的最长子序列,保证锚点单调
function LisLongest(pairs) {
const tails = [];
const prev = new Array(pairs.length).fill(-1);
for (let i = 0; i < pairs.length; i++) {
const v = pairs[i][1];
let lo = 0, hi = tails.length;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (pairs[tails[mid]][1] < v) lo = mid + 1; else hi = mid;
}
prev[i] = lo > 0 ? tails[lo - 1] : -1;
tails[lo] = i;
}
const out = [];
let k = tails.length ? tails[tails.length - 1] : -1;
while (k >= 0) { out.push(pairs[k]); k = prev[k]; }
return out.reverse();
}
// 段内局部对齐(带质量门限,低质段整段丢弃)
function LocalWalk(oldT, os, oe, newT, ns, ne, sink, counter) {
const oLen = oe - os, nLen = ne - ns;
if (oLen <= 0 || nLen <= 0) return;
if (oLen > REGION_MAX || nLen > REGION_MAX) { counter.skipBig++; return; }
const pairs = [];
const c = { same: 0, rename: 0, sync: 0, unsync: 0 };
WalkAlign(oldT.slice(os, oe), newT.slice(ns, ne), (a, b) => pairs.push([a, b]), c, null, 150);
counter.same += c.same;
counter.rename += c.rename;
counter.sync += c.sync;
counter.unsync += c.unsync;
const bad = (c.unsync * 2) / (oLen + nLen || 1);
if (bad > REGION_BAD) { counter.badRegion++; return; }
for (const [a, b] of pairs) sink(a, b);
}
function RecursiveAlign(oldT, newT, sink, counter, depth) {
const anchors = UniqueAnchors(oldT, newT);
if (anchors.length === 0) {
LocalWalk(oldT, 0, oldT.length, newT, 0, newT.length, sink, counter);
return;
}
const parts = [];
let oi = 0, nj = 0;
for (const [i, j] of anchors) {
parts.push([oi, i, nj, j]);
oi = i + 1;
nj = j + 1;
}
parts.push([oi, oldT.length, nj, newT.length]);
for (const [os, oe, ns, ne] of parts) {
const oLen = oe - os, nLen = ne - ns;
if (oLen <= 0 || nLen <= 0) continue;
if (oLen <= REGION_MAX && nLen <= REGION_MAX) {
LocalWalk(oldT, os, oe, newT, ns, ne, sink, counter);
} else if (depth < MAX_DEPTH) {
RecursiveAlign(oldT.slice(os, oe), newT.slice(ns, ne), sink, counter, depth + 1);
} else {
counter.skipBig++;
}
}
}
function AlignByAnchors(oldT, newT, sink, counter) {
RecursiveAlign(oldT, newT, sink, counter, 0);
}
// ===== 主流程 =====
function SumVotes(votes) {
const merged = {};
const detail = {};
for (const [oldName, m] of votes) {
let best = null, total = 0;
for (const [k, v] of m) {
total += v;
if (!best || v > best.v) best = { k, v };
}
merged[oldName] = best.k;
detail[oldName] = {
to: best.k,
keep: best.k === oldName,
votes: best.v,
total,
ratio: +(best.v / total).toFixed(3),
};
}
return { merged, detail };
}
function BuildMap(oldDir, newBundlePath, ids) {
const bundle = fs.readFileSync(newBundlePath, 'utf8');
const perModule = {};
const globalVotes = new Map();
const stats = [];
for (const id of ids) {
const oldSrc = ReadOldModule(oldDir, id);
const newSrc = ExtractNewModule(bundle, id);
if (!oldSrc || !newSrc) {
stats.push({ id, error: 'missing source' });
continue;
}
const oldT = FilterSignificant(Tokenize(oldSrc));
const newT = FilterSignificant(Tokenize(newSrc));
const counter = { same: 0, rename: 0, sync: 0, unsync: 0, skipBig: 0, badRegion: 0, voted: 0 };
const votes = new Map();
AlignByAnchors(oldT, newT, (a, b) => {
if (a.length < MIN_ID_LEN) return;
counter.voted++;
if (!votes.has(a)) votes.set(a, new Map());
const m = votes.get(a);
m.set(b, (m.get(b) || 0) + 1);
if (!globalVotes.has(a)) globalVotes.set(a, new Map());
const g = globalVotes.get(a);
g.set(b, (g.get(b) || 0) + 1);
}, counter);
const per = {};
for (const [oldName, m] of votes) {
let best = null, total = 0;
for (const [k, v] of m) {
total += v;
if (!best || v > best.v) best = { k, v };
}
if (best.v >= 2 && best.v / total >= 0.6) per[oldName] = best.k;
}
perModule[id] = per;
stats.push({
id,
oldTok: oldT.length,
newTok: newT.length,
same: counter.same,
rename: counter.rename,
sync: counter.sync,
unsync: counter.unsync,
skipBig: counter.skipBig,
badRegion: counter.badRegion,
voted: counter.voted,
mapped: Object.keys(per).length,
});
}
const { merged, detail } = SumVotes(globalVotes);
perModule['_merged'] = merged;
perModule['_detail'] = detail;
perModule['_stats'] = stats;
// 跨模块冲突检测:同一旧名在不同模块映射到不同新名
const conflicts = [];
const seen = new Map();
for (const id of ids) {
const per = perModule[id];
if (!per) continue;
for (const [oldName, newName] of Object.entries(per)) {
if (!seen.has(oldName)) { seen.set(oldName, new Map()); }
const m = seen.get(oldName);
if (!m.has(newName)) m.set(newName, []);
m.get(newName).push(id);
}
}
for (const [oldName, m] of seen) {
if (m.size > 1) {
const items = [];
for (const [newName, ids2] of m) items.push(newName + '(' + ids2.join('/') + ')');
conflicts.push(oldName + ' => ' + items.join(' | '));
}
}
perModule['_conflicts'] = conflicts;
return perModule;
}
function main() {
const oldDir = process.argv[2];
const newBundle = process.argv[3];
let ids = (process.argv[4] || '').split(',').filter(Boolean);
const outPath = process.argv[5] || path.join(__dirname, 'symbol_map.json');
if (!oldDir || !newBundle || ids.length === 0) {
console.error('用法: node map_symbols.js <旧unpacked目录> <新bundle> <模块id逗号分隔|all> [输出json]');
process.exit(1);
}
if (ids.length === 1 && ids[0] === 'all') {
ids = fs.readdirSync(oldDir)
.filter((f) => /^module_\d+\.js$/.test(f))
.map((f) => f.replace(/^module_|\.js$/g, ''));
console.log('模块数: ' + ids.length);
}
const result = BuildMap(oldDir, newBundle, ids);
fs.writeFileSync(outPath, JSON.stringify(result, null, 2), 'utf8');
console.log('=== 统计 ===');
for (const s of result._stats) {
console.log(s.error
? ('[' + s.id + '] ' + s.error)
: ('[' + s.id + '] 旧token=' + s.oldTok + ' 新token=' + s.newTok +
' 一致=' + s.same + ' 改名=' + s.rename + ' 重同步丢失=' + s.sync + ' 未同步=' + s.unsync +
' 跳过段=' + s.skipBig + ' 低质段=' + s.badRegion + ' 投票=' + s.voted + ' 映射=' + s.mapped));
}
const names = Object.keys(result._merged);
console.log('=== 映射条目: ' + names.length + ' ===');
console.log('输出: ' + outPath);
}
module.exports = { Tokenize, FilterSignificant, ExtractNewModule, ReadOldModule, WalkAlign, UniqueAnchors, RecursiveAlign, AlignByAnchors, BuildMap };
if (require.main === module) main();
+120 -25
View File
@@ -1,24 +1,49 @@
// split.js - 将 main.b7114b43.js 切割为多文件加载结构
// split.js - 将 main.<hash>.js 切割为多文件加载结构
// 优化: 资源模块(标准 t.exports=i.p+"..." 格式)用 JSON + 模板替代,不生成独立文件
// 用法: node split.js [bundle路径] [输出目录] [--clean] [--no-hornet]
// 未指定 bundle 时自动取项目根目录下最新的 main.<hash>.js
// 未指定输出目录时默认 3dtank.com/play/static/js
// --clean 先删除旧产物(unpacked/module_*.js、resources.json、旧 main.*.js;保留 hornet_*.js)
// --no-hornet 不在主文件末尾注入 hornet_bundle 自动加载
// 输出:
// ./3dtank.com/play/static/js/main.b7114b43.js (主文件: 加载器 + runtime)
// ./3dtank.com/play/static/js/unpacked/resources.json (资源模块 ID→路径映射)
// ./3dtank.com/play/static/js/unpacked/module_<id>.js (仅代码模块,约 126 个)
// <输出目录>/main.<hash>.js (主文件: 加载器 + runtime)
// <输出目录>/unpacked/resources.json (资源模块 ID→路径映射)
// <输出目录>/unpacked/module_<id>.js (仅代码模块)
const fs = require('fs');
const path = require('path');
const acorn = require('acorn');
const walk = require('acorn-walk');
const prettier = require('prettier');
const BUNDLE_PATH = path.resolve(__dirname, '..', 'main.b7114b43.js');
const OUT_DIR = path.resolve(__dirname, '..', '3dtank.com', 'play', 'static', 'js');
const ROOT = path.resolve(__dirname, '..');
// 自动取最新的 main.<hash>.js(排除输出目录里的加载器)
function AutoDetectBundle() {
const cands = fs.readdirSync(ROOT)
.filter((f) => /^main\.[0-9a-f]+\.js$/.test(f))
.map((f) => path.join(ROOT, f))
.filter((f) => fs.statSync(f).isFile())
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
return cands[0] || null;
}
const argv = process.argv.slice(2);
const flags = argv.filter((a) => a.startsWith('--'));
const positional = argv.filter((a) => !a.startsWith('--'));
const CLEAN = flags.indexOf('--clean') >= 0;
const WITH_HORNET = flags.indexOf('--no-hornet') < 0;
const LOADER_ONLY = flags.indexOf('--loader-only') >= 0; // 只重生成主文件,复用已有模块文件
const autoBundle = positional[0] ? null : AutoDetectBundle();
const BUNDLE_PATH = path.resolve(positional[0] || autoBundle || '');
const OUT_DIR = path.resolve(positional[1] || path.join(ROOT, '3dtank.com', 'play', 'static', 'js'));
const UNPACKED_DIR = path.join(OUT_DIR, 'unpacked');
const MAIN_PATH = path.join(OUT_DIR, 'main.b7114b43.js');
const MAIN_PATH = BUNDLE_PATH ? path.join(OUT_DIR, path.basename(BUNDLE_PATH)) : '';
const RESOURCES_JSON_PATH = path.join(UNPACKED_DIR, 'resources.json');
// UMD 包装模板
const UMD_PREFIX = `var __script_base_url__=(typeof document!=="undefined"&&document.currentScript&&document.currentScript.src)?document.currentScript.src.replace(/[^\\/]+$/,""):"./";!function(t,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define([],n):"object"==typeof exports?exports.Launcher=n():t.Launcher=n()}(globalThis,async()=>{`;
const UMD_SUFFIX = `})//# sourceMappingURL=main.b7114b43.js.map\n`;
const UMD_SUFFIX = (bundlePath) => `})//# sourceMappingURL=${path.basename(bundlePath)}.map\n`;
// 资源模块识别:标准格式 (t,n,i)=>{"use strict";t.exports=i.p+"path"}
// 提取 path
@@ -39,7 +64,32 @@ function tryExtractResource(valueSrc) {
}
(async () => {
if (!BUNDLE_PATH || !fs.existsSync(BUNDLE_PATH)) {
console.error('找不到待切割的 bundle:' + (BUNDLE_PATH || '(未自动探测到 main.<hash>.js)'));
process.exit(1);
}
if (!fs.existsSync(UNPACKED_DIR)) fs.mkdirSync(UNPACKED_DIR, { recursive: true });
if (CLEAN) {
console.log('[0/7] 清理旧产物...');
let removed = 0;
for (const dir of [UNPACKED_DIR, OUT_DIR]) {
if (!fs.existsSync(dir)) continue;
for (const f of fs.readdirSync(dir)) {
const isModule = /^module_\d+\.js$/.test(f);
const isResources = f === 'resources.json';
const isOldMain = /^main\.[0-9a-f]+\.js$/.test(f) && path.join(dir, f) !== MAIN_PATH;
if (isModule || isResources || isOldMain) {
fs.unlinkSync(path.join(dir, f));
removed++;
}
}
}
console.log(` 已删除 ${removed} 个旧文件(保留 hornet_*.js)`);
}
console.log('[1/7] 读取原 bundle...');
console.log(` 文件: ${BUNDLE_PATH}`);
const source = fs.readFileSync(BUNDLE_PATH, 'utf8');
console.log(` 大小: ${(source.length / 1024 / 1024).toFixed(2)} MB`);
@@ -107,6 +157,12 @@ function tryExtractResource(valueSrc) {
const paddedId = typeof keyNum === 'number' ? String(keyNum).padStart(5, '0') : keyRaw;
const idLiteral = typeof keyNum === 'number' ? keyNum : JSON.stringify(keyRaw);
if (LOADER_ONLY && fs.existsSync(path.join(UNPACKED_DIR, `module_${paddedId}.js`))) {
codeModuleIds.push(keyNum);
codeCount++;
continue;
}
let beautified = valueSrc;
try {
beautified = await prettier.format('(' + valueSrc + ')', {
@@ -143,20 +199,24 @@ function tryExtractResource(valueSrc) {
console.log(` 完成。资源模块: ${resourceCount}, 代码模块: ${codeCount}, 美化失败: ${failCount}`);
console.log('[4/7] 写入 resources.json...');
fs.writeFileSync(
RESOURCES_JSON_PATH,
JSON.stringify(
{
type: 'webpack-resource-map',
generatedAt: new Date().toISOString(),
totalResources: resourceCount,
resourceMap,
},
null,
0
),
'utf8'
);
if (LOADER_ONLY && fs.existsSync(RESOURCES_JSON_PATH)) {
console.log(' --loader-only:复用已有 resources.json');
} else {
fs.writeFileSync(
RESOURCES_JSON_PATH,
JSON.stringify(
{
type: 'webpack-resource-map',
generatedAt: new Date().toISOString(),
totalResources: resourceCount,
resourceMap,
},
null,
0
),
'utf8'
);
}
console.log(` resources.json 大小: ${(fs.statSync(RESOURCES_JSON_PATH).size / 1024).toFixed(1)} KB`);
console.log('[5/7] 提取原 webpack runtime...');
@@ -170,7 +230,7 @@ function tryExtractResource(valueSrc) {
}
console.log(` runtime 代码长度: ${runtimeCode.length} 字符`);
console.log('[6/7] 生成主文件 main.b7114b43.js...');
console.log(`[6/7] 生成主文件 ${path.basename(BUNDLE_PATH)}...`);
const codeIdsArrayLiteral = codeModuleIds
.map((id) => (typeof id === 'number' ? id : JSON.stringify(id)))
.join(',');
@@ -211,14 +271,49 @@ await Promise.all(__code_ids__.map(async (__id__) => {
var __webpack_modules__ = window.__modules__;
`;
// 6.3 hornet 工具链自动加载(注入在 runtime 入口模块加载之后、return 之前)
const hornetCode = WITH_HORNET
? `
// ===== 暴露 webpack require(hornet 工具链 hook_gamemode/hornet_lib 依赖)=====
window.__wp_require__ = __webpack_require__;
// ===== 加载 hornet_bundle(默认关闭:URL 加 ?hornet=1 才加载,避免日志遮挡)=====
if (/[?&]hornet=1(&|$)/.test(location.search)) {
try {
const __hornet_url__ = __base_url__ + 'unpacked/hornet_bundle.js';
const __hornet_resp__ = await fetch(__hornet_url__);
if (__hornet_resp__.ok) {
const __hornet_code__ = await __hornet_resp__.text();
(0, eval)(__hornet_code__);
console.log('[Hornet] bundle 已加载');
} else {
console.warn('[Hornet] bundle 加载失败: HTTP ' + __hornet_resp__.status);
}
} catch (__hornet_err__) {
console.error('[Hornet] bundle 加载异常:', __hornet_err__);
}
}
`
: '';
let runtimeWithHornet = runtimeCode;
if (WITH_HORNET) {
const retIdx = runtimeWithHornet.lastIndexOf('return __webpack_exports__');
if (retIdx >= 0) {
runtimeWithHornet = runtimeWithHornet.slice(0, retIdx) + hornetCode + runtimeWithHornet.slice(retIdx);
} else {
console.warn(' [WARN] 未找到 return __webpack_exports__,跳过 hornet 注入');
}
}
const newBundle =
UMD_PREFIX +
'\n' +
loaderCode +
'\n// ===== 原 webpack runtime (一字未改) =====\n' +
runtimeCode +
runtimeWithHornet +
';\n' +
UMD_SUFFIX;
UMD_SUFFIX(BUNDLE_PATH);
fs.writeFileSync(MAIN_PATH, newBundle, 'utf8');
console.log(` 主文件大小: ${(newBundle.length / 1024).toFixed(1)} KB`);
+4 -2
View File
@@ -5,8 +5,10 @@ const path = require('path');
const http = require('http');
const ROOT = path.resolve(__dirname, '..', '3dtank.com');
const MAIN_PATH = path.join(ROOT, 'play', 'static', 'js', 'main.b7114b43.js');
const UNPACKED_DIR = path.join(ROOT, 'play', 'static', 'js', 'unpacked');
const JS_DIR = path.join(ROOT, 'play', 'static', 'js');
// 自动定位切割版主文件 main.<hash>.js
const MAIN_PATH = path.join(JS_DIR, fs.readdirSync(JS_DIR).filter((f) => /^main\.[0-9a-f]+\.js$/.test(f)).sort().pop() || '');
const UNPACKED_DIR = path.join(JS_DIR, 'unpacked');
const RESOURCES_JSON_PATH = path.join(UNPACKED_DIR, 'resources.json');
// 简单的 HTTP 服务器,提供静态文件