Files
TankIC/tools/hornet_freelook.js
T
TermiNexus 90c377ad79 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 重新生成。
2026-09-17 19:59:40 +08:00

341 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ===== 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
};
})();