// ===== 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 = '3D透视设置▼'; 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 = '' + titleText + ''; 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 = '
热键(控制台可改: __hornetPanel__.SetHotkey)
'; Object.keys(HotkeyLabels).forEach(function(k) { html += '
' + '' + HotkeyLabels[k] + '' + '' + Hotkeys[k] + '
'; }); 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/面板/窗口可拖动,热键已加载'); })();