From 6e79057856cb5f42b789e5ccb90424caf80169d3 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 20 Oct 2026 20:06:24 +0800 Subject: [PATCH] =?UTF-8?q?feat(noclip):=20=E6=96=B0=E5=A2=9E=E7=A9=BF?= =?UTF-8?q?=E5=9D=A6=E5=85=8B=E5=B7=A5=E5=85=B7=E5=B9=B6=E6=8E=A5=E5=85=A5?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 清掉本地坦克碰撞形状 mask 的坦克位(bit0),可穿过其他玩家坦克;地形静态几何(category bit4)碰撞保持不变。引擎状态切换会重写 mask,运行期每帧补一次,停止时按原始快照精确还原。 --- .../play/static/js/unpacked/hornet_bundle.js | 188 +++++++++++++++++- 3dtank_bundle.js | 188 +++++++++++++++++- build_bundle.py | 5 +- tools/hornet_noclip.js | 178 +++++++++++++++++ tools/hornet_panel.js | 7 +- 5 files changed, 555 insertions(+), 11 deletions(-) create mode 100644 tools/hornet_noclip.js diff --git a/3dtank.com/play/static/js/unpacked/hornet_bundle.js b/3dtank.com/play/static/js/unpacked/hornet_bundle.js index b9e7ffd..19900e9 100644 --- a/3dtank.com/play/static/js/unpacked/hornet_bundle.js +++ b/3dtank.com/play/static/js/unpacked/hornet_bundle.js @@ -5808,6 +5808,187 @@ })(); +// ===== hornet_noclip.js ===== +// hornet_noclip.js — 穿坦克(移动中忽略玩家障碍物,强行行走) +// 放在控制面板「穿坦克」开关里,只影响**本地坦克**,不改服务端/其他玩家。 +// +// 碰撞过滤(module_55088 的 ki.q9q / ki.x9o,全物理世界唯一判定处): +// if (0 === (a.y9j_1 & b.z9j_1) || 0 === (b.y9j_1 & a.z9j_1)) return; // 0 → 不产生碰撞 +// · y9j_1 = 形状的 category 位组("我是哪一类") +// · z9j_1 = 形状的 mask 位组("我愿意和哪几类碰") +// 实测位定义(module_87072): +// bit0(1) = 坦克(车体 hull-main 与各部件统一 y9j_1 = 1) +// bit4(16) = 地图静态几何(s6() 造地形:y9j_1 = 16, z9j_1 = 1055) +// 坦克 mask 恒含 16 → 清掉 bit0 后:撞墙照常,**只穿坦克**。 +// +// 为什么每帧补一次: +// 引擎在坦克状态切换时会重写 mask —— tjt() 调 n155(11)(把 bit0 加回来), +// onClientTankState 的 2/5 分支调 n155(1)。所以运行期间每帧把 bit0 清掉。 +// +// 停止:按进入时快照的原始 mask 精确还原(不猜引擎默认值)。 + +(function () { + 'use strict'; + + var Lib = window.__hornetLib__; + if (!Lib) { + console.error('[穿坦克] 请先加载 hornet_lib.js'); + return; + } + + // 坦克 category 位(bit0) + var TANK_CATEGORY_BIT = 1; + + var Stat = { + running: false, + shapes: 0, // 本帧处理的碰撞形状数 + applied: 0 // 本次开启累计生效帧数 + }; + + var CurrentPhysics = null; // 当前被处理的 tankPhysics 组件(ejt) + var MaskSnapshot = new Map(); // 形状 -> 进入时原始 mask + var LoggedPhysics = new WeakSet(); // 已打印过日志的组件 + + // 判定是否坦克物理组件(ejt / 组件名 tankPhysics) + // 特征:zzq_1 是车体主碰撞形状(x9j_1 === "hull-main"),且带 n155 / m155 / pbk + function IsTankPhysics(Comp) { + if (!Comp) return false; + if (typeof Comp.n155 !== 'function' || typeof Comp.m155 !== 'function') return false; + if (typeof Comp.pbk !== 'function') return false; + var Hull = Comp.zzq_1; + return !!(Hull && Hull.x9j_1 === 'hull-main'); + } + + // 在实体上找坦克物理组件(不走 FindComponentByField:zzq_1 字段名在其它类里也有复用) + function FindTankPhysics(Tank) { + if (!Tank || !Tank.qz1_1 || !Tank.qz1_1.s2a_1) return null; + var Found = null; + try { + Tank.qz1_1.s2a_1.forEach(function (ValueList) { + if (Found) return; + try { + var Comp = (ValueList && typeof ValueList.g1 === 'function') ? ValueList.g1(0) : ValueList; + if (IsTankPhysics(Comp)) Found = Comp; + } catch (e) {} + }); + } catch (e) {} + return Found; + } + + // 本地坦克:拥有中的坦克优先;观战/死亡时退回当前观察目标(此时不参与战斗,影响可忽略) + function GetLocalTank() { + var Gm = window.__gameMode__; + if (!Gm) return null; + try { if (Gm.u10b_1) return Gm.u10b_1; } catch (e) {} + try { if (Gm.t10b_1) return Gm.t10b_1; } catch (e) {} + return null; + } + + // 还原快照里的原始 mask + function ReleaseSnapshot() { + MaskSnapshot.forEach(function (Mask, Shape) { + try { + if (Shape && typeof Mask === 'number') Shape.z9j_1 = Mask; + } catch (e) {} + }); + MaskSnapshot.clear(); + CurrentPhysics = null; + Stat.shapes = 0; + } + + // 对本地坦克所有碰撞形状清掉「坦克位」 + function ApplyMask() { + var Tank = GetLocalTank(); + if (!Tank) { if (CurrentPhysics) ReleaseSnapshot(); return; } + + var Phys = FindTankPhysics(Tank); + if (!Phys) { if (CurrentPhysics) ReleaseSnapshot(); return; } + + // 换车/重生:物理组件被重建 → 先还原旧的,再接管新的 + if (Phys !== CurrentPhysics) { + if (CurrentPhysics) ReleaseSnapshot(); + CurrentPhysics = Phys; + } + + var Body = null; + try { Body = Phys.pbk(); } catch (e) { Body = null; } + var Shapes = Body && Body.y9i_1; + if (!Shapes || typeof Shapes.e1 !== 'function' || typeof Shapes.g1 !== 'function') return; + + var Count = Shapes.e1() | 0; + var Touched = 0; + for (var i = 0; i < Count; i++) { + var Shape = null; + try { Shape = Shapes.g1(i); } catch (e) { Shape = null; } + if (!Shape || typeof Shape.z9j_1 !== 'number') continue; + // 首次见到该形状时记录原始 mask,保证 Stop 能精确还原 + if (!MaskSnapshot.has(Shape)) MaskSnapshot.set(Shape, Shape.z9j_1); + Shape.z9j_1 = Shape.z9j_1 & ~TANK_CATEGORY_BIT; + Touched++; + } + Stat.shapes = Touched; + if (Touched > 0) { + Stat.applied++; + if (!LoggedPhysics.has(Phys)) { + LoggedPhysics.add(Phys); + // 原始 mask 一并打印:若这里没有任何 mask 含 bit0,说明坦克碰撞位变了,需要重新核对 + var Masks = []; + MaskSnapshot.forEach(function (Mask) { Masks.push(Mask); }); + var HasTankBit = false; + for (var k = 0; k < Masks.length; k++) { if (Masks[k] & TANK_CATEGORY_BIT) { HasTankBit = true; break; } } + console.log('[穿坦克] 已生效:本地坦克 ' + Touched + ' 块碰撞形状,原始 mask=[' + Masks.join(',') + '] ' + + (HasTankBit ? '(含坦克位 bit0,已清除)' : '⚠ 未发现坦克位 bit0,可能版本字段有变')); + } + } + } + + function Loop() { + if (!Stat.running) return; + requestAnimationFrame(Loop); + try { ApplyMask(); } catch (e) { + // 引擎中途换场景/重建物理时可能抛错,忽略即可(下一帧会重新尝试) + } + } + + function _start() { + if (Stat.running) return; + Stat.running = true; + Stat.applied = 0; + ApplyMask(); + requestAnimationFrame(Loop); + console.log('[穿坦克] 已启动(只穿坦克,地形碰撞保留)'); + } + + function _stop() { + if (!Stat.running) return; + Stat.running = false; + ReleaseSnapshot(); + console.log('[穿坦克] 已停止,碰撞已还原'); + } + + var Tool = { + Start: function () { _start(); }, + Stop: function () { _stop(); }, + GetRunning: function () { return Stat.running; }, + Status: function () { + if (!Stat.running) return '已停止'; + return Stat.shapes > 0 ? ('穿坦克 ' + Stat.shapes + ' 块') : '等待坦克'; + } + }; + + window.__hornetTools__ = window.__hornetTools__ || {}; + window.__hornetTools__.NoClip = Tool; + window.__hornetNoClip__ = { + IsTankPhysics: IsTankPhysics, + GetStat: function () { + return { running: Stat.running, shapes: Stat.shapes, applied: Stat.applied }; + } + }; + + console.log('[3DTank] 穿坦克模块已加载'); +})(); + + // ===== hornet_panel.js ===== // ===== 3DTank 控制面板 ===== // 功能:Alt+T 切换面板显示,控制各工具开关 @@ -5960,14 +6141,15 @@ { key: 'AimWarning', label: '瞄准警告', obj: tools.AimWarning }, { key: 'FreeLook', label: '自由视角', obj: tools.FreeLook }, { key: 'WarSandbox', label: '战争沙盘', obj: tools.WarSandbox }, - { key: 'Mouse', label: '鼠标稳定', obj: tools.Mouse } + { key: 'Mouse', label: '鼠标稳定', obj: tools.Mouse }, + { key: 'NoClip', label: '穿坦克', obj: tools.NoClip } ]; } // ===== 工具启用状态(持久化 + 默认启用)===== // 默认除「自由视角」外全部开启:自由视角会直接接管相机,默认开启过于突兀 - 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 ToolKeys = ['Radar', 'Wireframe', 'Rearview', 'AimWarning', 'FreeLook', 'WarSandbox', 'Mouse', 'NoClip']; + var ToolDefaults = { Radar: true, Wireframe: true, Rearview: true, AimWarning: true, FreeLook: false, WarSandbox: true, Mouse: true, NoClip: false }; var ToolEnabled = {}; try { var __savedEnabled = JSON.parse(localStorage.getItem('hornet_tool_enabled') || '{}'); diff --git a/3dtank_bundle.js b/3dtank_bundle.js index b9e7ffd..19900e9 100644 --- a/3dtank_bundle.js +++ b/3dtank_bundle.js @@ -5808,6 +5808,187 @@ })(); +// ===== hornet_noclip.js ===== +// hornet_noclip.js — 穿坦克(移动中忽略玩家障碍物,强行行走) +// 放在控制面板「穿坦克」开关里,只影响**本地坦克**,不改服务端/其他玩家。 +// +// 碰撞过滤(module_55088 的 ki.q9q / ki.x9o,全物理世界唯一判定处): +// if (0 === (a.y9j_1 & b.z9j_1) || 0 === (b.y9j_1 & a.z9j_1)) return; // 0 → 不产生碰撞 +// · y9j_1 = 形状的 category 位组("我是哪一类") +// · z9j_1 = 形状的 mask 位组("我愿意和哪几类碰") +// 实测位定义(module_87072): +// bit0(1) = 坦克(车体 hull-main 与各部件统一 y9j_1 = 1) +// bit4(16) = 地图静态几何(s6() 造地形:y9j_1 = 16, z9j_1 = 1055) +// 坦克 mask 恒含 16 → 清掉 bit0 后:撞墙照常,**只穿坦克**。 +// +// 为什么每帧补一次: +// 引擎在坦克状态切换时会重写 mask —— tjt() 调 n155(11)(把 bit0 加回来), +// onClientTankState 的 2/5 分支调 n155(1)。所以运行期间每帧把 bit0 清掉。 +// +// 停止:按进入时快照的原始 mask 精确还原(不猜引擎默认值)。 + +(function () { + 'use strict'; + + var Lib = window.__hornetLib__; + if (!Lib) { + console.error('[穿坦克] 请先加载 hornet_lib.js'); + return; + } + + // 坦克 category 位(bit0) + var TANK_CATEGORY_BIT = 1; + + var Stat = { + running: false, + shapes: 0, // 本帧处理的碰撞形状数 + applied: 0 // 本次开启累计生效帧数 + }; + + var CurrentPhysics = null; // 当前被处理的 tankPhysics 组件(ejt) + var MaskSnapshot = new Map(); // 形状 -> 进入时原始 mask + var LoggedPhysics = new WeakSet(); // 已打印过日志的组件 + + // 判定是否坦克物理组件(ejt / 组件名 tankPhysics) + // 特征:zzq_1 是车体主碰撞形状(x9j_1 === "hull-main"),且带 n155 / m155 / pbk + function IsTankPhysics(Comp) { + if (!Comp) return false; + if (typeof Comp.n155 !== 'function' || typeof Comp.m155 !== 'function') return false; + if (typeof Comp.pbk !== 'function') return false; + var Hull = Comp.zzq_1; + return !!(Hull && Hull.x9j_1 === 'hull-main'); + } + + // 在实体上找坦克物理组件(不走 FindComponentByField:zzq_1 字段名在其它类里也有复用) + function FindTankPhysics(Tank) { + if (!Tank || !Tank.qz1_1 || !Tank.qz1_1.s2a_1) return null; + var Found = null; + try { + Tank.qz1_1.s2a_1.forEach(function (ValueList) { + if (Found) return; + try { + var Comp = (ValueList && typeof ValueList.g1 === 'function') ? ValueList.g1(0) : ValueList; + if (IsTankPhysics(Comp)) Found = Comp; + } catch (e) {} + }); + } catch (e) {} + return Found; + } + + // 本地坦克:拥有中的坦克优先;观战/死亡时退回当前观察目标(此时不参与战斗,影响可忽略) + function GetLocalTank() { + var Gm = window.__gameMode__; + if (!Gm) return null; + try { if (Gm.u10b_1) return Gm.u10b_1; } catch (e) {} + try { if (Gm.t10b_1) return Gm.t10b_1; } catch (e) {} + return null; + } + + // 还原快照里的原始 mask + function ReleaseSnapshot() { + MaskSnapshot.forEach(function (Mask, Shape) { + try { + if (Shape && typeof Mask === 'number') Shape.z9j_1 = Mask; + } catch (e) {} + }); + MaskSnapshot.clear(); + CurrentPhysics = null; + Stat.shapes = 0; + } + + // 对本地坦克所有碰撞形状清掉「坦克位」 + function ApplyMask() { + var Tank = GetLocalTank(); + if (!Tank) { if (CurrentPhysics) ReleaseSnapshot(); return; } + + var Phys = FindTankPhysics(Tank); + if (!Phys) { if (CurrentPhysics) ReleaseSnapshot(); return; } + + // 换车/重生:物理组件被重建 → 先还原旧的,再接管新的 + if (Phys !== CurrentPhysics) { + if (CurrentPhysics) ReleaseSnapshot(); + CurrentPhysics = Phys; + } + + var Body = null; + try { Body = Phys.pbk(); } catch (e) { Body = null; } + var Shapes = Body && Body.y9i_1; + if (!Shapes || typeof Shapes.e1 !== 'function' || typeof Shapes.g1 !== 'function') return; + + var Count = Shapes.e1() | 0; + var Touched = 0; + for (var i = 0; i < Count; i++) { + var Shape = null; + try { Shape = Shapes.g1(i); } catch (e) { Shape = null; } + if (!Shape || typeof Shape.z9j_1 !== 'number') continue; + // 首次见到该形状时记录原始 mask,保证 Stop 能精确还原 + if (!MaskSnapshot.has(Shape)) MaskSnapshot.set(Shape, Shape.z9j_1); + Shape.z9j_1 = Shape.z9j_1 & ~TANK_CATEGORY_BIT; + Touched++; + } + Stat.shapes = Touched; + if (Touched > 0) { + Stat.applied++; + if (!LoggedPhysics.has(Phys)) { + LoggedPhysics.add(Phys); + // 原始 mask 一并打印:若这里没有任何 mask 含 bit0,说明坦克碰撞位变了,需要重新核对 + var Masks = []; + MaskSnapshot.forEach(function (Mask) { Masks.push(Mask); }); + var HasTankBit = false; + for (var k = 0; k < Masks.length; k++) { if (Masks[k] & TANK_CATEGORY_BIT) { HasTankBit = true; break; } } + console.log('[穿坦克] 已生效:本地坦克 ' + Touched + ' 块碰撞形状,原始 mask=[' + Masks.join(',') + '] ' + + (HasTankBit ? '(含坦克位 bit0,已清除)' : '⚠ 未发现坦克位 bit0,可能版本字段有变')); + } + } + } + + function Loop() { + if (!Stat.running) return; + requestAnimationFrame(Loop); + try { ApplyMask(); } catch (e) { + // 引擎中途换场景/重建物理时可能抛错,忽略即可(下一帧会重新尝试) + } + } + + function _start() { + if (Stat.running) return; + Stat.running = true; + Stat.applied = 0; + ApplyMask(); + requestAnimationFrame(Loop); + console.log('[穿坦克] 已启动(只穿坦克,地形碰撞保留)'); + } + + function _stop() { + if (!Stat.running) return; + Stat.running = false; + ReleaseSnapshot(); + console.log('[穿坦克] 已停止,碰撞已还原'); + } + + var Tool = { + Start: function () { _start(); }, + Stop: function () { _stop(); }, + GetRunning: function () { return Stat.running; }, + Status: function () { + if (!Stat.running) return '已停止'; + return Stat.shapes > 0 ? ('穿坦克 ' + Stat.shapes + ' 块') : '等待坦克'; + } + }; + + window.__hornetTools__ = window.__hornetTools__ || {}; + window.__hornetTools__.NoClip = Tool; + window.__hornetNoClip__ = { + IsTankPhysics: IsTankPhysics, + GetStat: function () { + return { running: Stat.running, shapes: Stat.shapes, applied: Stat.applied }; + } + }; + + console.log('[3DTank] 穿坦克模块已加载'); +})(); + + // ===== hornet_panel.js ===== // ===== 3DTank 控制面板 ===== // 功能:Alt+T 切换面板显示,控制各工具开关 @@ -5960,14 +6141,15 @@ { key: 'AimWarning', label: '瞄准警告', obj: tools.AimWarning }, { key: 'FreeLook', label: '自由视角', obj: tools.FreeLook }, { key: 'WarSandbox', label: '战争沙盘', obj: tools.WarSandbox }, - { key: 'Mouse', label: '鼠标稳定', obj: tools.Mouse } + { key: 'Mouse', label: '鼠标稳定', obj: tools.Mouse }, + { key: 'NoClip', label: '穿坦克', obj: tools.NoClip } ]; } // ===== 工具启用状态(持久化 + 默认启用)===== // 默认除「自由视角」外全部开启:自由视角会直接接管相机,默认开启过于突兀 - 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 ToolKeys = ['Radar', 'Wireframe', 'Rearview', 'AimWarning', 'FreeLook', 'WarSandbox', 'Mouse', 'NoClip']; + var ToolDefaults = { Radar: true, Wireframe: true, Rearview: true, AimWarning: true, FreeLook: false, WarSandbox: true, Mouse: true, NoClip: false }; var ToolEnabled = {}; try { var __savedEnabled = JSON.parse(localStorage.getItem('hornet_tool_enabled') || '{}'); diff --git a/build_bundle.py b/build_bundle.py index 287acf1..08a29c0 100644 --- a/build_bundle.py +++ b/build_bundle.py @@ -24,8 +24,9 @@ FILES = [ "hornet_freelook.js", # 7. 自由视角模块 -> __hornetTools__.FreeLook "hornet_warsandbox.js", # 8. 战争沙盘模块 -> __hornetTools__.WarSandbox "hornet_mouse.js", # 9. 鼠标输入稳定器 -> __hornetTools__.Mouse - "hornet_panel.js", # 10. 控制面板(Alt+T + HUD) - "hornet_prefetch.js", # 11. 车库资源预热(战局空闲时后台加载车库所需资源) + "hornet_noclip.js", # 10. 穿坦克(忽略玩家碰撞)-> __hornetTools__.NoClip + "hornet_panel.js", # 11. 控制面板(Alt+T + HUD) + "hornet_prefetch.js", # 12. 车库资源预热(战局空闲时后台加载车库所需资源) ] HEADER = """// ============================================================ diff --git a/tools/hornet_noclip.js b/tools/hornet_noclip.js new file mode 100644 index 0000000..89843aa --- /dev/null +++ b/tools/hornet_noclip.js @@ -0,0 +1,178 @@ +// hornet_noclip.js — 穿坦克(移动中忽略玩家障碍物,强行行走) +// 放在控制面板「穿坦克」开关里,只影响**本地坦克**,不改服务端/其他玩家。 +// +// 碰撞过滤(module_55088 的 ki.q9q / ki.x9o,全物理世界唯一判定处): +// if (0 === (a.y9j_1 & b.z9j_1) || 0 === (b.y9j_1 & a.z9j_1)) return; // 0 → 不产生碰撞 +// · y9j_1 = 形状的 category 位组("我是哪一类") +// · z9j_1 = 形状的 mask 位组("我愿意和哪几类碰") +// 实测位定义(module_87072): +// bit0(1) = 坦克(车体 hull-main 与各部件统一 y9j_1 = 1) +// bit4(16) = 地图静态几何(s6() 造地形:y9j_1 = 16, z9j_1 = 1055) +// 坦克 mask 恒含 16 → 清掉 bit0 后:撞墙照常,**只穿坦克**。 +// +// 为什么每帧补一次: +// 引擎在坦克状态切换时会重写 mask —— tjt() 调 n155(11)(把 bit0 加回来), +// onClientTankState 的 2/5 分支调 n155(1)。所以运行期间每帧把 bit0 清掉。 +// +// 停止:按进入时快照的原始 mask 精确还原(不猜引擎默认值)。 + +(function () { + 'use strict'; + + var Lib = window.__hornetLib__; + if (!Lib) { + console.error('[穿坦克] 请先加载 hornet_lib.js'); + return; + } + + // 坦克 category 位(bit0) + var TANK_CATEGORY_BIT = 1; + + var Stat = { + running: false, + shapes: 0, // 本帧处理的碰撞形状数 + applied: 0 // 本次开启累计生效帧数 + }; + + var CurrentPhysics = null; // 当前被处理的 tankPhysics 组件(ejt) + var MaskSnapshot = new Map(); // 形状 -> 进入时原始 mask + var LoggedPhysics = new WeakSet(); // 已打印过日志的组件 + + // 判定是否坦克物理组件(ejt / 组件名 tankPhysics) + // 特征:zzq_1 是车体主碰撞形状(x9j_1 === "hull-main"),且带 n155 / m155 / pbk + function IsTankPhysics(Comp) { + if (!Comp) return false; + if (typeof Comp.n155 !== 'function' || typeof Comp.m155 !== 'function') return false; + if (typeof Comp.pbk !== 'function') return false; + var Hull = Comp.zzq_1; + return !!(Hull && Hull.x9j_1 === 'hull-main'); + } + + // 在实体上找坦克物理组件(不走 FindComponentByField:zzq_1 字段名在其它类里也有复用) + function FindTankPhysics(Tank) { + if (!Tank || !Tank.qz1_1 || !Tank.qz1_1.s2a_1) return null; + var Found = null; + try { + Tank.qz1_1.s2a_1.forEach(function (ValueList) { + if (Found) return; + try { + var Comp = (ValueList && typeof ValueList.g1 === 'function') ? ValueList.g1(0) : ValueList; + if (IsTankPhysics(Comp)) Found = Comp; + } catch (e) {} + }); + } catch (e) {} + return Found; + } + + // 本地坦克:拥有中的坦克优先;观战/死亡时退回当前观察目标(此时不参与战斗,影响可忽略) + function GetLocalTank() { + var Gm = window.__gameMode__; + if (!Gm) return null; + try { if (Gm.u10b_1) return Gm.u10b_1; } catch (e) {} + try { if (Gm.t10b_1) return Gm.t10b_1; } catch (e) {} + return null; + } + + // 还原快照里的原始 mask + function ReleaseSnapshot() { + MaskSnapshot.forEach(function (Mask, Shape) { + try { + if (Shape && typeof Mask === 'number') Shape.z9j_1 = Mask; + } catch (e) {} + }); + MaskSnapshot.clear(); + CurrentPhysics = null; + Stat.shapes = 0; + } + + // 对本地坦克所有碰撞形状清掉「坦克位」 + function ApplyMask() { + var Tank = GetLocalTank(); + if (!Tank) { if (CurrentPhysics) ReleaseSnapshot(); return; } + + var Phys = FindTankPhysics(Tank); + if (!Phys) { if (CurrentPhysics) ReleaseSnapshot(); return; } + + // 换车/重生:物理组件被重建 → 先还原旧的,再接管新的 + if (Phys !== CurrentPhysics) { + if (CurrentPhysics) ReleaseSnapshot(); + CurrentPhysics = Phys; + } + + var Body = null; + try { Body = Phys.pbk(); } catch (e) { Body = null; } + var Shapes = Body && Body.y9i_1; + if (!Shapes || typeof Shapes.e1 !== 'function' || typeof Shapes.g1 !== 'function') return; + + var Count = Shapes.e1() | 0; + var Touched = 0; + for (var i = 0; i < Count; i++) { + var Shape = null; + try { Shape = Shapes.g1(i); } catch (e) { Shape = null; } + if (!Shape || typeof Shape.z9j_1 !== 'number') continue; + // 首次见到该形状时记录原始 mask,保证 Stop 能精确还原 + if (!MaskSnapshot.has(Shape)) MaskSnapshot.set(Shape, Shape.z9j_1); + Shape.z9j_1 = Shape.z9j_1 & ~TANK_CATEGORY_BIT; + Touched++; + } + Stat.shapes = Touched; + if (Touched > 0) { + Stat.applied++; + if (!LoggedPhysics.has(Phys)) { + LoggedPhysics.add(Phys); + // 原始 mask 一并打印:若这里没有任何 mask 含 bit0,说明坦克碰撞位变了,需要重新核对 + var Masks = []; + MaskSnapshot.forEach(function (Mask) { Masks.push(Mask); }); + var HasTankBit = false; + for (var k = 0; k < Masks.length; k++) { if (Masks[k] & TANK_CATEGORY_BIT) { HasTankBit = true; break; } } + console.log('[穿坦克] 已生效:本地坦克 ' + Touched + ' 块碰撞形状,原始 mask=[' + Masks.join(',') + '] ' + + (HasTankBit ? '(含坦克位 bit0,已清除)' : '⚠ 未发现坦克位 bit0,可能版本字段有变')); + } + } + } + + function Loop() { + if (!Stat.running) return; + requestAnimationFrame(Loop); + try { ApplyMask(); } catch (e) { + // 引擎中途换场景/重建物理时可能抛错,忽略即可(下一帧会重新尝试) + } + } + + function _start() { + if (Stat.running) return; + Stat.running = true; + Stat.applied = 0; + ApplyMask(); + requestAnimationFrame(Loop); + console.log('[穿坦克] 已启动(只穿坦克,地形碰撞保留)'); + } + + function _stop() { + if (!Stat.running) return; + Stat.running = false; + ReleaseSnapshot(); + console.log('[穿坦克] 已停止,碰撞已还原'); + } + + var Tool = { + Start: function () { _start(); }, + Stop: function () { _stop(); }, + GetRunning: function () { return Stat.running; }, + Status: function () { + if (!Stat.running) return '已停止'; + return Stat.shapes > 0 ? ('穿坦克 ' + Stat.shapes + ' 块') : '等待坦克'; + } + }; + + window.__hornetTools__ = window.__hornetTools__ || {}; + window.__hornetTools__.NoClip = Tool; + window.__hornetNoClip__ = { + IsTankPhysics: IsTankPhysics, + GetStat: function () { + return { running: Stat.running, shapes: Stat.shapes, applied: Stat.applied }; + } + }; + + console.log('[3DTank] 穿坦克模块已加载'); +})(); diff --git a/tools/hornet_panel.js b/tools/hornet_panel.js index 53bb3fd..14743b3 100644 --- a/tools/hornet_panel.js +++ b/tools/hornet_panel.js @@ -149,14 +149,15 @@ { key: 'AimWarning', label: '瞄准警告', obj: tools.AimWarning }, { key: 'FreeLook', label: '自由视角', obj: tools.FreeLook }, { key: 'WarSandbox', label: '战争沙盘', obj: tools.WarSandbox }, - { key: 'Mouse', label: '鼠标稳定', obj: tools.Mouse } + { key: 'Mouse', label: '鼠标稳定', obj: tools.Mouse }, + { key: 'NoClip', label: '穿坦克', obj: tools.NoClip } ]; } // ===== 工具启用状态(持久化 + 默认启用)===== // 默认除「自由视角」外全部开启:自由视角会直接接管相机,默认开启过于突兀 - 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 ToolKeys = ['Radar', 'Wireframe', 'Rearview', 'AimWarning', 'FreeLook', 'WarSandbox', 'Mouse', 'NoClip']; + var ToolDefaults = { Radar: true, Wireframe: true, Rearview: true, AimWarning: true, FreeLook: false, WarSandbox: true, Mouse: true, NoClip: false }; var ToolEnabled = {}; try { var __savedEnabled = JSON.parse(localStorage.getItem('hornet_tool_enabled') || '{}');