旧 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 重新生成。
341 lines
13 KiB
JavaScript
341 lines
13 KiB
JavaScript
// ===== 3DTank自由视角 =====
|
||
// 在浏览器控制台运行(需先运行 hook_gamemode.js 和 hornet_lib.js)
|
||
// 功能:解锁主相机,WASD移动/鼠标右键旋转/QE升降/Shift加速/滚轮调速
|
||
// 原理:Hook mainCamera.h37,在相机矩阵更新前覆盖l35_1/k35_1参数
|
||
|
||
(function() {
|
||
function _start() {
|
||
console.log('===== 3DTank自由视角启动 =====');
|
||
|
||
if (!window.__gameMode__) {
|
||
console.error('请先运行 hook_gamemode.js');
|
||
return;
|
||
}
|
||
|
||
if (window.__hornetFreeLook__) {
|
||
window.__hornetFreeLook__.Stop();
|
||
console.log('已停止旧自由视角');
|
||
}
|
||
|
||
var gm = window.__gameMode__;
|
||
|
||
// ===== 引用公共库 =====
|
||
var lib = window.__hornetLib__;
|
||
if (!lib) {
|
||
console.error('[自由视角] 请先运行 hornet_lib.js');
|
||
return;
|
||
}
|
||
function GetPlayerTank() { return lib.GetPlayerTank(gm); }
|
||
function GetTankPosition(tank) { return lib.GetTankPosition(tank); }
|
||
|
||
// ===== 获取游戏渲染对象 =====
|
||
var scene = null, mainCamera = null, ff = null;
|
||
var initError = null;
|
||
|
||
try {
|
||
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.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);
|
||
}
|
||
if (initError) return;
|
||
|
||
// ===== 自由视角状态 =====
|
||
var freeLookActive = false;
|
||
var running = true;
|
||
|
||
// 自由视角参数(从当前相机参数初始化)
|
||
var freeYaw = 0, freePitch = 0, freeRoll = 0;
|
||
var freePosX = 0, freePosY = 0, freePosZ = 0;
|
||
var moveSpeed = 1200; // 基础移动速度(世界单位/秒)
|
||
var speedMultiplier = 1; // 滚轮调速倍数(0.2~5.0)
|
||
|
||
// 按键状态
|
||
var keys = {};
|
||
var mouseSensitivity = 0.005; // 鼠标灵敏度
|
||
|
||
// ===== Hook mainCamera.h37 =====
|
||
var hadOwnH37 = mainCamera.hasOwnProperty('o37');
|
||
var origH37 = mainCamera.o37;
|
||
|
||
function applyFreeLookParams() {
|
||
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 更新后设置)
|
||
// o37 行向量布局(列主序存储):
|
||
// 行0 = (d[0], d[4], d[8]) = right 向量
|
||
// 行1 = (d[1], d[5], d[9]) = up 向量
|
||
// 行2 = (d[2], d[6], d[10]) = -forward 向量
|
||
var cachedFwdX = 0, cachedFwdY = 0, cachedRightX = 0, cachedRightY = 0;
|
||
var cachedInitialized = false;
|
||
var suspended = false; // 后视镜等模块渲染时临时暂停 hook
|
||
|
||
mainCamera.o37 = function() {
|
||
if (freeLookActive && !suspended) {
|
||
applyFreeLookParams();
|
||
origH37.call(this);
|
||
// 从更新后的 viewMatrix (u36_1) 提取前方向和右方向(水平分量 x,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.o37 已 Hook');
|
||
|
||
// ===== 开启/关闭自由视角 =====
|
||
function enableFreeLook() {
|
||
// 从当前相机参数初始化(平滑过渡)
|
||
// l35_1: f=pitch, g=roll, h=yaw
|
||
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();
|
||
}
|
||
|
||
function disableFreeLook() {
|
||
freeLookActive = false;
|
||
keys = {}; // 清空按键状态,防止卡死
|
||
console.log('[自由视角] 已关闭,恢复游戏相机控制');
|
||
updateHud();
|
||
}
|
||
|
||
function toggleFreeLook() {
|
||
if (freeLookActive) disableFreeLook();
|
||
else enableFreeLook();
|
||
}
|
||
|
||
// ===== 键盘事件(capture阶段拦截,防止游戏接收WASD) =====
|
||
// 用 e.code(物理键位)匹配,比 e.key 更稳定,不受 Shift 修饰影响
|
||
var keyMap = {
|
||
'KeyW': 'w', 'KeyA': 'a', 'KeyS': 's', 'KeyD': 'd',
|
||
'KeyQ': 'q', 'KeyE': 'e',
|
||
'Space': ' ',
|
||
'ShiftLeft': 'shift', 'ShiftRight': 'shift'
|
||
};
|
||
|
||
function onKeyDown(e) {
|
||
// 焦点在输入框/选择框时不拦截(允许面板输入)
|
||
var ae = document.activeElement;
|
||
if (ae && (ae.tagName === 'INPUT' || ae.tagName === 'SELECT' || ae.tagName === 'TEXTAREA')) return;
|
||
// Alt+F 切换(全局,无论是否开启)
|
||
if (e.altKey && (e.key === 'f' || e.key === 'F')) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
toggleFreeLook();
|
||
return;
|
||
}
|
||
if (!freeLookActive) return;
|
||
var key = keyMap[e.code] || e.key.toLowerCase();
|
||
if (keyMap[e.code]) {
|
||
keys[key] = true;
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
}
|
||
}
|
||
|
||
function onKeyUp(e) {
|
||
// 焦点在输入框/选择框时不拦截
|
||
var ae = document.activeElement;
|
||
if (ae && (ae.tagName === 'INPUT' || ae.tagName === 'SELECT' || ae.tagName === 'TEXTAREA')) return;
|
||
var key = keyMap[e.code] || e.key.toLowerCase();
|
||
if (keyMap[e.code]) {
|
||
keys[key] = false; // 总是清理按键状态,防止卡死
|
||
// 只在自由视角激活时拦截事件,暂停时放行给游戏
|
||
if (freeLookActive) {
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
}
|
||
}
|
||
}
|
||
|
||
// ===== 鼠标事件(自由视角激活时鼠标移动直接旋转,不按键) =====
|
||
function onMouseMove(e) {
|
||
if (!freeLookActive) return;
|
||
// 鼠标左移→视角左转(yaw减),鼠标上移→视角上抬(pitch增)
|
||
freeYaw -= e.movementX * mouseSensitivity;
|
||
freePitch -= e.movementY * mouseSensitivity;
|
||
// 限制俯仰角到±80°,避免接近90°时的万向锁翻转
|
||
var limit = 80 * Math.PI / 180;
|
||
if (freePitch > limit) freePitch = limit;
|
||
if (freePitch < -limit) freePitch = -limit;
|
||
}
|
||
|
||
function onWheel(e) {
|
||
if (!freeLookActive) return;
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
// 滚轮上=加速,下=减速
|
||
if (e.deltaY < 0) speedMultiplier = Math.min(5.0, speedMultiplier + 0.2);
|
||
else speedMultiplier = Math.max(0.2, speedMultiplier - 0.2);
|
||
updateHud();
|
||
}
|
||
|
||
// capture 阶段拦截键盘
|
||
window.addEventListener('keydown', onKeyDown, true);
|
||
window.addEventListener('keyup', onKeyUp, true);
|
||
document.addEventListener('mousemove', onMouseMove);
|
||
window.addEventListener('wheel', onWheel, true);
|
||
|
||
// ===== HUD 显示 =====
|
||
var hud = document.createElement('div');
|
||
hud.id = 'hornetFreeLookHud';
|
||
hud.style.cssText = 'position:fixed;bottom:10px;left:10px;z-index:99999;' +
|
||
'background:rgba(0,0,0,0.7);color:#8f8;padding:6px 10px;border-radius:4px;' +
|
||
'font:12px monospace;pointer-events:none;border:1px solid rgba(120,255,120,0.5);' +
|
||
'display:none;user-select:none;';
|
||
document.body.appendChild(hud);
|
||
|
||
function updateHud() {
|
||
if (!freeLookActive) { hud.style.display = 'none'; return; }
|
||
hud.style.display = 'block';
|
||
hud.innerHTML =
|
||
'[自由视角 ON] 速度:' + (moveSpeed * speedMultiplier).toFixed(0) + '/秒<br>' +
|
||
'位置:(' + freePosX.toFixed(0) + ',' + freePosY.toFixed(0) + ',' + freePosZ.toFixed(0) + ')<br>' +
|
||
'Yaw:' + freeYaw.toFixed(2) + ' Pitch:' + freePitch.toFixed(2) + '<br>' +
|
||
'WASD移动 鼠标旋转 空格上升/Shift下降 滚轮调速 Alt+F关闭';
|
||
}
|
||
|
||
// ===== 主循环:处理按键 → 更新位置 =====
|
||
var rafId = null;
|
||
var lastTime = performance.now();
|
||
|
||
function loop(now) {
|
||
if (!running) return;
|
||
var dt = (now - lastTime) / 1000;
|
||
lastTime = now;
|
||
if (dt > 0.1) dt = 0.1; // 防止卡顿后大跳
|
||
|
||
if (freeLookActive) {
|
||
var speed = moveSpeed * speedMultiplier * dt; // 每秒 moveSpeed 单位
|
||
|
||
// 相机前方向/右方向:优先用 viewMatrix 提取的真实方向(跟随鼠标朝向)
|
||
var fwdX, fwdY, rightX, rightY;
|
||
if (cachedInitialized) {
|
||
// 从 viewMatrix 提取的水平方向分量(列主序)
|
||
fwdX = cachedFwdX;
|
||
fwdY = cachedFwdY;
|
||
rightX = cachedRightX;
|
||
rightY = cachedRightY;
|
||
// 归一化水平方向(pitch 不为0时向量长度<1,保持移动速度一致)
|
||
var fwdLen = Math.sqrt(fwdX * fwdX + fwdY * fwdY);
|
||
if (fwdLen > 1e-6) { fwdX /= fwdLen; fwdY /= fwdLen; }
|
||
var rightLen = Math.sqrt(rightX * rightX + rightY * rightY);
|
||
if (rightLen > 1e-6) { rightX /= rightLen; rightY /= rightLen; }
|
||
} else {
|
||
// fallback: sin/cos(viewMatrix 尚未初始化时)
|
||
fwdX = -Math.sin(freeYaw);
|
||
fwdY = -Math.cos(freeYaw);
|
||
rightX = Math.cos(freeYaw);
|
||
rightY = -Math.sin(freeYaw);
|
||
}
|
||
|
||
var moved = false;
|
||
// W/S 前后(水平移动,可与其他键组合)
|
||
if (keys['w']) { freePosX += fwdX * speed; freePosY += fwdY * speed; moved = true; }
|
||
if (keys['s']) { freePosX -= fwdX * speed; freePosY -= fwdY * speed; moved = true; }
|
||
// A/D 左右(水平移动,可与其他键组合)
|
||
if (keys['a']) { freePosX -= rightX * speed; freePosY -= rightY * speed; moved = true; }
|
||
if (keys['d']) { freePosX += rightX * speed; freePosY += rightY * speed; moved = true; }
|
||
// 空格上升 / Shift 下降(绝对 z 轴,可与 WASD 组合)
|
||
if (keys[' ']) { freePosZ += speed; moved = true; }
|
||
if (keys['shift']) { freePosZ -= speed; moved = true; }
|
||
// Q/E 升降(备用)
|
||
if (keys['q']) { freePosZ -= speed; moved = true; }
|
||
if (keys['e']) { freePosZ += speed; moved = true; }
|
||
|
||
if (moved) updateHud();
|
||
}
|
||
|
||
rafId = requestAnimationFrame(loop);
|
||
}
|
||
rafId = requestAnimationFrame(loop);
|
||
|
||
// ===== 对外接口 =====
|
||
var self = {
|
||
Stop: function() {
|
||
running = false;
|
||
if (rafId) cancelAnimationFrame(rafId);
|
||
disableFreeLook();
|
||
// 恢复 h37
|
||
if (hadOwnH37) mainCamera.o37 = origH37;
|
||
else delete mainCamera.o37;
|
||
// 移除监听
|
||
window.removeEventListener('keydown', onKeyDown, true);
|
||
window.removeEventListener('keyup', onKeyUp, true);
|
||
document.removeEventListener('mousemove', onMouseMove);
|
||
window.removeEventListener('wheel', onWheel, true);
|
||
if (hud.parentNode) hud.parentNode.removeChild(hud);
|
||
window.__hornetFreeLook__ = null;
|
||
console.log('[自由视角] 已停止');
|
||
},
|
||
Status: function() {
|
||
// 返回字符串供面板 HUD 显示(其他工具也是字符串)
|
||
if (!running) return '未运行';
|
||
return freeLookActive ? '已开启' : '已暂停';
|
||
},
|
||
GetRunning: function() { return freeLookActive; },
|
||
IsActive: function() { return freeLookActive; },
|
||
Toggle: function() { toggleFreeLook(); },
|
||
Enable: function() { if (!freeLookActive) enableFreeLook(); },
|
||
Disable: function() { if (freeLookActive) disableFreeLook(); },
|
||
// 供其他模块(雷达/3D透视)获取虚拟相机参数
|
||
GetPosition: function() { return freeLookActive ? { x: freePosX, y: freePosY, z: freePosZ } : null; },
|
||
GetDirection: function() { return freeLookActive ? { yaw: freeYaw, pitch: freePitch } : null; },
|
||
// 供后视镜等模块渲染时临时暂停 h37 hook
|
||
SetSuspended: function(s) { suspended = s; }
|
||
};
|
||
|
||
window.__hornetFreeLook__ = self;
|
||
window.__hornetTools__ = window.__hornetTools__ || {};
|
||
window.__hornetTools__.FreeLook = self;
|
||
|
||
// 启动时自动激活自由视角
|
||
enableFreeLook();
|
||
|
||
console.log('[自由视角] 就绪 — 已激活,Alt+F 切换激活/暂停');
|
||
}
|
||
|
||
function _stop() {
|
||
if (window.__hornetFreeLook__) window.__hornetFreeLook__.Stop();
|
||
}
|
||
function _status() {
|
||
return window.__hornetFreeLook__ ? window.__hornetFreeLook__.Status() : '未运行';
|
||
}
|
||
function _getRunning() {
|
||
return !!(window.__hornetFreeLook__ && window.__hornetFreeLook__.GetRunning());
|
||
}
|
||
|
||
window.__hornetTools__ = window.__hornetTools__ || {};
|
||
window.__hornetTools__.FreeLook = {
|
||
Start: _start,
|
||
Stop: _stop,
|
||
Status: _status,
|
||
GetRunning: _getRunning
|
||
};
|
||
})();
|