Files
TankIC/tools/hornet_panel.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

765 lines
30 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)
// 实现要点: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/面板/窗口可拖动,热键已加载');
})();