旧 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 重新生成。
765 lines
30 KiB
JavaScript
765 lines
30 KiB
JavaScript
// ===== 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)
|
||
// 实现要点:Pointer Events + setPointerCapture 把指针"锁"在 handle 上,
|
||
// pointerup/pointercancel 必定回到 handle;并在 window 捕获阶段加兜底,
|
||
// 避免面板内 stopPropagation 或游戏画布抢事件导致"松手后仍粘着鼠标"。
|
||
// 返回:{ End, IsActive },供隐藏面板时强制结束拖动
|
||
function MakeDraggable(elem, handle, ignoreTags) {
|
||
handle = handle || elem;
|
||
ignoreTags = ignoreTags || ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'];
|
||
handle.style.cursor = 'move';
|
||
|
||
var Active = false;
|
||
var PointerId = null;
|
||
var GrabX = 0;
|
||
var GrabY = 0;
|
||
|
||
function MoveTo(ClientX, ClientY) {
|
||
var MaxX = window.innerWidth - 40;
|
||
var MaxY = window.innerHeight - 24;
|
||
var NextX = Math.max(0, Math.min(MaxX, ClientX - GrabX));
|
||
var NextY = Math.max(0, Math.min(MaxY, ClientY - GrabY));
|
||
elem.style.left = NextX + 'px';
|
||
elem.style.top = NextY + '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';
|
||
}
|
||
|
||
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 }
|
||
];
|
||
}
|
||
|
||
// ===== HUD(左上角状态显示,可拖动)=====
|
||
var hud = document.createElement('div');
|
||
hud.id = '3dtank-hud';
|
||
hud.style.cssText =
|
||
'position:fixed;top:10px;left:10px;z-index:100000;pointer-events:auto;cursor:move;' +
|
||
'font:11px Consolas,monospace;color:#7df9ff;background:rgba(5,10,18,0.55);' +
|
||
'backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);' +
|
||
'padding:8px 12px;border-radius:6px;border:1px solid rgba(125,249,255,0.25);' +
|
||
'white-space:pre;line-height:1.6;text-shadow:0 0 3px rgba(125,249,255,0.4);' +
|
||
'user-select:none;-webkit-user-select:none;box-shadow:0 2px 12px rgba(0,0,0,0.4);';
|
||
document.body.appendChild(hud);
|
||
var HudDrag = MakeDraggable(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);
|
||
});
|
||
|
||
// ===== 后视镜设置 =====
|
||
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);
|
||
});
|
||
|
||
// 从工具读取当前设置(打开面板时同步)
|
||
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);
|
||
|
||
// ===== 阻止面板内事件冒泡到 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();
|
||
};
|
||
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 =====
|
||
function UpdateHud() {
|
||
requestAnimationFrame(UpdateHud);
|
||
var ght = window.__gameMode__;
|
||
var ghtStatus = ght ? '已捕获' : '未捕获';
|
||
var ghtColor = ght ? '#7df9ff' : '#f5a623';
|
||
var lines = [];
|
||
lines.push('3DTank 控制台 [拖动移动]');
|
||
lines.push('GHT: ' + ghtStatus);
|
||
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 = ghtColor;
|
||
}
|
||
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(); // 防止隐藏时拖动状态残留(粘手)
|
||
}
|
||
|
||
function TogglePanel() {
|
||
if (panel.style.display === 'block') {
|
||
HidePanel();
|
||
} else {
|
||
panel.style.display = 'block';
|
||
UpdateButtons();
|
||
SyncWfSettings();
|
||
}
|
||
}
|
||
|
||
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();
|
||
}
|
||
}
|
||
});
|
||
}
|
||
window.addEventListener('keydown', OnHotkey);
|
||
// 热键系统定义完成,更新显示
|
||
UpdateHotkeyDisplay();
|
||
|
||
// ===== 暴露接口 =====
|
||
window.__hornetPanel__ = {
|
||
Toggle: TogglePanel,
|
||
Show: function() { panel.style.display = 'block'; UpdateButtons(); SyncWfSettings(); },
|
||
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/面板/窗口可拖动,热键已加载');
|
||
})();
|