Files
TankIC/tools/hornet_panel.js
T
TermiNexus dcf5dfce5c fix(tools): 工具链符号对齐新版 bundle(修复 HORNET 功能大面积失效)
- 根因:新版 main.7cfa4058 重拆后游戏符号整体平移,工具里写死的字段名(pyt_1/q100_1/r11l/oyz/jyw/cyw_1/dyw_1…)全部对不上 → 找不到组件/相机/场景

- 新增 tools/apply_symbol_map.js:按 map_symbols 映射批量替换,4 道安全规则:① 长名或含下划线 ② 排除 JS 关键字/内置名黑名单 ③ 游戏符号命名特征(含数字或 _数字 结尾) ④ 双向校验(旧名在新 bundle 仍存在→视为未变跳过;新名不存在→映射错误丢弃)

- 单遍替换,避免链式污染(映射表新旧同名复用时会被二次替换)

- 应用到 12 个 hornet_*.js 共 231 处(跳过 25764 个未变符号)

- 重新生成 hornet_bundle.js(10 模块 / 249263 字节)并同步 unpacked
2026-10-16 14:55:39 +08:00

921 lines
36 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 控制面板 =====
// 功能:Alt+T 切换面板显示,控制各工具开关
// 左上角 HUD 实时显示 GHT 捕获状态和各工具运行状态
// HUD 和面板支持鼠标拖动
// 依赖:hook_gamemode.js + hornet_lib.js + 各 hornet_*.js 工具模块
(function() {
console.log('[3DTank] 控制面板加载');
// ===== 清理旧面板 =====
if (window.__hornetPanel__) {
window.__hornetPanel__.Destroy();
console.log('已清理旧控制面板');
}
// 通用拖动功能
// elem: 要拖动的元素,handle: 触发拖动的元素(默认=elem)
// ignoreTags: 点击这些标签时不触发拖动(如 BUTTON)
// key: 可选,传入后把窗口位置存到 localStorage(hornet_layout_<key>)并在创建时还原
// 实现要点:Pointer Events + setPointerCapture 把指针"锁"在 handle 上,
// pointerup/pointercancel 必定回到 handle;并在 window 捕获阶段加兜底,
// 避免面板内 stopPropagation 或游戏画布抢事件导致"松手后仍粘着鼠标"。
// 返回:{ End, IsActive },供隐藏面板时强制结束拖动
function MakeDraggable(elem, handle, ignoreTags, key) {
handle = handle || elem;
ignoreTags = ignoreTags || ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'];
handle.style.cursor = 'move';
var StorageKey = key ? ('hornet_layout_' + key) : null;
function ClampX(v) { return Math.max(0, Math.min(window.innerWidth - 40, v)); }
function ClampY(v) { return Math.max(0, Math.min(window.innerHeight - 24, v)); }
// 还原上次保存的位置
if (StorageKey) {
try {
var __saved = localStorage.getItem(StorageKey);
if (__saved) {
var __p = JSON.parse(__saved);
if (__p && typeof __p.left === 'number' && typeof __p.top === 'number') {
elem.style.transform = 'none';
elem.style.left = ClampX(__p.left) + 'px';
elem.style.top = ClampY(__p.top) + 'px';
}
}
} catch(e) {}
}
function SavePos() {
if (!StorageKey) return;
try {
var r = elem.getBoundingClientRect();
localStorage.setItem(StorageKey, JSON.stringify({ left: Math.round(r.left), top: Math.round(r.top) }));
} catch(e) {}
}
var Active = false;
var PointerId = null;
var GrabX = 0;
var GrabY = 0;
function MoveTo(ClientX, ClientY) {
elem.style.left = ClampX(ClientX - GrabX) + 'px';
elem.style.top = ClampY(ClientY - GrabY) + 'px';
}
function Begin(ClientX, ClientY) {
var Rect = elem.getBoundingClientRect();
// 移除 transform 居中,改用绝对定位,避免拖动起点跳变
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;
handle.style.cursor = 'grabbing';
}
function End() {
if (!Active) return;
Active = false;
if (PointerId !== null && handle.releasePointerCapture) {
try { handle.releasePointerCapture(PointerId); } catch (Err) {}
}
PointerId = null;
handle.style.cursor = 'move';
SavePos();
}
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__ 获取)=====
function GetToolList() {
var tools = window.__hornetTools__ || {};
return [
{ key: 'Radar', label: '雷达', obj: tools.Radar },
{ key: 'Wireframe', label: '3D透视', obj: tools.Wireframe, hasMode: true },
{ key: 'Rearview', label: '后视镜', obj: tools.Rearview },
{ key: 'AimWarning', label: '瞄准警告', obj: tools.AimWarning },
{ key: 'FreeLook', label: '自由视角', obj: tools.FreeLook },
{ key: 'WarSandbox', label: '战争沙盘', obj: tools.WarSandbox },
{ key: 'Mouse', label: '鼠标稳定', obj: tools.Mouse }
];
}
// ===== 工具启用状态(持久化 + 默认启用)=====
// 默认除「自由视角」外全部开启:自由视角会直接接管相机,默认开启过于突兀
var ToolKeys = ['Radar', 'Wireframe', 'Rearview', 'AimWarning', 'FreeLook', 'WarSandbox', 'Mouse'];
var ToolDefaults = { Radar: true, Wireframe: true, Rearview: true, AimWarning: true, FreeLook: false, WarSandbox: true, Mouse: true };
var ToolEnabled = {};
try {
var __savedEnabled = JSON.parse(localStorage.getItem('hornet_tool_enabled') || '{}');
ToolKeys.forEach(function(k) {
ToolEnabled[k] = (typeof __savedEnabled[k] === 'boolean') ? __savedEnabled[k] : ToolDefaults[k];
});
} catch(e) {
ToolKeys.forEach(function(k) { ToolEnabled[k] = ToolDefaults[k]; });
}
// 把各工具当前的运行状态写回持久化(开关时调用)
function SaveToolEnabled() {
try {
var tools = window.__hornetTools__ || {};
ToolKeys.forEach(function(k) {
var t = tools[k];
if (t && typeof t.GetRunning === 'function') ToolEnabled[k] = !!t.GetRunning();
});
localStorage.setItem('hornet_tool_enabled', JSON.stringify(ToolEnabled));
} catch(e) {}
}
// 需要 ght 才能初始化的工具(要 hook 相机 / GL)——等捕获后再启动,避免失败报错刷屏
var GameModeTools = { FreeLook: true, Rearview: true, WarSandbox: true };
// 自动按保存状态开启:覆盖层(雷达/透视/瞄准)无 ght 也能先开并出 UI;
// 每 30 帧检查一次,全部进入运行状态即停
var AutoEnableDone = false;
var AutoEnableFrame = 0;
function TryAutoEnable() {
if (AutoEnableDone) return;
AutoEnableFrame++;
if (AutoEnableFrame % 30 !== 0) return;
var HasGm = !!window.__gameMode__;
var tools = window.__hornetTools__ || {};
var pending = 0;
ToolKeys.forEach(function(k) {
if (!ToolEnabled[k]) return;
if (GameModeTools[k] && !HasGm) { pending++; return; } // 等 ght 捕获
var t = tools[k];
if (!t || typeof t.Start !== 'function') return;
if (typeof t.GetRunning === 'function' && t.GetRunning()) return;
pending++;
try { t.Start(); } catch(e) {}
});
if (pending === 0) {
AutoEnableDone = true;
UpdateButtons();
console.log('[3DTank] 已按保存状态自动开启工具(覆盖层无 ght 也可先开;后视镜/沙盘/自由视角等 ght 捕获后开)');
}
}
// ===== HUD(左上角状态显示)—— 已按需求移除:**不挂载到页面**,等于不再显示。
// 注意:保留 hud 对象与 UpdateHud 循环 —— 循环里还负责 TryAutoEnable()(按保存状态自动开启工具)。
var hud = document.createElement('div');
hud.id = '3dtank-hud';
// 原样式 + document.body.appendChild(hud) 已移除(那就是常驻左上角的「3DTank 控制台」小窗)
var HudDrag = MakeDraggable(hud, null, null, 'hud');
// ===== 面板(可拖动,Alt+T 切换)=====
var panel = document.createElement('div');
panel.id = '3dtank-panel';
// 初始居中:用 left/top 50% + transform,拖动后移除 transform
panel.style.cssText =
'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:100001;' +
'background:rgba(12,16,24,0.72);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);' +
'border:1px solid rgba(125,249,255,0.2);border-radius:12px;' +
'padding:18px 22px;color:#e8eef5;font:13px Consolas,monospace;min-width:340px;' +
'box-shadow:0 8px 32px rgba(0,0,0,0.6),0 0 1px rgba(125,249,255,0.3),inset 0 1px 0 rgba(255,255,255,0.05);' +
'display:none;user-select:none;-webkit-user-select:none;';
// 标题(可拖动手柄)
var title = document.createElement('div');
title.textContent = '3DTank 控制面板';
title.style.cssText =
'font-size:16px;font-weight:bold;color:#7df9ff;margin-bottom:4px;' +
'text-shadow:0 0 6px rgba(125,249,255,0.4);' +
'cursor:move;padding-bottom:8px;border-bottom:1px solid rgba(125,249,255,0.15);' +
'letter-spacing:0.5px;';
panel.appendChild(title);
var subtitle = document.createElement('div');
subtitle.textContent = 'Alt+T 切换显示/隐藏 | 拖动标题移动面板';
subtitle.style.cssText = 'font-size:11px;color:#5a7a8a;margin:6px 0 14px;';
panel.appendChild(subtitle);
// 工具按钮容器
var btnContainer = document.createElement('div');
panel.appendChild(btnContainer);
// ===== 3D透视设置子面板(可折叠)=====
var wfSettings = document.createElement('div');
wfSettings.style.cssText = 'margin-top:12px;padding-top:10px;border-top:1px solid rgba(125,249,255,0.08);';
var wfHeader = document.createElement('div');
wfHeader.style.cssText = 'display:flex;justify-content:space-between;align-items:center;cursor:pointer;color:#7df9ff;font-weight:bold;font-size:13px;';
wfHeader.innerHTML = '<span>3D透视设置</span><span id="wfToggle" style="font-size:11px;color:#5a7a8a;">▼</span>';
wfSettings.appendChild(wfHeader);
var wfBody = document.createElement('div');
wfBody.style.cssText = 'margin-top:8px;display:block;';
wfSettings.appendChild(wfBody);
var wfCollapsed = false;
wfHeader.onclick = function() {
wfCollapsed = !wfCollapsed;
wfBody.style.display = wfCollapsed ? 'none' : 'block';
document.getElementById('wfToggle').textContent = wfCollapsed ? '▶' : '▼';
};
// 行布局辅助
function MakeRow(labelText) {
var row = document.createElement('div');
row.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:6px 0;';
var lab = document.createElement('span');
lab.style.cssText = 'color:#b8c8d8;font-size:12px;';
lab.textContent = labelText;
row.appendChild(lab);
var ctrl = document.createElement('div');
ctrl.style.cssText = 'display:flex;gap:6px;align-items:center;';
row.appendChild(ctrl);
wfBody.appendChild(row);
return { row: row, ctrl: ctrl };
}
// 线宽调节
var lwRow = MakeRow('线宽');
var lwSlider = document.createElement('input');
lwSlider.type = 'range';
lwSlider.min = '0.5'; lwSlider.max = '8'; lwSlider.step = '0.5';
lwSlider.style.cssText = 'width:120px;accent-color:#7df9ff;';
var lwVal = document.createElement('span');
lwVal.style.cssText = 'color:#7df9ff;font-size:11px;min-width:28px;text-align:right;';
lwRow.ctrl.appendChild(lwSlider);
lwRow.ctrl.appendChild(lwVal);
lwSlider.oninput = function() {
var v = parseFloat(lwSlider.value);
lwVal.textContent = v.toFixed(1);
var tools = window.__hornetTools__ || {};
if (tools.Wireframe) tools.Wireframe.SetLineWidth(v);
};
// 渲染模式选择
var hmRow = MakeRow('渲染模式');
var hmSelect = document.createElement('select');
hmSelect.style.cssText =
'padding:3px 6px;border:1px solid rgba(125,249,255,0.25);border-radius:4px;' +
'background:rgba(20,30,40,0.6);color:#e8eef5;font:11px Consolas;cursor:pointer;';
var hmOptions = [
{ v: 'team', t: '队伍模式(蓝友/红敌)' },
{ v: 'whitelist', t: '白名单(仅名单内渲染)' },
{ v: 'blacklist', t: '黑名单(名单内不渲染)' }
];
hmOptions.forEach(function(o) {
var opt = document.createElement('option');
opt.value = o.v; opt.textContent = o.t;
hmSelect.appendChild(opt);
});
hmRow.ctrl.appendChild(hmSelect);
hmSelect.onchange = function() {
var tools = window.__hornetTools__ || {};
if (tools.Wireframe) tools.Wireframe.SetHighlightMode(hmSelect.value);
};
// 名单管理
var hlRow = MakeRow('名单');
var hlInput = document.createElement('input');
hlInput.type = 'text';
hlInput.placeholder = '玩家名';
hlInput.style.cssText =
'width:120px;padding:3px 6px;border:1px solid rgba(125,249,255,0.25);border-radius:4px;' +
'background:rgba(20,30,40,0.6);color:#e8eef5;font:11px Consolas;';
var hlAddBtn = document.createElement('button');
hlAddBtn.textContent = '+';
hlAddBtn.style.cssText =
'padding:3px 8px;border:1px solid rgba(46,204,113,0.4);border-radius:4px;' +
'background:rgba(46,204,113,0.2);color:#2ecc71;cursor:pointer;font:bold 12px Consolas;';
hlRow.ctrl.appendChild(hlInput);
hlRow.ctrl.appendChild(hlAddBtn);
var hlList = document.createElement('div');
hlList.style.cssText = 'margin-top:6px;max-height:120px;overflow-y:auto;font-size:11px;';
wfBody.appendChild(hlList);
function RefreshHlList() {
var tools = window.__hornetTools__ || {};
var arr = tools.Wireframe ? tools.Wireframe.GetHighlightList() : [];
hlList.innerHTML = '';
if (arr.length === 0) {
var empty = document.createElement('div');
empty.style.cssText = 'color:#5a7a8a;padding:4px 0;font-style:italic;';
empty.textContent = '(空)';
hlList.appendChild(empty);
return;
}
arr.forEach(function(name) {
var item = document.createElement('div');
item.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:3px 4px;border-bottom:1px solid rgba(125,249,255,0.05);';
var ns = document.createElement('span');
ns.style.cssText = 'color:#b8c8d8;';
ns.textContent = name;
item.appendChild(ns);
var delBtn = document.createElement('button');
delBtn.textContent = '×';
delBtn.style.cssText =
'padding:1px 6px;border:1px solid rgba(224,85,85,0.4);border-radius:3px;' +
'background:rgba(224,85,85,0.2);color:#e05555;cursor:pointer;font:bold 11px Consolas;';
delBtn.onclick = function() {
if (tools.Wireframe) tools.Wireframe.RemoveHighlight(name);
RefreshHlList();
};
item.appendChild(delBtn);
hlList.appendChild(item);
});
}
function AddFromInput() {
var name = hlInput.value.trim();
if (!name) return;
var tools = window.__hornetTools__ || {};
if (tools.Wireframe) tools.Wireframe.AddHighlight(name);
hlInput.value = '';
RefreshHlList();
}
hlAddBtn.onclick = AddFromInput;
hlInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') { e.preventDefault(); AddFromInput(); }
});
var hlClearRow = document.createElement('div');
hlClearRow.style.cssText = 'margin-top:4px;text-align:right;';
var hlClearBtn = document.createElement('button');
hlClearBtn.textContent = '清空名单';
hlClearBtn.style.cssText =
'padding:3px 8px;border:1px solid rgba(224,85,85,0.4);border-radius:4px;' +
'background:rgba(224,85,85,0.15);color:#e05555;cursor:pointer;font:11px Consolas;';
hlClearBtn.onclick = function() {
var tools = window.__hornetTools__ || {};
if (tools.Wireframe) tools.Wireframe.ClearHighlight();
RefreshHlList();
};
hlClearRow.appendChild(hlClearBtn);
wfBody.appendChild(hlClearRow);
panel.appendChild(wfSettings);
// ===== 通用可折叠设置子面板辅助函数 =====
function MakeSettingsSection(titleText) {
var sec = document.createElement('div');
sec.style.cssText = 'margin-top:10px;padding-top:8px;border-top:1px solid rgba(125,249,255,0.08);';
var hdr = document.createElement('div');
hdr.style.cssText = 'display:flex;justify-content:space-between;align-items:center;cursor:pointer;color:#7df9ff;font-weight:bold;font-size:12px;';
var toggleChar = document.createElement('span');
toggleChar.style.cssText = 'font-size:10px;color:#5a7a8a;';
toggleChar.textContent = '▶';
hdr.innerHTML = '<span>' + titleText + '</span>';
hdr.appendChild(toggleChar);
sec.appendChild(hdr);
var body = document.createElement('div');
body.style.cssText = 'margin-top:6px;display:none;';
sec.appendChild(body);
hdr.onclick = function() {
var shown = body.style.display !== 'none';
body.style.display = shown ? 'none' : 'block';
toggleChar.textContent = shown ? '▶' : '▼';
};
panel.appendChild(sec);
function row(labelText) {
var r = document.createElement('div');
r.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:5px 0;';
var lab = document.createElement('span');
lab.style.cssText = 'color:#b8c8d8;font-size:11px;';
lab.textContent = labelText;
r.appendChild(lab);
var ctrl = document.createElement('div');
ctrl.style.cssText = 'display:flex;gap:6px;align-items:center;';
r.appendChild(ctrl);
body.appendChild(r);
return { row: r, ctrl: ctrl };
}
function slider(labelText, min, max, step, fmt, onInput) {
var r = row(labelText);
var s = document.createElement('input');
s.type = 'range'; s.min = String(min); s.max = String(max); s.step = String(step);
s.style.cssText = 'width:110px;accent-color:#7df9ff;';
var v = document.createElement('span');
v.style.cssText = 'color:#7df9ff;font-size:10px;min-width:32px;text-align:right;';
s.oninput = function() {
var val = parseFloat(s.value);
v.textContent = fmt(val);
onInput(val);
};
r.ctrl.appendChild(s); r.ctrl.appendChild(v);
return {
set: function(val) { s.value = val; v.textContent = fmt(parseFloat(val)); },
get: function() { return parseFloat(s.value); }
};
}
function checkbox(labelText, onChange) {
var r = row(labelText);
var c = document.createElement('input');
c.type = 'checkbox';
c.style.cssText = 'accent-color:#7df9ff;width:16px;height:16px;cursor:pointer;';
c.onchange = function() { onChange(c.checked); };
r.ctrl.appendChild(c);
return {
set: function(on) { c.checked = !!on; },
get: function() { return c.checked; }
};
}
return { sec: sec, body: body, row: row, slider: slider, checkbox: checkbox };
}
// ===== 雷达设置 =====
var radarSec = MakeSettingsSection('雷达设置');
var radarRangeCtl = radarSec.slider('范围(m)', 30, 600, 10, function(v){ return v.toFixed(0); }, function(v) {
var tools = window.__hornetTools__ || {};
if (tools.Radar) tools.Radar.SetRange(v * 100);
});
var radarOpacityCtl = radarSec.slider('透明度', 0.2, 1.0, 0.05, function(v){ return v.toFixed(2); }, function(v) {
var tools = window.__hornetTools__ || {};
if (tools.Radar) tools.Radar.SetOpacity(v);
});
// ===== 鼠标稳定设置 =====
// 强制原始输入:游戏锁定失败会静默回退成带 OS 加速的普通锁定,手感会飘
// 锁后忽略:锁定刚建立 / 失焦恢复后的头几毫秒位移常是巨幅跳变,直接丢弃
// 尖峰钳制:单事件位移超过阈值就截断(0 = 关闭)
var mouseSec = MakeSettingsSection('鼠标稳定');
var mouseRawCtl = mouseSec.checkbox('强制原始输入', function(on) {
var tools = window.__hornetTools__ || {};
if (tools.Mouse) tools.Mouse.SetForceRaw(on);
});
var mouseDropCtl = mouseSec.slider('锁后忽略(ms)', 0, 500, 20, function(v){ return v.toFixed(0); }, function(v) {
var tools = window.__hornetTools__ || {};
if (tools.Mouse) tools.Mouse.SetDropMs(v);
});
var mouseClampCtl = mouseSec.slider('尖峰钳制(px)', 0, 1000, 25, function(v){ return v.toFixed(0); }, function(v) {
var tools = window.__hornetTools__ || {};
if (tools.Mouse) tools.Mouse.SetMaxDelta(v);
});
(function () { // 回填鼠标模块里已保存的配置
var tools = window.__hornetTools__ || {};
if (!tools.Mouse) return;
mouseRawCtl.set(tools.Mouse.GetForceRaw());
mouseDropCtl.set(tools.Mouse.GetDropMs());
mouseClampCtl.set(tools.Mouse.GetMaxDelta());
})();
// ===== 后视镜设置 =====
var rvSec = MakeSettingsSection('后视镜设置');
var rvFovCtl = rvSec.slider('FOV', 0.5, 2.5, 0.05, function(v){ return v.toFixed(2); }, function(v) {
var tools = window.__hornetTools__ || {};
if (tools.Rearview) tools.Rearview.SetFov(v);
});
var rvOpacityCtl = rvSec.slider('透明度', 0.2, 1.0, 0.05, function(v){ return v.toFixed(2); }, function(v) {
var tools = window.__hornetTools__ || {};
if (tools.Rearview) tools.Rearview.SetOpacity(v);
});
// ===== 瞄准警告设置 =====
var awSec = MakeSettingsSection('瞄准警告设置');
var awSensCtl = awSec.slider('灵敏度', 0.3, 3.0, 0.1, function(v){ return v.toFixed(1); }, function(v) {
var tools = window.__hornetTools__ || {};
if (tools.AimWarning) tools.AimWarning.SetSensitivity(v);
});
var awSoundCtl = awSec.checkbox('警告声音', function(on) {
var tools = window.__hornetTools__ || {};
if (tools.AimWarning) tools.AimWarning.SetSound(on);
});
// ===== 战争沙盘设置 =====
var wsSec = MakeSettingsSection('战争沙盘设置');
var wsGridCtl = wsSec.checkbox('显示网格', function(on) {
var tools = window.__hornetTools__ || {};
if (tools.WarSandbox) tools.WarSandbox.SetShowGrid(on);
});
var wsMarkerCtl = wsSec.slider('标记大小', 0.5, 3.0, 0.1, function(v){ return v.toFixed(1); }, function(v) {
var tools = window.__hornetTools__ || {};
if (tools.WarSandbox) tools.WarSandbox.SetMarkerSize(v);
});
// ===== 电磁炮(RAILGUN / z4t)设置 =====
// 锁定/蓄力时长实时覆盖 window.__hornetLockSec__(游戏内 z4t.c18o() 优先读它),
// 拖动滑块**立即生效**,不需要重打包或刷新页面。
// 实测门槛:1.5s / 2.0s / 原版3~5s = 满伤;1.0s / 0.1s = 无伤害 ⇒ 门槛在 1.0~1.5s。
var rgSec = MakeSettingsSection('电磁炮设置');
var rgSecVal = 1.5;
function ApplyLock(v) { window.__hornetLockSec__ = (v > 0 ? v : undefined); }
function SaveLock(on, v) {
try {
localStorage.setItem('hornet_lock_on', on ? '1' : '0');
localStorage.setItem('hornet_lock_sec', String(v));
} catch (e) {}
}
var rgOnCtl = rgSec.checkbox('启用时长覆盖', function(on) {
ApplyLock(on ? rgSecVal : 0);
SaveLock(on, rgSecVal);
});
var rgLockCtl = rgSec.slider('锁定/蓄力(s)', 0.1, 5.0, 0.05, function(v) { return v.toFixed(2); }, function(v) {
rgSecVal = v;
if (rgOnCtl.get()) ApplyLock(v);
SaveLock(rgOnCtl.get(), v);
});
(function () { // 回填上次保存的配置;**默认开启覆盖、默认值 1.5s**(实测满伤的最小锁定/蓄力时长)
try {
var on = localStorage.getItem('hornet_lock_on') !== '0';
var v = parseFloat(localStorage.getItem('hornet_lock_sec'));
if (isFinite(v) && v > 0) rgSecVal = v;
rgLockCtl.set(rgSecVal);
rgOnCtl.set(on);
ApplyLock(on ? rgSecVal : 0);
} catch (e) {}
})();
// 从工具读取当前设置(打开面板时同步)
function SyncWfSettings() {
var tools = window.__hornetTools__ || {};
if (tools.Wireframe) {
var lw = tools.Wireframe.GetLineWidth();
lwSlider.value = lw; lwVal.textContent = lw.toFixed(1);
var hm = tools.Wireframe.GetHighlightMode();
hmSelect.value = hm;
}
RefreshHlList();
// 同步其他工具设置
if (tools.Radar) {
radarRangeCtl.set(tools.Radar.GetRange() / 100);
radarOpacityCtl.set(tools.Radar.GetOpacity());
}
if (tools.Rearview) {
rvFovCtl.set(tools.Rearview.GetFov());
rvOpacityCtl.set(tools.Rearview.GetOpacity());
}
if (tools.AimWarning) {
awSensCtl.set(tools.AimWarning.GetSensitivity());
awSoundCtl.set(tools.AimWarning.GetSound());
}
if (tools.WarSandbox) {
wsGridCtl.set(tools.WarSandbox.GetShowGrid());
wsMarkerCtl.set(tools.WarSandbox.GetMarkerSize());
}
}
// 底部状态
var footer = document.createElement('div');
footer.style.cssText = 'margin-top:14px;padding-top:10px;border-top:1px solid rgba(125,249,255,0.08);font-size:11px;color:#5a7a8a;text-align:center;';
footer.textContent = '点击按钮切换 开/关';
panel.appendChild(footer);
// 热键显示区域(UpdateHotkeyDisplay 在热键系统定义后调用)
var hotkeySection = document.createElement('div');
hotkeySection.style.cssText = 'margin-top:12px;padding-top:10px;border-top:1px solid rgba(125,249,255,0.08);font-size:11px;';
function UpdateHotkeyDisplay() {
if (!HotkeyLabels || !Hotkeys) return;
var html = '<div style="color:#7a9aaa;margin-bottom:4px;">热键(控制台可改: __hornetPanel__.SetHotkey)</div>';
Object.keys(HotkeyLabels).forEach(function(k) {
html += '<div style="display:flex;justify-content:space-between;padding:2px 0;">' +
'<span style="color:#5a7a8a;">' + HotkeyLabels[k] + '</span>' +
'<span style="color:#7df9ff;font-weight:bold;">' + Hotkeys[k] + '</span></div>';
});
hotkeySection.innerHTML = html;
}
panel.appendChild(hotkeySection);
document.body.appendChild(panel);
// 面板通过标题栏拖动
var PanelDrag = MakeDraggable(panel, title, null, 'panel');
// ===== 阻止面板内事件冒泡到 window(防止游戏/工具拦截键盘鼠标)=====
// 鼠标事件:面板上所有鼠标事件都阻止冒泡(防止游戏转视角/开炮等)
['mousedown','mouseup','mousemove','wheel','click','dblclick','contextmenu'].forEach(function(evtName) {
panel.addEventListener(evtName, function(e) { e.stopPropagation(); });
});
// 键盘事件:只在输入元素上阻止冒泡(保留面板上其他位置的热键功能)
['keydown','keypress','keyup'].forEach(function(evtName) {
panel.addEventListener(evtName, function(e) {
var tag = e.target.tagName;
if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') {
e.stopPropagation();
}
});
});
// HUD 也阻止鼠标事件冒泡(防止拖动HUD时游戏响应)
['mousedown','mouseup','mousemove','wheel','click','contextmenu'].forEach(function(evtName) {
hud.addEventListener(evtName, function(e) { e.stopPropagation(); });
});
// ===== 构建工具按钮(含模式切换)=====
var btnMap = {}; // key -> { row, btn, label, tool, modeBtns }
function BuildButtons() {
btnContainer.innerHTML = '';
btnMap = {};
var list = GetToolList();
list.forEach(function(t) {
var row = document.createElement('div');
row.style.cssText = 'display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid rgba(125,249,255,0.06);';
var label = document.createElement('span');
label.textContent = t.label;
label.style.cssText = 'color:#b8c8d8;font-size:13px;';
row.appendChild(label);
// 右侧按钮容器
var btnBox = document.createElement('div');
btnBox.style.cssText = 'display:flex;gap:4px;align-items:center;';
var modeBtns = null;
// Wireframe 模式切换按钮
if (t.hasMode && t.obj && t.obj.SetMode) {
modeBtns = {};
var modes = [
{ mode: 'wireframe', text: '线框' },
{ mode: 'outline', text: '描边' }
];
modes.forEach(function(mb) {
var b = document.createElement('button');
b.textContent = mb.text;
b.style.cssText =
'padding:4px 8px;border:1px solid rgba(125,249,255,0.25);border-radius:4px;' +
'font:11px Consolas,monospace;cursor:pointer;color:#e8eef5;min-width:44px;' +
'background:rgba(20,30,40,0.6);transition:all 0.2s;';
b.onclick = function() {
if (t.obj) {
t.obj.SetMode(mb.mode);
UpdateButtons();
}
};
btnBox.appendChild(b);
modeBtns[mb.mode] = b;
});
}
var btn = document.createElement('button');
btn.style.cssText =
'padding:6px 16px;border:none;border-radius:6px;font:bold 12px Consolas,monospace;' +
'cursor:pointer;min-width:72px;color:#fff;transition:all 0.2s;' +
'box-shadow:0 2px 8px rgba(0,0,0,0.3);';
btn.onclick = function() {
if (!t.obj) {
console.warn('[3DTank] 工具 ' + t.key + ' 未加载');
return;
}
if (t.obj.GetRunning()) {
t.obj.Stop();
} else {
t.obj.Start();
}
UpdateButtons();
SaveToolEnabled();
};
btnBox.appendChild(btn);
row.appendChild(btnBox);
btnContainer.appendChild(row);
btnMap[t.key] = { btn: btn, label: label, tool: t.obj, modeBtns: modeBtns };
});
}
BuildButtons();
// ===== 更新按钮状态 =====
function UpdateButtons() {
Object.keys(btnMap).forEach(function(key) {
var info = btnMap[key];
if (!info.tool) {
info.btn.textContent = '未加载';
info.btn.style.background = '#555';
info.btn.disabled = true;
return;
}
var running = info.tool.GetRunning();
info.btn.textContent = running ? '关闭' : '开启';
info.btn.style.background = running
? 'linear-gradient(135deg,#e05555,#c0333d)'
: 'linear-gradient(135deg,#2ecc71,#27ae60)';
info.btn.disabled = false;
// 更新模式按钮高亮
if (info.modeBtns) {
var curMode = null;
try { curMode = window.__hornet3DWireframe__ && window.__hornet3DWireframe__.mode; } catch(e) {}
Object.keys(info.modeBtns).forEach(function(mk) {
var mb = info.modeBtns[mk];
if (mk === curMode && running) {
mb.style.background = 'linear-gradient(135deg,#2ecc71,#27ae60)';
mb.style.opacity = '1';
mb.style.color = '#fff';
} else {
mb.style.background = 'rgba(20,30,40,0.6)';
mb.style.opacity = '0.6';
mb.style.color = '#8899aa';
}
});
}
});
}
// ===== 更新 HUD(HUD 本体已移除,这里主要保留 TryAutoEnable 与循环)=====
function UpdateHud() {
requestAnimationFrame(UpdateHud);
TryAutoEnable(); // gameMode 捕获后自动按保存状态开启工具
if (!hud.parentNode) return; // HUD 未挂载(已移除)→ 不必拼状态文本/写 DOM
var ght = window.__gameMode__;
var lines = [];
lines.push('3DTank 控制台 [拖动移动]');
lines.push('GHT: ' + (ght ? '已捕获' : '未捕获'));
var list = GetToolList();
list.forEach(function(t) {
var status = t.obj ? t.obj.Status() : '未加载';
var dot = t.obj && t.obj.GetRunning() ? '●' : '○';
lines.push(dot + ' ' + t.label + ': ' + status);
});
hud.textContent = lines.join('\n');
hud.style.color = ght ? '#7df9ff' : '#f5a623';
}
UpdateHud();
// ===== 热键系统(localStorage 持久化)=====
var DefaultHotkeys = {
panel: 'Alt+T',
radar: 'Alt+R',
wireframe: 'Alt+W',
rearview: 'Alt+V',
aimWarning: 'Alt+A',
freeLook: 'Alt+F',
warSandbox: 'Alt+M'
};
var HotkeyLabels = {
panel: '面板',
radar: '雷达',
wireframe: '3D透视',
rearview: '后视镜',
aimWarning: '瞄准警告',
freeLook: '自由视角',
warSandbox: '战争沙盘'
};
var Hotkeys = {};
try {
var saved = localStorage.getItem('3dtank_hotkeys');
var parsed = saved ? JSON.parse(saved) : {};
Object.keys(DefaultHotkeys).forEach(function(k) {
Hotkeys[k] = parsed[k] || DefaultHotkeys[k];
});
} catch(e) {
Object.keys(DefaultHotkeys).forEach(function(k) { Hotkeys[k] = DefaultHotkeys[k]; });
}
function SaveHotkeys() {
try { localStorage.setItem('3dtank_hotkeys', JSON.stringify(Hotkeys)); } catch(e) {}
}
function EventToHotkey(e) {
var parts = [];
if (e.altKey) parts.push('Alt');
if (e.ctrlKey) parts.push('Ctrl');
if (e.shiftKey) parts.push('Shift');
var key = e.key;
if (key.length === 1) key = key.toUpperCase();
if (key === ' ') key = 'Space';
parts.push(key);
return parts.join('+');
}
function HidePanel() {
panel.style.display = 'none';
PanelDrag.End(); // 防止隐藏时拖动状态残留(粘手)
}
// 释放鼠标指针锁定:游戏中鼠标被 Pointer Lock 捕获时指针隐藏、锁定在画布上,
// 面板点不到,需退出锁定让光标恢复可交互
function ReleasePointerLock() {
try {
if (document.exitPointerLock && document.pointerLockElement) {
document.exitPointerLock();
}
} catch(e) {}
}
function ShowPanel() {
panel.style.display = 'block';
ReleasePointerLock();
UpdateButtons();
SyncWfSettings();
}
function TogglePanel() {
if (panel.style.display === 'block') {
HidePanel();
} else {
ShowPanel();
}
}
function OnHotkey(e) {
if (e.key === 'Escape' && panel.style.display === 'block') {
HidePanel();
return;
}
var hk = EventToHotkey(e);
if (hk === Hotkeys.panel) {
e.preventDefault();
TogglePanel();
return;
}
var toolMap = { radar: 'Radar', wireframe: 'Wireframe', rearview: 'Rearview', aimWarning: 'AimWarning', freeLook: 'FreeLook', warSandbox: 'WarSandbox' };
Object.keys(toolMap).forEach(function(k) {
if (hk === Hotkeys[k]) {
e.preventDefault();
var tools = window.__hornetTools__ || {};
var tool = tools[toolMap[k]];
if (tool) {
if (tool.GetRunning()) tool.Stop();
else tool.Start();
UpdateButtons();
SaveToolEnabled();
}
}
});
}
// F5 刷新页面(游戏会吞掉 F5)——捕获阶段兜底,先于游戏处理
window.addEventListener('keydown', function(e) {
if (e.key === 'F5') { e.preventDefault(); e.stopImmediatePropagation(); location.reload(); }
}, true);
window.addEventListener('keydown', OnHotkey);
// 热键系统定义完成,更新显示
UpdateHotkeyDisplay();
// ===== 暴露接口 =====
window.__hornetPanel__ = {
Toggle: TogglePanel,
Show: ShowPanel,
Hide: HidePanel,
UpdateButtons: UpdateButtons,
RebuildButtons: BuildButtons,
GetHotkeys: function() { var r = {}; Object.keys(Hotkeys).forEach(function(k) { r[k] = Hotkeys[k]; }); return r; },
SetHotkey: function(name, key) {
if (DefaultHotkeys[name]) {
Hotkeys[name] = key;
SaveHotkeys();
UpdateHotkeyDisplay();
console.log('[3DTank] 热键 ' + name + ' 已设为 ' + key);
} else {
console.warn('[3DTank] 未知热键名: ' + name + ',可用: ' + Object.keys(DefaultHotkeys).join(', '));
}
},
ResetHotkeys: function() {
Object.keys(DefaultHotkeys).forEach(function(k) { Hotkeys[k] = DefaultHotkeys[k]; });
SaveHotkeys();
UpdateHotkeyDisplay();
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);
}
};
console.log('[3DTank] 控制面板已就绪,按 Alt+T 打开面板,HUD/面板/窗口可拖动,热键已加载');
})();