Files
TankIC/tools/hornet_mouse.js
T
TermiNexus 4005213226 feat(tools): 新版本工具链移植 + 本地镜像单包加载,中继改为原样直发与异步有序落盘
- hornet 工具链:无 ght 启动;透视冻结节点拖拽跟随、金箱标记、性能优化;雷达死亡红叉;鼠标稳定器;窗口布局持久化

- 本地镜像:切割与单包加载(pack_unpacked.js)+ SW 单包缓存 + F5 兜底刷新;入口更新为 main.9f4e7a89.js

- 中继 wss_server.js:转发改 socket 反压的原样直发(零等待零缓存);落盘改单条有序异步队列(默认开启);新增反压看门狗(默认 60s)
2026-10-11 19:35:54 +08:00

246 lines
10 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.
// hornet_mouse.js — 鼠标输入稳定器
// 目标:缓解原版「指针锁定下鼠标乱晃 / 难控制」的问题(游戏自身行为,非本工具链引入)。
//
// 游戏侧机制(module_03569):
// · 锁定:Lh() 先 requestPointerLock({unadjustedMovement:true})(原始输入),**失败则静默回退**
// 成不带 unadjustedMovement 的普通锁定 —— 后者会带上 OS 鼠标加速,手感立刻变飘。
// · 视角:mousemove 里直接把 e.movementX / e.movementY 喂给输入管理器
// (fal_1.ia9(qh(), movementX, movementY)),没有任何尖峰/跳变过滤。
//
// 本模块做三件事,全部可在面板「鼠标稳定」里开关:
// ① 强制原始输入:接管 Element.prototype.requestPointerLock,始终带 unadjustedMovement,
// 并把「回退/失败」打到控制台(手感异常的第一嫌疑人)。
// ② 锁后/恢复跳变抑制:指针锁定刚建立、或事件流中断(切窗口/失焦)后恢复的头 DropMs 毫秒内
// 丢弃 mousemove —— 这一两下常带巨大 movementX/Y,会直接把视角甩飞。
// ③ 尖峰钳制:单事件 |movementX| / |movementY| 超过 MaxDelta(0 = 关闭)时截断,
// 抑制驱动/焦点抖动造成的瞬时尖峰。
//
// 状态:HUD 里显示「鼠标: RAW 48/s 尖峰320 钳0 丢1」,或
// console.log(window.__hornetTools__.Mouse.GetStatus())
(function () {
'use strict';
var lib = window.__hornetLib__;
if (!lib) {
console.error('[鼠标] 请先加载 hornet_lib.js');
return;
}
// ===== 配置(localStorage 持久化)=====
var CONFIG_KEY = 'hornet_mouse_cfg';
var Config = {
ForceRaw: true, // 强制原始输入(unadjustedMovement),关掉则完全不干预指针锁定
DropMs: 120, // 锁定建立 / 事件流恢复后的忽略时长(ms,0 = 不丢事件)
GapMs: 250, // 事件间隔超过它视为「恢复」,同样触发忽略窗口(ms)
MaxDelta: 0 // 单事件位移钳制阈值(px,0 = 不钳制)
};
try {
var Saved = JSON.parse(localStorage.getItem(CONFIG_KEY) || '{}');
if (typeof Saved.ForceRaw === 'boolean') Config.ForceRaw = Saved.ForceRaw;
if (typeof Saved.DropMs === 'number') Config.DropMs = Saved.DropMs;
if (typeof Saved.GapMs === 'number') Config.GapMs = Saved.GapMs;
if (typeof Saved.MaxDelta === 'number') Config.MaxDelta = Saved.MaxDelta;
} catch (e) {}
function SaveConfig() {
try { localStorage.setItem(CONFIG_KEY, JSON.stringify(Config)); } catch (e) {}
}
function Now() {
return (window.performance && window.performance.now) ? window.performance.now() : Date.now();
}
// ===== 运行状态与统计 =====
var Stat = {
running: false,
rawMode: '未知', // 原始输入 / 带加速 / 未知
locked: false,
dropUntil: 0, // 该时刻前的 mousemove 全部丢弃
dropped: 0,
clamped: 0,
events: 0,
maxAbs: 0, // 本统计窗口内最大单事件位移
lastEventAt: 0,
rate: 0,
rateEvents: 0,
rateWindow: 0,
lastAnomalyAt: 0
};
var OrigRequestPointerLock = null;
var Attached = false;
// ===== ① 指针锁定:强制原始输入 =====
function InstallLockPatch() {
if (OrigRequestPointerLock) return;
OrigRequestPointerLock = Element.prototype.requestPointerLock;
Element.prototype.requestPointerLock = function (Options) {
var Opt = Options;
if (Config.ForceRaw && !(Opt && Opt.unadjustedMovement)) {
Opt = { unadjustedMovement: true }; // 显式请求原始输入(不改调用方对象)
}
// 只有在「本来没锁」时才算一次真正的锁定(避免已锁定时的重复请求反复刷新忽略窗口,
// 那会让鼠标一直被丢弃而失去响应)
var WasLocked = !!document.pointerLockElement;
Stat.locked = true;
if (!WasLocked) {
Stat.lastEventAt = 0; // 重新计时,避免把旧的间隔当成「恢复」
Stat.dropUntil = Config.DropMs > 0 ? (Now() + Config.DropMs) : 0;
}
var P;
try {
P = OrigRequestPointerLock.call(this, Opt);
} catch (Err) {
console.error('[鼠标] requestPointerLock 调用异常:', Err && Err.message);
return null;
}
var WantRaw = !!(Opt && Opt.unadjustedMovement);
if (P && typeof P.then === 'function') {
P.then(
function () {
Stat.rawMode = WantRaw ? '原始输入' : '带加速';
console.log('[鼠标] 指针锁定成功,模式=' + Stat.rawMode +
(WantRaw ? '' : '(带 OS 加速,手感会飘;可在面板勾选「强制原始输入」)'));
},
function (Err) {
Stat.rawMode = '未知';
console.warn('[鼠标] 指针锁定失败: ' + (Err && Err.message));
},
);
} else {
Stat.rawMode = WantRaw ? '原始输入' : '带加速';
}
return P;
};
}
function UninstallLockPatch() {
if (!OrigRequestPointerLock) return;
Element.prototype.requestPointerLock = OrigRequestPointerLock;
OrigRequestPointerLock = null;
}
// ===== ②③ mousemove 拦截:丢弃跳变 + 钳制尖峰 =====
// 注册在 window 的捕获阶段:一定早于游戏挂在画布/document 上的处理器,丢弃/改写才有效
function OnMouseMoveCapture(e) {
Stat.events++;
var T = Now();
// 事件流「恢复」判定(切窗口/失焦回来时的第一下常是巨幅位移)
if (Config.GapMs > 0 && Stat.lastEventAt && (T - Stat.lastEventAt) > Config.GapMs && Config.DropMs > 0) {
Stat.dropUntil = T + Config.DropMs;
}
Stat.lastEventAt = T;
// ② 忽略窗口内:整条事件丢弃(stopImmediatePropagation 阻断游戏处理器)
if (Stat.dropUntil && T < Stat.dropUntil) {
Stat.dropped++;
e.stopImmediatePropagation();
return;
}
var Dx = e.movementX, Dy = e.movementY;
if (typeof Dx !== 'number' || typeof Dy !== 'number') return;
var Ax = Math.abs(Dx), Ay = Math.abs(Dy);
if (Ax > Stat.maxAbs) Stat.maxAbs = Ax;
if (Ay > Stat.maxAbs) Stat.maxAbs = Ay;
// ③ 尖峰钳制:改写事件上的 movementX/Y(own property 遮蔽原型 getter)
if (Config.MaxDelta > 0 && (Ax > Config.MaxDelta || Ay > Config.MaxDelta)) {
var Nx = Ax > Config.MaxDelta ? (Dx > 0 ? Config.MaxDelta : -Config.MaxDelta) : Dx;
var Ny = Ay > Config.MaxDelta ? (Dy > 0 ? Config.MaxDelta : -Config.MaxDelta) : Dy;
try {
Object.defineProperty(e, 'movementX', { value: Nx, configurable: true });
Object.defineProperty(e, 'movementY', { value: Ny, configurable: true });
Stat.clamped++;
} catch (Err) {
// 属性不可写则退化为丢弃该事件(同样能压住甩飞)
Stat.clamped++;
e.stopImmediatePropagation();
return;
}
}
// 事件率统计(2s 窗口),尖峰异常时节流提示,帮助定位是「抖动」还是「跳变」
Stat.rateEvents++;
if (!Stat.rateWindow) Stat.rateWindow = T;
if (T - Stat.rateWindow >= 2000) {
Stat.rate = Math.round((Stat.rateEvents * 1000) / (T - Stat.rateWindow));
Stat.rateWindow = T;
Stat.rateEvents = 0;
var Bad = Stat.maxAbs > 400 && (T - Stat.lastAnomalyAt) > 3000;
if (Bad) {
Stat.lastAnomalyAt = T;
console.warn('[鼠标] 出现大位移尖峰: ' + Stat.maxAbs.toFixed(0) + 'px(事件率 ' + Stat.rate +
'/s,累计钳制 ' + Stat.clamped + ' 次)。若频繁甩飞,把「尖峰钳制」调成 250~400 试试');
}
Stat.maxAbs = 0; // 只统计窗口内最大值
}
}
// ===== 启动 / 停止 =====
function _start() {
if (Stat.running) return;
if (Config.ForceRaw) InstallLockPatch();
if (!Attached) {
window.addEventListener('mousemove', OnMouseMoveCapture, true);
Attached = true;
}
Stat.running = true;
console.log('[鼠标] 稳定器已启动 强制原始输入=' + Config.ForceRaw +
' 锁后忽略=' + Config.DropMs + 'ms 尖峰钳制=' + (Config.MaxDelta > 0 ? Config.MaxDelta + 'px' : '关'));
}
function _stop() {
if (!Stat.running) return;
UninstallLockPatch();
if (Attached) {
window.removeEventListener('mousemove', OnMouseMoveCapture, true);
Attached = false;
}
Stat.running = false;
Stat.dropped = 0; Stat.clamped = 0; Stat.events = 0; Stat.maxAbs = 0;
Stat.dropUntil = 0; Stat.lastEventAt = 0; Stat.rawMode = '未知';
console.log('[鼠标] 稳定器已停止');
}
var Api = {
config: Config,
_start: _start,
_stop: _stop,
setForceRaw: function (On) { Config.ForceRaw = !!On; SaveConfig(); if (Stat.running) { if (On) InstallLockPatch(); else UninstallLockPatch(); } },
setDropMs: function (Ms) { Config.DropMs = Math.max(0, Number(Ms) || 0); SaveConfig(); },
setMaxDelta: function (Px) { Config.MaxDelta = Math.max(0, Number(Px) || 0); SaveConfig(); },
getStatus: function () {
return {
running: Stat.running, rawMode: Stat.rawMode, rate: Stat.rate,
maxAbs: Math.round(Stat.maxAbs), clamped: Stat.clamped, dropped: Stat.dropped, events: Stat.events
};
}
};
window.__hornetMouse__ = Api;
var Tool = {
Start: function () { _start(); },
Stop: function () { _stop(); },
Status: function () {
if (!Stat.running) return '已停止';
return Stat.rawMode + ' ' + Stat.rate + '/s 尖峰' + Math.round(Stat.maxAbs) +
' 钳' + Stat.clamped + ' 丢' + Stat.dropped;
},
GetRunning: function () { return Stat.running; },
SetForceRaw: function (On) { Api.setForceRaw(On); },
GetForceRaw: function () { return Config.ForceRaw; },
SetDropMs: function (Ms) { Api.setDropMs(Ms); },
GetDropMs: function () { return Config.DropMs; },
SetMaxDelta: function (Px) { Api.setMaxDelta(Px); },
GetMaxDelta: function () { return Config.MaxDelta; },
GetStatus: function () { return Api.getStatus(); }
};
window.__hornetTools__ = window.__hornetTools__ || {};
window.__hornetTools__.Mouse = Tool;
console.log('[3DTank] 鼠标稳定模块已加载');
})();