- 双向校验误判:q100_1 / r11l / jyw / t101_1 在新版同名但含义不同(普通数值 / 别的方法),被当作未变而跳过 - 强制替换:q100_1 到 m10b_1(坦克管理)、r11l 到 j11y(坦克列表)、jyw 到 lz4(取实体)、t101_1 到 p10c_1 - 覆盖 hook_gamemode.js(文件名不匹配 hornet_ 被漏掉) - 物理组件字段 gzh_1 改为 rzf_1(新版 gzh_1 已是 TerminatorCC 字段,非物理) - 移除已不存在的 xyu(GameMode),改用 gm.lz4()(等价:取 GameMode 实体) - 重新生成 hornet_bundle.js(10 模块 / 249229 字节)并同步 unpacked
353 lines
14 KiB
JavaScript
353 lines
14 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.lz4().jz1_1;
|
||
mainCamera = scene.iyx_1;
|
||
ff = scene.gyw_1.d75_1;
|
||
if (!mainCamera || !mainCamera.u35_1 || !mainCamera.r35_1 || !mainCamera.o37) {
|
||
throw new Error('相机字段缺失');
|
||
}
|
||
console.log('[自由视角] 主相机:', mainCamera.constructor.name,
|
||
'l35=(' + mainCamera.u35_1.f20_1.toFixed(2) + ',' + mainCamera.u35_1.g20_1.toFixed(2) + ',' + mainCamera.u35_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.u35_1.f20_1 = freePitch; // pitch (f)
|
||
mainCamera.u35_1.g20_1 = freeRoll; // roll (g) 保持初始值
|
||
mainCamera.u35_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.u35_1.f20_1;
|
||
freeRoll = mainCamera.u35_1.g20_1; // roll 保持初始值
|
||
freeYaw = mainCamera.u35_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;
|
||
|
||
// 换图自刷新:gameMode 实例变化时整体重启,重新 Hook 新相机的 o37
|
||
var NewGm = window.__gameMode__;
|
||
if (NewGm && NewGm !== gm) {
|
||
console.log('[自由视角] 检测到新战斗,正在重启以绑定新相机...');
|
||
self.Stop();
|
||
_start();
|
||
return;
|
||
}
|
||
// 旧实例已失效、新实例尚未捕获:跳过本帧
|
||
if (!window.__gameMode__) { rafId = requestAnimationFrame(loop); 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
|
||
};
|
||
})();
|