fix(noclip): 改为拦截碰撞配对,修复关闭后拾取物失效
穿坦克不再修改碰撞 mask(坦克 category 与拾取物触发体共用 bit0,清位会导致拾取失败且关闭后需重生才恢复)。改为包装 module_55088 碰撞世界 ki.q9q,仅跳过双方都是坦克物理组件的配对:地形、拾取物触发、地雷、子弹均保持原样,关闭开关无需还原状态。
This commit is contained in:
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] 穿坦克模块已加载(配对过滤模式)');
|
||||
})();
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user