From 5a3440229edaf7109ee0797571a98a90746f2ae1 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 20 Oct 2026 20:39:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(noclip):=20=E6=94=B9=E4=B8=BA=E6=8B=A6?= =?UTF-8?q?=E6=88=AA=E7=A2=B0=E6=92=9E=E9=85=8D=E5=AF=B9=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=85=B3=E9=97=AD=E5=90=8E=E6=8B=BE=E5=8F=96=E7=89=A9?= =?UTF-8?q?=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 穿坦克不再修改碰撞 mask(坦克 category 与拾取物触发体共用 bit0,清位会导致拾取失败且关闭后需重生才恢复)。改为包装 module_55088 碰撞世界 ki.q9q,仅跳过双方都是坦克物理组件的配对:地形、拾取物触发、地雷、子弹均保持原样,关闭开关无需还原状态。 --- .../play/static/js/unpacked/hornet_bundle.js | 237 ++++++++---------- 3dtank_bundle.js | 237 ++++++++---------- tools/hornet_noclip.js | 229 ++++++++--------- tools/hornet_schema.js | 8 +- 4 files changed, 333 insertions(+), 378 deletions(-) diff --git a/3dtank.com/play/static/js/unpacked/hornet_bundle.js b/3dtank.com/play/static/js/unpacked/hornet_bundle.js index 2408d72..f456422 100644 --- a/3dtank.com/play/static/js/unpacked/hornet_bundle.js +++ b/3dtank.com/play/static/js/unpacked/hornet_bundle.js @@ -279,6 +279,11 @@ 'Shape.Category': 'y9j_1', // bit0=坦克(1) bit3=地雷(8) bit4=地形(16) 'Shape.Mask': 'z9j_1', // 愿意碰撞的分类位 'Shape.Ghost': 'g9k_1', // true = 只探测不产生碰撞响应 + 'Shape.Body': 'b9k_1', // 形状 → 所属刚体(Fn) + 'Body.Owner': 'x9i_1', // 刚体 → 拥有者(坦克物理组件 ejt) + + // ---- 碰撞世界(module_55088 的 ki)---- + 'CollisionWorld.ShouldCollide': 'q9q()', // 唯一配对过滤:mask + 形状类型 → 生成接触 // ---- 实体事件总线 / 外力事件(ODt)---- 'Entity.Dispatch': 'kz7()', // 向实体内所有订阅者派发事件 @@ -329,7 +334,8 @@ // ⚠ 旧版是 kd / rk;新版 kd 已指向无关构造器 var Exports = { 'Export.TeamOf': ['vd', 'kd'], - 'Export.TeamSame': ['dl', 'rk'] + 'Export.TeamSame': ['dl', 'rk'], + 'Export.PhysicsWorld': ['h'] // module_55088.$_$.h = 碰撞世界类 ki }; // ===== 路径解析 ===== @@ -6156,23 +6162,29 @@ // ===== hornet_noclip.js ===== -// 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 后:撞墙照常,**只穿坦克**。 +// ⚠ 关键修正(2026-10-20):**不再改碰撞 mask**。 +// 之前清掉坦克形状 mask 的 bit0(1)会让拾取物失效 —— 坦克 category=1, +// 而拾取物的触发体(module_87072 的 Ilt:category=1、mask=1、半径触发) +// 判定需要「触发体.cat & 坦克.mask」成立,清掉 bit0 后就检测不到拾取物了。 // -// 为什么每帧补一次: -// 引擎在坦克状态切换时会重写 mask —— tjt() 调 n155(11)(把 bit0 加回来), -// onClientTankState 的 2/5 分支调 n155(1)。所以运行期间每帧把 bit0 清掉。 +// 现在的做法:在**碰撞世界的配对过滤**上精确拦截。 +// module_55088 的 ki.q9q(a, b) 是全物理世界唯一的配对判定: +// if (0 === (a.y9j_1 & b.z9j_1) || 0 === (b.y9j_1 & a.z9j_1)) return; // 不生成接触 +// ...按形状类型取配对处理器,生成接触点 +// 我们只在「a、b 两边的拥有者都是坦克物理组件」时直接返回, +// 等价于引擎自己的「不碰撞」分支 → 坦克之间没有接触点、没有推力,也不会互相卡住。 // -// 停止:按进入时快照的原始 mask 精确还原(不猜引擎默认值)。 +// 这样做的收益: +// · mask / category 一个字节都不改 → 地形、拾取物触发、地雷、子弹全部保持原样 +// · 关闭开关不需要还原任何状态(wrapper 直接转发原函数) +// · 只影响坦克↔坦克这一种配对,不波及静态物体与触发体 +// +// 形状 → 刚体 → 拥有者: +// Shape.b9k_1 = 刚体(Fn,u9j 里 `t.b9k_1 = this`) +// Body.x9i_1 = 拥有者(坦克物理组件 ejt 在 y15j 里 `pbk().x9i_1 = this`) (function () { 'use strict'; @@ -6183,9 +6195,6 @@ return; } - // 坦克 category 位(bit0) - var TANK_CATEGORY_BIT = 1; - // ===== 字段抽象层(语义名 → 匿名字段,总表在 hornet_schema.js)===== var Schema = window.__hornetSchema__ || null; function Fld(Key, Fallback) { @@ -6196,139 +6205,114 @@ var N = Schema ? Schema.MethodName(Key) : null; return N || Fallback; } - var FPhysicsHull = Fld('Physics.Hull', 'zzq_1'); // 车体主碰撞形状 - var FShapeName = Fld('Shape.Name', 'x9j_1'); // 形状名('hull-main') - var FShapeMask = Fld('Shape.Mask', 'z9j_1'); // 碰撞掩码 - var FBodyShapes = 'y9i_1'; // 刚体上的形状列表(pbk().y9i_1) - var MAddMask = Mth('Physics.AddMask', 'n155'); - var MRemoveMask = Mth('Physics.RemoveMask', 'm155'); - var MGetBody = Mth('Physics.Body', 'pbk'); + function ExportKeys(Key, Fallback) { + if (Schema && Schema.Exports && Schema.Exports[Key]) return Schema.Exports[Key]; + return Fallback; + } + + var FPhysicsHull = Fld('Physics.Hull', 'zzq_1'); // 车体主碰撞形状 + var FShapeName = Fld('Shape.Name', 'x9j_1'); // 形状名('hull-main') + var FShapeBody = Fld('Shape.Body', 'b9k_1'); // 形状 → 刚体 + var FBodyOwner = Fld('Body.Owner', 'x9i_1'); // 刚体 → 拥有者 + var MRemoveMask = Mth('Physics.RemoveMask', 'm155'); // 坦克物理组件的识别方法 + var MCollide = Mth('CollisionWorld.ShouldCollide', 'q9q'); + var WorldKeys = ExportKeys('Export.PhysicsWorld', ['h']); + var PHYSICS_MODULE = 55088; // 碰撞世界所在模块 var Stat = { running: false, - shapes: 0, // 本帧处理的碰撞形状数 - applied: 0 // 本次开启累计生效帧数 + skipped: 0, // 累计跳过的坦克↔坦克配对次数 + patched: false }; - var CurrentPhysics = null; // 当前被处理的 tankPhysics 组件(ejt) - var MaskSnapshot = new Map(); // 形状 -> 进入时原始 mask - var LoggedPhysics = new WeakSet(); // 已打印过日志的组件 + var WorldProto = null; // 碰撞世界类原型(ki.prototype) + var OrigCollide = null; // 原始配对过滤 - // 判定是否坦克物理组件(ejt / 组件名 tankPhysics) - // 特征:zzq_1 是车体主碰撞形状(x9j_1 === "hull-main"),且带 n155 / m155 / pbk - function IsTankPhysics(Comp) { - if (!Comp) return false; - if (typeof Comp[MAddMask] !== 'function' || typeof Comp[MRemoveMask] !== 'function') return false; - if (typeof Comp[MGetBody] !== 'function') return false; - var Hull = Comp[FPhysicsHull]; + // 拥有者是否是坦克物理组件(ejt):带 m155,且 zzq_1.x9j_1 === 'hull-main' + function IsTankOwner(Owner) { + if (!Owner || typeof Owner !== 'object') return false; + if (typeof Owner[MRemoveMask] !== 'function') return false; + var Hull = Owner[FPhysicsHull]; return !!(Hull && Hull[FShapeName] === '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 IsTankShape(Shape) { + if (!Shape || typeof Shape !== 'object') return false; + var Body = Shape[FShapeBody]; + if (!Body) return false; + return IsTankOwner(Body[FBodyOwner]); } - // 本地坦克:拥有中的坦克优先;观战/死亡时退回当前观察目标(此时不参与战斗,影响可忽略) - 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) {} + // 取碰撞世界类原型:module 55088 的导出键 → 校验原型上确实有配对过滤方法 + function GetWorldProto() { + if (WorldProto) return WorldProto; + var Req = window.__wp_require__; + if (typeof Req !== 'function') return null; + var Mod = null; + try { Mod = Req(PHYSICS_MODULE); } catch (e) { return null; } + var Exports = Mod && Mod.$_$; + if (!Exports) return null; + for (var i = 0; i < WorldKeys.length; i++) { + var Klass = Exports[WorldKeys[i]]; + if (typeof Klass !== 'function' || !Klass.prototype) continue; + if (typeof Klass.prototype[MCollide] !== 'function') continue; + WorldProto = Klass.prototype; + return WorldProto; + } return null; } - // 还原快照里的原始 mask - function ReleaseSnapshot() { - MaskSnapshot.forEach(function (Mask, Shape) { - try { - if (Shape && typeof Mask === 'number') Shape[FShapeMask] = 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; + function InstallPatch() { + if (Stat.patched) return true; + var Proto = GetWorldProto(); + if (!Proto) { + console.warn('[穿坦克] 未找到碰撞世界(module ' + PHYSICS_MODULE + ' 导出键可能已变),本功能无效'); + return false; } - - var Body = null; - try { Body = Phys[MGetBody](); } catch (e) { Body = null; } - var Shapes = Body && Body[FBodyShapes]; - 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[FShapeMask] !== 'number') continue; - // 首次见到该形状时记录原始 mask,保证 Stop 能精确还原 - if (!MaskSnapshot.has(Shape)) MaskSnapshot.set(Shape, Shape[FShapeMask]); - Shape[FShapeMask] = Shape[FShapeMask] & ~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,可能版本字段有变')); + OrigCollide = Proto[MCollide]; + var Wrapper = function (ShapeA, ShapeB) { + // 开关打开且两边都是坦克 → 直接当作"不碰撞"返回(不生成接触点) + if (Stat.running && IsTankShape(ShapeA) && IsTankShape(ShapeB)) { + Stat.skipped++; + return undefined; } - } + return OrigCollide.apply(this, arguments); + }; + Proto[MCollide] = Wrapper; + Stat.patched = true; + console.log('[穿坦克] 已接管碰撞配对过滤(只拦坦克↔坦克,拾取物/地形/子弹不受影响)'); + return true; } - function Loop() { - if (!Stat.running) return; - requestAnimationFrame(Loop); - try { ApplyMask(); } catch (e) { - // 引擎中途换场景/重建物理时可能抛错,忽略即可(下一帧会重新尝试) - } + // 碰撞世界可能还没加载(在车库里提前开开关):每 0.5s 重试一次,加载后自动接管 + var RetryTimer = null; + function ScheduleRetry() { + if (RetryTimer) return; + RetryTimer = setInterval(function () { + if (!Stat.running) { clearInterval(RetryTimer); RetryTimer = null; return; } + if (InstallPatch()) { clearInterval(RetryTimer); RetryTimer = null; } + }, 500); + } + function CancelRetry() { + if (RetryTimer) { clearInterval(RetryTimer); RetryTimer = null; } } function _start() { if (Stat.running) return; Stat.running = true; - Stat.applied = 0; - ApplyMask(); - requestAnimationFrame(Loop); - console.log('[穿坦克] 已启动(只穿坦克,地形碰撞保留)'); + Stat.skipped = 0; + if (!InstallPatch()) { + console.warn('[穿坦克] 碰撞世界尚未加载,进入等待(加载后自动接管)'); + ScheduleRetry(); + } + console.log('[穿坦克] 已启动'); } function _stop() { if (!Stat.running) return; Stat.running = false; - ReleaseSnapshot(); - console.log('[穿坦克] 已停止,碰撞已还原'); + CancelRetry(); + console.log('[穿坦克] 已停止(碰撞恢复,无需还原任何状态)'); } var Tool = { @@ -6337,20 +6321,21 @@ GetRunning: function () { return Stat.running; }, Status: function () { if (!Stat.running) return '已停止'; - return Stat.shapes > 0 ? ('穿坦克 ' + Stat.shapes + ' 块') : '等待坦克'; + return Stat.patched ? ('拦截 ' + Stat.skipped + ' 对') : '等待碰撞世界'; } }; window.__hornetTools__ = window.__hornetTools__ || {}; window.__hornetTools__.NoClip = Tool; window.__hornetNoClip__ = { - IsTankPhysics: IsTankPhysics, + IsTankOwner: IsTankOwner, + IsTankShape: IsTankShape, GetStat: function () { - return { running: Stat.running, shapes: Stat.shapes, applied: Stat.applied }; + return { running: Stat.running, skipped: Stat.skipped, patched: Stat.patched }; } }; - console.log('[3DTank] 穿坦克模块已加载'); + console.log('[3DTank] 穿坦克模块已加载(配对过滤模式)'); })(); diff --git a/3dtank_bundle.js b/3dtank_bundle.js index 2408d72..f456422 100644 --- a/3dtank_bundle.js +++ b/3dtank_bundle.js @@ -279,6 +279,11 @@ 'Shape.Category': 'y9j_1', // bit0=坦克(1) bit3=地雷(8) bit4=地形(16) 'Shape.Mask': 'z9j_1', // 愿意碰撞的分类位 'Shape.Ghost': 'g9k_1', // true = 只探测不产生碰撞响应 + 'Shape.Body': 'b9k_1', // 形状 → 所属刚体(Fn) + 'Body.Owner': 'x9i_1', // 刚体 → 拥有者(坦克物理组件 ejt) + + // ---- 碰撞世界(module_55088 的 ki)---- + 'CollisionWorld.ShouldCollide': 'q9q()', // 唯一配对过滤:mask + 形状类型 → 生成接触 // ---- 实体事件总线 / 外力事件(ODt)---- 'Entity.Dispatch': 'kz7()', // 向实体内所有订阅者派发事件 @@ -329,7 +334,8 @@ // ⚠ 旧版是 kd / rk;新版 kd 已指向无关构造器 var Exports = { 'Export.TeamOf': ['vd', 'kd'], - 'Export.TeamSame': ['dl', 'rk'] + 'Export.TeamSame': ['dl', 'rk'], + 'Export.PhysicsWorld': ['h'] // module_55088.$_$.h = 碰撞世界类 ki }; // ===== 路径解析 ===== @@ -6156,23 +6162,29 @@ // ===== hornet_noclip.js ===== -// 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 后:撞墙照常,**只穿坦克**。 +// ⚠ 关键修正(2026-10-20):**不再改碰撞 mask**。 +// 之前清掉坦克形状 mask 的 bit0(1)会让拾取物失效 —— 坦克 category=1, +// 而拾取物的触发体(module_87072 的 Ilt:category=1、mask=1、半径触发) +// 判定需要「触发体.cat & 坦克.mask」成立,清掉 bit0 后就检测不到拾取物了。 // -// 为什么每帧补一次: -// 引擎在坦克状态切换时会重写 mask —— tjt() 调 n155(11)(把 bit0 加回来), -// onClientTankState 的 2/5 分支调 n155(1)。所以运行期间每帧把 bit0 清掉。 +// 现在的做法:在**碰撞世界的配对过滤**上精确拦截。 +// module_55088 的 ki.q9q(a, b) 是全物理世界唯一的配对判定: +// if (0 === (a.y9j_1 & b.z9j_1) || 0 === (b.y9j_1 & a.z9j_1)) return; // 不生成接触 +// ...按形状类型取配对处理器,生成接触点 +// 我们只在「a、b 两边的拥有者都是坦克物理组件」时直接返回, +// 等价于引擎自己的「不碰撞」分支 → 坦克之间没有接触点、没有推力,也不会互相卡住。 // -// 停止:按进入时快照的原始 mask 精确还原(不猜引擎默认值)。 +// 这样做的收益: +// · mask / category 一个字节都不改 → 地形、拾取物触发、地雷、子弹全部保持原样 +// · 关闭开关不需要还原任何状态(wrapper 直接转发原函数) +// · 只影响坦克↔坦克这一种配对,不波及静态物体与触发体 +// +// 形状 → 刚体 → 拥有者: +// Shape.b9k_1 = 刚体(Fn,u9j 里 `t.b9k_1 = this`) +// Body.x9i_1 = 拥有者(坦克物理组件 ejt 在 y15j 里 `pbk().x9i_1 = this`) (function () { 'use strict'; @@ -6183,9 +6195,6 @@ return; } - // 坦克 category 位(bit0) - var TANK_CATEGORY_BIT = 1; - // ===== 字段抽象层(语义名 → 匿名字段,总表在 hornet_schema.js)===== var Schema = window.__hornetSchema__ || null; function Fld(Key, Fallback) { @@ -6196,139 +6205,114 @@ var N = Schema ? Schema.MethodName(Key) : null; return N || Fallback; } - var FPhysicsHull = Fld('Physics.Hull', 'zzq_1'); // 车体主碰撞形状 - var FShapeName = Fld('Shape.Name', 'x9j_1'); // 形状名('hull-main') - var FShapeMask = Fld('Shape.Mask', 'z9j_1'); // 碰撞掩码 - var FBodyShapes = 'y9i_1'; // 刚体上的形状列表(pbk().y9i_1) - var MAddMask = Mth('Physics.AddMask', 'n155'); - var MRemoveMask = Mth('Physics.RemoveMask', 'm155'); - var MGetBody = Mth('Physics.Body', 'pbk'); + function ExportKeys(Key, Fallback) { + if (Schema && Schema.Exports && Schema.Exports[Key]) return Schema.Exports[Key]; + return Fallback; + } + + var FPhysicsHull = Fld('Physics.Hull', 'zzq_1'); // 车体主碰撞形状 + var FShapeName = Fld('Shape.Name', 'x9j_1'); // 形状名('hull-main') + var FShapeBody = Fld('Shape.Body', 'b9k_1'); // 形状 → 刚体 + var FBodyOwner = Fld('Body.Owner', 'x9i_1'); // 刚体 → 拥有者 + var MRemoveMask = Mth('Physics.RemoveMask', 'm155'); // 坦克物理组件的识别方法 + var MCollide = Mth('CollisionWorld.ShouldCollide', 'q9q'); + var WorldKeys = ExportKeys('Export.PhysicsWorld', ['h']); + var PHYSICS_MODULE = 55088; // 碰撞世界所在模块 var Stat = { running: false, - shapes: 0, // 本帧处理的碰撞形状数 - applied: 0 // 本次开启累计生效帧数 + skipped: 0, // 累计跳过的坦克↔坦克配对次数 + patched: false }; - var CurrentPhysics = null; // 当前被处理的 tankPhysics 组件(ejt) - var MaskSnapshot = new Map(); // 形状 -> 进入时原始 mask - var LoggedPhysics = new WeakSet(); // 已打印过日志的组件 + var WorldProto = null; // 碰撞世界类原型(ki.prototype) + var OrigCollide = null; // 原始配对过滤 - // 判定是否坦克物理组件(ejt / 组件名 tankPhysics) - // 特征:zzq_1 是车体主碰撞形状(x9j_1 === "hull-main"),且带 n155 / m155 / pbk - function IsTankPhysics(Comp) { - if (!Comp) return false; - if (typeof Comp[MAddMask] !== 'function' || typeof Comp[MRemoveMask] !== 'function') return false; - if (typeof Comp[MGetBody] !== 'function') return false; - var Hull = Comp[FPhysicsHull]; + // 拥有者是否是坦克物理组件(ejt):带 m155,且 zzq_1.x9j_1 === 'hull-main' + function IsTankOwner(Owner) { + if (!Owner || typeof Owner !== 'object') return false; + if (typeof Owner[MRemoveMask] !== 'function') return false; + var Hull = Owner[FPhysicsHull]; return !!(Hull && Hull[FShapeName] === '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 IsTankShape(Shape) { + if (!Shape || typeof Shape !== 'object') return false; + var Body = Shape[FShapeBody]; + if (!Body) return false; + return IsTankOwner(Body[FBodyOwner]); } - // 本地坦克:拥有中的坦克优先;观战/死亡时退回当前观察目标(此时不参与战斗,影响可忽略) - 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) {} + // 取碰撞世界类原型:module 55088 的导出键 → 校验原型上确实有配对过滤方法 + function GetWorldProto() { + if (WorldProto) return WorldProto; + var Req = window.__wp_require__; + if (typeof Req !== 'function') return null; + var Mod = null; + try { Mod = Req(PHYSICS_MODULE); } catch (e) { return null; } + var Exports = Mod && Mod.$_$; + if (!Exports) return null; + for (var i = 0; i < WorldKeys.length; i++) { + var Klass = Exports[WorldKeys[i]]; + if (typeof Klass !== 'function' || !Klass.prototype) continue; + if (typeof Klass.prototype[MCollide] !== 'function') continue; + WorldProto = Klass.prototype; + return WorldProto; + } return null; } - // 还原快照里的原始 mask - function ReleaseSnapshot() { - MaskSnapshot.forEach(function (Mask, Shape) { - try { - if (Shape && typeof Mask === 'number') Shape[FShapeMask] = 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; + function InstallPatch() { + if (Stat.patched) return true; + var Proto = GetWorldProto(); + if (!Proto) { + console.warn('[穿坦克] 未找到碰撞世界(module ' + PHYSICS_MODULE + ' 导出键可能已变),本功能无效'); + return false; } - - var Body = null; - try { Body = Phys[MGetBody](); } catch (e) { Body = null; } - var Shapes = Body && Body[FBodyShapes]; - 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[FShapeMask] !== 'number') continue; - // 首次见到该形状时记录原始 mask,保证 Stop 能精确还原 - if (!MaskSnapshot.has(Shape)) MaskSnapshot.set(Shape, Shape[FShapeMask]); - Shape[FShapeMask] = Shape[FShapeMask] & ~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,可能版本字段有变')); + OrigCollide = Proto[MCollide]; + var Wrapper = function (ShapeA, ShapeB) { + // 开关打开且两边都是坦克 → 直接当作"不碰撞"返回(不生成接触点) + if (Stat.running && IsTankShape(ShapeA) && IsTankShape(ShapeB)) { + Stat.skipped++; + return undefined; } - } + return OrigCollide.apply(this, arguments); + }; + Proto[MCollide] = Wrapper; + Stat.patched = true; + console.log('[穿坦克] 已接管碰撞配对过滤(只拦坦克↔坦克,拾取物/地形/子弹不受影响)'); + return true; } - function Loop() { - if (!Stat.running) return; - requestAnimationFrame(Loop); - try { ApplyMask(); } catch (e) { - // 引擎中途换场景/重建物理时可能抛错,忽略即可(下一帧会重新尝试) - } + // 碰撞世界可能还没加载(在车库里提前开开关):每 0.5s 重试一次,加载后自动接管 + var RetryTimer = null; + function ScheduleRetry() { + if (RetryTimer) return; + RetryTimer = setInterval(function () { + if (!Stat.running) { clearInterval(RetryTimer); RetryTimer = null; return; } + if (InstallPatch()) { clearInterval(RetryTimer); RetryTimer = null; } + }, 500); + } + function CancelRetry() { + if (RetryTimer) { clearInterval(RetryTimer); RetryTimer = null; } } function _start() { if (Stat.running) return; Stat.running = true; - Stat.applied = 0; - ApplyMask(); - requestAnimationFrame(Loop); - console.log('[穿坦克] 已启动(只穿坦克,地形碰撞保留)'); + Stat.skipped = 0; + if (!InstallPatch()) { + console.warn('[穿坦克] 碰撞世界尚未加载,进入等待(加载后自动接管)'); + ScheduleRetry(); + } + console.log('[穿坦克] 已启动'); } function _stop() { if (!Stat.running) return; Stat.running = false; - ReleaseSnapshot(); - console.log('[穿坦克] 已停止,碰撞已还原'); + CancelRetry(); + console.log('[穿坦克] 已停止(碰撞恢复,无需还原任何状态)'); } var Tool = { @@ -6337,20 +6321,21 @@ GetRunning: function () { return Stat.running; }, Status: function () { if (!Stat.running) return '已停止'; - return Stat.shapes > 0 ? ('穿坦克 ' + Stat.shapes + ' 块') : '等待坦克'; + return Stat.patched ? ('拦截 ' + Stat.skipped + ' 对') : '等待碰撞世界'; } }; window.__hornetTools__ = window.__hornetTools__ || {}; window.__hornetTools__.NoClip = Tool; window.__hornetNoClip__ = { - IsTankPhysics: IsTankPhysics, + IsTankOwner: IsTankOwner, + IsTankShape: IsTankShape, GetStat: function () { - return { running: Stat.running, shapes: Stat.shapes, applied: Stat.applied }; + return { running: Stat.running, skipped: Stat.skipped, patched: Stat.patched }; } }; - console.log('[3DTank] 穿坦克模块已加载'); + console.log('[3DTank] 穿坦克模块已加载(配对过滤模式)'); })(); diff --git a/tools/hornet_noclip.js b/tools/hornet_noclip.js index cf31843..e4638a2 100644 --- a/tools/hornet_noclip.js +++ b/tools/hornet_noclip.js @@ -1,20 +1,26 @@ -// 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 后:撞墙照常,**只穿坦克**。 +// ⚠ 关键修正(2026-10-20):**不再改碰撞 mask**。 +// 之前清掉坦克形状 mask 的 bit0(1)会让拾取物失效 —— 坦克 category=1, +// 而拾取物的触发体(module_87072 的 Ilt:category=1、mask=1、半径触发) +// 判定需要「触发体.cat & 坦克.mask」成立,清掉 bit0 后就检测不到拾取物了。 // -// 为什么每帧补一次: -// 引擎在坦克状态切换时会重写 mask —— tjt() 调 n155(11)(把 bit0 加回来), -// onClientTankState 的 2/5 分支调 n155(1)。所以运行期间每帧把 bit0 清掉。 +// 现在的做法:在**碰撞世界的配对过滤**上精确拦截。 +// module_55088 的 ki.q9q(a, b) 是全物理世界唯一的配对判定: +// if (0 === (a.y9j_1 & b.z9j_1) || 0 === (b.y9j_1 & a.z9j_1)) return; // 不生成接触 +// ...按形状类型取配对处理器,生成接触点 +// 我们只在「a、b 两边的拥有者都是坦克物理组件」时直接返回, +// 等价于引擎自己的「不碰撞」分支 → 坦克之间没有接触点、没有推力,也不会互相卡住。 // -// 停止:按进入时快照的原始 mask 精确还原(不猜引擎默认值)。 +// 这样做的收益: +// · mask / category 一个字节都不改 → 地形、拾取物触发、地雷、子弹全部保持原样 +// · 关闭开关不需要还原任何状态(wrapper 直接转发原函数) +// · 只影响坦克↔坦克这一种配对,不波及静态物体与触发体 +// +// 形状 → 刚体 → 拥有者: +// Shape.b9k_1 = 刚体(Fn,u9j 里 `t.b9k_1 = this`) +// Body.x9i_1 = 拥有者(坦克物理组件 ejt 在 y15j 里 `pbk().x9i_1 = this`) (function () { 'use strict'; @@ -25,9 +31,6 @@ return; } - // 坦克 category 位(bit0) - var TANK_CATEGORY_BIT = 1; - // ===== 字段抽象层(语义名 → 匿名字段,总表在 hornet_schema.js)===== var Schema = window.__hornetSchema__ || null; function Fld(Key, Fallback) { @@ -38,139 +41,114 @@ var N = Schema ? Schema.MethodName(Key) : null; return N || Fallback; } - var FPhysicsHull = Fld('Physics.Hull', 'zzq_1'); // 车体主碰撞形状 - var FShapeName = Fld('Shape.Name', 'x9j_1'); // 形状名('hull-main') - var FShapeMask = Fld('Shape.Mask', 'z9j_1'); // 碰撞掩码 - var FBodyShapes = 'y9i_1'; // 刚体上的形状列表(pbk().y9i_1) - var MAddMask = Mth('Physics.AddMask', 'n155'); - var MRemoveMask = Mth('Physics.RemoveMask', 'm155'); - var MGetBody = Mth('Physics.Body', 'pbk'); + function ExportKeys(Key, Fallback) { + if (Schema && Schema.Exports && Schema.Exports[Key]) return Schema.Exports[Key]; + return Fallback; + } + + var FPhysicsHull = Fld('Physics.Hull', 'zzq_1'); // 车体主碰撞形状 + var FShapeName = Fld('Shape.Name', 'x9j_1'); // 形状名('hull-main') + var FShapeBody = Fld('Shape.Body', 'b9k_1'); // 形状 → 刚体 + var FBodyOwner = Fld('Body.Owner', 'x9i_1'); // 刚体 → 拥有者 + var MRemoveMask = Mth('Physics.RemoveMask', 'm155'); // 坦克物理组件的识别方法 + var MCollide = Mth('CollisionWorld.ShouldCollide', 'q9q'); + var WorldKeys = ExportKeys('Export.PhysicsWorld', ['h']); + var PHYSICS_MODULE = 55088; // 碰撞世界所在模块 var Stat = { running: false, - shapes: 0, // 本帧处理的碰撞形状数 - applied: 0 // 本次开启累计生效帧数 + skipped: 0, // 累计跳过的坦克↔坦克配对次数 + patched: false }; - var CurrentPhysics = null; // 当前被处理的 tankPhysics 组件(ejt) - var MaskSnapshot = new Map(); // 形状 -> 进入时原始 mask - var LoggedPhysics = new WeakSet(); // 已打印过日志的组件 + var WorldProto = null; // 碰撞世界类原型(ki.prototype) + var OrigCollide = null; // 原始配对过滤 - // 判定是否坦克物理组件(ejt / 组件名 tankPhysics) - // 特征:zzq_1 是车体主碰撞形状(x9j_1 === "hull-main"),且带 n155 / m155 / pbk - function IsTankPhysics(Comp) { - if (!Comp) return false; - if (typeof Comp[MAddMask] !== 'function' || typeof Comp[MRemoveMask] !== 'function') return false; - if (typeof Comp[MGetBody] !== 'function') return false; - var Hull = Comp[FPhysicsHull]; + // 拥有者是否是坦克物理组件(ejt):带 m155,且 zzq_1.x9j_1 === 'hull-main' + function IsTankOwner(Owner) { + if (!Owner || typeof Owner !== 'object') return false; + if (typeof Owner[MRemoveMask] !== 'function') return false; + var Hull = Owner[FPhysicsHull]; return !!(Hull && Hull[FShapeName] === '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 IsTankShape(Shape) { + if (!Shape || typeof Shape !== 'object') return false; + var Body = Shape[FShapeBody]; + if (!Body) return false; + return IsTankOwner(Body[FBodyOwner]); } - // 本地坦克:拥有中的坦克优先;观战/死亡时退回当前观察目标(此时不参与战斗,影响可忽略) - 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) {} + // 取碰撞世界类原型:module 55088 的导出键 → 校验原型上确实有配对过滤方法 + function GetWorldProto() { + if (WorldProto) return WorldProto; + var Req = window.__wp_require__; + if (typeof Req !== 'function') return null; + var Mod = null; + try { Mod = Req(PHYSICS_MODULE); } catch (e) { return null; } + var Exports = Mod && Mod.$_$; + if (!Exports) return null; + for (var i = 0; i < WorldKeys.length; i++) { + var Klass = Exports[WorldKeys[i]]; + if (typeof Klass !== 'function' || !Klass.prototype) continue; + if (typeof Klass.prototype[MCollide] !== 'function') continue; + WorldProto = Klass.prototype; + return WorldProto; + } return null; } - // 还原快照里的原始 mask - function ReleaseSnapshot() { - MaskSnapshot.forEach(function (Mask, Shape) { - try { - if (Shape && typeof Mask === 'number') Shape[FShapeMask] = 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; + function InstallPatch() { + if (Stat.patched) return true; + var Proto = GetWorldProto(); + if (!Proto) { + console.warn('[穿坦克] 未找到碰撞世界(module ' + PHYSICS_MODULE + ' 导出键可能已变),本功能无效'); + return false; } - - var Body = null; - try { Body = Phys[MGetBody](); } catch (e) { Body = null; } - var Shapes = Body && Body[FBodyShapes]; - 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[FShapeMask] !== 'number') continue; - // 首次见到该形状时记录原始 mask,保证 Stop 能精确还原 - if (!MaskSnapshot.has(Shape)) MaskSnapshot.set(Shape, Shape[FShapeMask]); - Shape[FShapeMask] = Shape[FShapeMask] & ~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,可能版本字段有变')); + OrigCollide = Proto[MCollide]; + var Wrapper = function (ShapeA, ShapeB) { + // 开关打开且两边都是坦克 → 直接当作"不碰撞"返回(不生成接触点) + if (Stat.running && IsTankShape(ShapeA) && IsTankShape(ShapeB)) { + Stat.skipped++; + return undefined; } - } + return OrigCollide.apply(this, arguments); + }; + Proto[MCollide] = Wrapper; + Stat.patched = true; + console.log('[穿坦克] 已接管碰撞配对过滤(只拦坦克↔坦克,拾取物/地形/子弹不受影响)'); + return true; } - function Loop() { - if (!Stat.running) return; - requestAnimationFrame(Loop); - try { ApplyMask(); } catch (e) { - // 引擎中途换场景/重建物理时可能抛错,忽略即可(下一帧会重新尝试) - } + // 碰撞世界可能还没加载(在车库里提前开开关):每 0.5s 重试一次,加载后自动接管 + var RetryTimer = null; + function ScheduleRetry() { + if (RetryTimer) return; + RetryTimer = setInterval(function () { + if (!Stat.running) { clearInterval(RetryTimer); RetryTimer = null; return; } + if (InstallPatch()) { clearInterval(RetryTimer); RetryTimer = null; } + }, 500); + } + function CancelRetry() { + if (RetryTimer) { clearInterval(RetryTimer); RetryTimer = null; } } function _start() { if (Stat.running) return; Stat.running = true; - Stat.applied = 0; - ApplyMask(); - requestAnimationFrame(Loop); - console.log('[穿坦克] 已启动(只穿坦克,地形碰撞保留)'); + Stat.skipped = 0; + if (!InstallPatch()) { + console.warn('[穿坦克] 碰撞世界尚未加载,进入等待(加载后自动接管)'); + ScheduleRetry(); + } + console.log('[穿坦克] 已启动'); } function _stop() { if (!Stat.running) return; Stat.running = false; - ReleaseSnapshot(); - console.log('[穿坦克] 已停止,碰撞已还原'); + CancelRetry(); + console.log('[穿坦克] 已停止(碰撞恢复,无需还原任何状态)'); } var Tool = { @@ -179,18 +157,19 @@ GetRunning: function () { return Stat.running; }, Status: function () { if (!Stat.running) return '已停止'; - return Stat.shapes > 0 ? ('穿坦克 ' + Stat.shapes + ' 块') : '等待坦克'; + return Stat.patched ? ('拦截 ' + Stat.skipped + ' 对') : '等待碰撞世界'; } }; window.__hornetTools__ = window.__hornetTools__ || {}; window.__hornetTools__.NoClip = Tool; window.__hornetNoClip__ = { - IsTankPhysics: IsTankPhysics, + IsTankOwner: IsTankOwner, + IsTankShape: IsTankShape, GetStat: function () { - return { running: Stat.running, shapes: Stat.shapes, applied: Stat.applied }; + return { running: Stat.running, skipped: Stat.skipped, patched: Stat.patched }; } }; - console.log('[3DTank] 穿坦克模块已加载'); + console.log('[3DTank] 穿坦克模块已加载(配对过滤模式)'); })(); diff --git a/tools/hornet_schema.js b/tools/hornet_schema.js index 0c930b5..f3de0d8 100644 --- a/tools/hornet_schema.js +++ b/tools/hornet_schema.js @@ -64,6 +64,11 @@ 'Shape.Category': 'y9j_1', // bit0=坦克(1) bit3=地雷(8) bit4=地形(16) 'Shape.Mask': 'z9j_1', // 愿意碰撞的分类位 'Shape.Ghost': 'g9k_1', // true = 只探测不产生碰撞响应 + 'Shape.Body': 'b9k_1', // 形状 → 所属刚体(Fn) + 'Body.Owner': 'x9i_1', // 刚体 → 拥有者(坦克物理组件 ejt) + + // ---- 碰撞世界(module_55088 的 ki)---- + 'CollisionWorld.ShouldCollide': 'q9q()', // 唯一配对过滤:mask + 形状类型 → 生成接触 // ---- 实体事件总线 / 外力事件(ODt)---- 'Entity.Dispatch': 'kz7()', // 向实体内所有订阅者派发事件 @@ -114,7 +119,8 @@ // ⚠ 旧版是 kd / rk;新版 kd 已指向无关构造器 var Exports = { 'Export.TeamOf': ['vd', 'kd'], - 'Export.TeamSame': ['dl', 'rk'] + 'Export.TeamSame': ['dl', 'rk'], + 'Export.PhysicsWorld': ['h'] // module_55088.$_$.h = 碰撞世界类 ki }; // ===== 路径解析 =====