fix(noclip): 改为拦截碰撞配对,修复关闭后拾取物失效

穿坦克不再修改碰撞 mask(坦克 category 与拾取物触发体共用 bit0,清位会导致拾取失败且关闭后需重生才恢复)。改为包装 module_55088 碰撞世界 ki.q9q,仅跳过双方都是坦克物理组件的配对:地形、拾取物触发、地雷、子弹均保持原样,关闭开关无需还原状态。
This commit is contained in:
TermiNexus committed 2026-10-20 20:39:29 +08:00
1 parent bea22bb5f9
commit 5a3440229e
4 files changed
+333 -378

No files matched your search

@@ -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] 穿坦克模块已加载(配对过滤模式)');
})();